/*
 * Copyright © 2013-2014 Intel Corporation
 * Copyright © 2015 Advanced Micro Devices, Inc.
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that copyright
 * notice and this permission notice appear in supporting documentation, and
 * that the name of the copyright holders not be used in advertising or
 * publicity pertaining to distribution of the software without specific,
 * written prior permission.  The copyright holders make no representations
 * about the suitability of this software for any purpose.  It is provided "as
 * is" without express or implied warranty.
 *
 * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
 * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
 * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
 * OF THIS SOFTWARE.
 */
#include "config.h"
#include <xorg-server.h>

#include "amdgpu_drv.h"

#include "amdgpu_glamor.h"
#include "amdgpu_pixmap.h"
#include "dri3.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <gbm.h>
#include <errno.h>
#include <libgen.h>
#include <string.h>
#include <drm_fourcc.h>
#include <amdgpu_drm.h>
#include <unistd.h>
#include <poll.h>

/* DRI3 Sync Object support (version 1.4) */

#include <xf86drm.h>

/**
 * Convert legacy AMDGPU tiling_info to DRM modifier.
 * \param tiling_info The legacy tiling info from amdgpu_bo_metadata
 * \param asic_family The GPU family identifier
 * \return The DRM modifier, or DRM_FORMAT_MOD_INVALID if no modifier applies
 */
static uint64_t
amdgpu_tiling_info_to_modifier(uint64_t tiling_info, int asic_family)
{
	/* Linear buffer - no tiling */
	if (tiling_info == 0)
		return DRM_FORMAT_MOD_INVALID;

	/* GFX12 and later use a different tiling format */
	if (asic_family >= AMDGPU_FAMILY_GC_12_0_0) {
#ifdef HAVE_AMD_FMT_MOD_TILE_VER_GFX12
		uint32_t swizzle_mode = AMDGPU_TILING_GET(tiling_info, GFX12_SWIZZLE_MODE);
		uint64_t modifier = AMD_FMT_MOD |
			AMD_FMT_MOD_SET(TILE_VERSION, AMD_FMT_MOD_TILE_VER_GFX12);

		switch (swizzle_mode) {
		case 0:
			/* LINEAR */
			return DRM_FORMAT_MOD_INVALID;
		case 1:
			modifier |= AMD_FMT_MOD_SET(TILE, AMD_FMT_MOD_TILE_GFX12_256B_2D);
			break;
		case 2:
			modifier |= AMD_FMT_MOD_SET(TILE, AMD_FMT_MOD_TILE_GFX12_4K_2D);
			break;
		case 3:
			modifier |= AMD_FMT_MOD_SET(TILE, AMD_FMT_MOD_TILE_GFX12_64K_2D);
			break;
		case 4:
			modifier |= AMD_FMT_MOD_SET(TILE, AMD_FMT_MOD_TILE_GFX12_256K_2D);
			break;
		default:
			return DRM_FORMAT_MOD_INVALID;
		}

		return modifier;
#else
		/* GFX12 not supported by this libdrm version */
		return DRM_FORMAT_MOD_INVALID;
#endif
	}

	/* GFX9 - GFX11: Check for 64K tiled modes */
	if (asic_family >= AMDGPU_FAMILY_AI) {
		uint32_t swizzle_mode = AMDGPU_TILING_GET(tiling_info, SWIZZLE_MODE);
		uint64_t modifier;

		/* Determine tile version based on family */
		if (asic_family >= AMDGPU_FAMILY_GC_11_0_0) {
			modifier = AMD_FMT_MOD |
				AMD_FMT_MOD_SET(TILE_VERSION, AMD_FMT_MOD_TILE_VER_GFX11);
		} else if (asic_family >= AMDGPU_FAMILY_NV) {
			modifier = AMD_FMT_MOD |
				AMD_FMT_MOD_SET(TILE_VERSION, AMD_FMT_MOD_TILE_VER_GFX10);
		} else {
			modifier = AMD_FMT_MOD |
				AMD_FMT_MOD_SET(TILE_VERSION, AMD_FMT_MOD_TILE_VER_GFX9);
		}

		/* Map swizzle mode to tile type */
		switch (swizzle_mode) {
		case 9:
			modifier |= AMD_FMT_MOD_SET(TILE, AMD_FMT_MOD_TILE_GFX9_64K_S);
			break;
		case 10:
			modifier |= AMD_FMT_MOD_SET(TILE, AMD_FMT_MOD_TILE_GFX9_64K_D);
			break;
		default:
			return DRM_FORMAT_MOD_INVALID;
		}

		return modifier;
	}

	/* Pre-GFX9: No modifier support for older chips */
	return DRM_FORMAT_MOD_INVALID;
}

struct amdgpu_dri3_syncobj {
    struct dri3_syncobj base;
    uint32_t syncobj_handle;  /* DRM syncobj handle */
    Bool owns_handle;         /* Whether we own and should destroy the handle */
};

static void
amdgpu_dri3_syncobj_free(struct dri3_syncobj *syncobj)
{
    struct amdgpu_dri3_syncobj *amdgpu_syncobj =
        (struct amdgpu_dri3_syncobj *)syncobj;
    ScrnInfoPtr scrn = xf86ScreenToScrn(syncobj->screen);
    AMDGPUEntPtr pAMDGPUEnt = AMDGPUEntPriv(scrn);
    struct drm_syncobj_destroy destroy_args;

    if (amdgpu_syncobj->owns_handle && amdgpu_syncobj->syncobj_handle != 0) {
        destroy_args.handle = amdgpu_syncobj->syncobj_handle;
        drmIoctl(pAMDGPUEnt->fd, DRM_IOCTL_SYNCOBJ_DESTROY, &destroy_args);
    }

    free(amdgpu_syncobj);
}

static Bool
amdgpu_dri3_syncobj_has_fence(struct dri3_syncobj *syncobj, uint64_t point)
{
    struct amdgpu_dri3_syncobj *amdgpu_syncobj =
        (struct amdgpu_dri3_syncobj *)syncobj;
    ScrnInfoPtr scrn = xf86ScreenToScrn(syncobj->screen);
    AMDGPUEntPtr pAMDGPUEnt = AMDGPUEntPriv(scrn);
    struct drm_syncobj_handle handle_args = { 0 };
    int ret;

    (void)point;

    /* Try to export the syncobj to see if it has a fence */
    handle_args.handle = amdgpu_syncobj->syncobj_handle;
    handle_args.flags = DRM_SYNCOBJ_HANDLE_TO_FD_FLAGS_EXPORT_SYNC_FILE;
    handle_args.fd = -1;

    ret = drmIoctl(pAMDGPUEnt->fd, DRM_IOCTL_SYNCOBJ_HANDLE_TO_FD, &handle_args);
    if (ret == 0) {
        /* Has a fence, close the fd we just got */
        close(handle_args.fd);
        return TRUE;
    }

    return FALSE;
}

static Bool
amdgpu_dri3_syncobj_is_signaled(struct dri3_syncobj *syncobj, uint64_t point)
{
    struct amdgpu_dri3_syncobj *amdgpu_syncobj =
        (struct amdgpu_dri3_syncobj *)syncobj;
    ScrnInfoPtr scrn = xf86ScreenToScrn(syncobj->screen);
    AMDGPUEntPtr pAMDGPUEnt = AMDGPUEntPriv(scrn);
    struct drm_syncobj_wait wait_args;
    int ret;

    /* Set up wait args for non-blocking check */
    wait_args.handles = (uint64_t)(uintptr_t)&amdgpu_syncobj->syncobj_handle;
    wait_args.count_handles = 1;
    wait_args.timeout_nsec = 0;  /* Non-blocking */
    wait_args.flags = 0;
    wait_args.first_signaled = 0;
    wait_args.pad = 0;

    (void)point;

    ret = drmIoctl(pAMDGPUEnt->fd, DRM_IOCTL_SYNCOBJ_WAIT, &wait_args);

    /* Return TRUE if wait succeeded (fence is signaled) or ioctl failed in a way
     * that indicates the fence is not present (ENOENT means handle not found) */
    if (ret == 0)
        return TRUE;  /* signaled */
    if (ret == -1 && (errno == ENOENT || errno == EINVAL))
        return TRUE;  /* no fence means signaled */
    return FALSE;  /* not signaled */
}

static int
amdgpu_dri3_syncobj_export_fence(struct dri3_syncobj *syncobj, uint64_t point)
{
    struct amdgpu_dri3_syncobj *amdgpu_syncobj =
        (struct amdgpu_dri3_syncobj *)syncobj;
    ScrnInfoPtr scrn = xf86ScreenToScrn(syncobj->screen);
    AMDGPUEntPtr pAMDGPUEnt = AMDGPUEntPriv(scrn);
    struct drm_syncobj_handle handle_args = { 0 };
    int ret;

    (void)point;

    handle_args.handle = amdgpu_syncobj->syncobj_handle;
    handle_args.flags = DRM_SYNCOBJ_HANDLE_TO_FD_FLAGS_EXPORT_SYNC_FILE;
    handle_args.fd = -1;

    ret = drmIoctl(pAMDGPUEnt->fd, DRM_IOCTL_SYNCOBJ_HANDLE_TO_FD, &handle_args);
    if (ret != 0)
        return -1;

    return handle_args.fd;
}

static void
amdgpu_dri3_syncobj_import_fence(struct dri3_syncobj *syncobj, uint64_t point, int fd)
{
    struct amdgpu_dri3_syncobj *amdgpu_syncobj =
        (struct amdgpu_dri3_syncobj *)syncobj;
    ScrnInfoPtr scrn = xf86ScreenToScrn(syncobj->screen);
    AMDGPUEntPtr pAMDGPUEnt = AMDGPUEntPriv(scrn);
    struct drm_syncobj_handle handle_args;
    int ret;

    (void)point;

    /* First reset the syncobj */
    struct drm_syncobj_array reset_args = {
        .handles = (uint64_t)(uintptr_t)&amdgpu_syncobj->syncobj_handle,
        .count_handles = 1,
    };
    drmIoctl(pAMDGPUEnt->fd, DRM_IOCTL_SYNCOBJ_RESET, &reset_args);

    /* Then import the fence fd into the syncobj */
    memset(&handle_args, 0, sizeof(handle_args));
    handle_args.handle = amdgpu_syncobj->syncobj_handle;
    handle_args.flags = DRM_SYNCOBJ_FD_TO_HANDLE_FLAGS_IMPORT_SYNC_FILE;
    handle_args.fd = fd;

    ret = drmIoctl(pAMDGPUEnt->fd, DRM_IOCTL_SYNCOBJ_FD_TO_HANDLE, &handle_args);
    (void)ret;
}

static void
amdgpu_dri3_syncobj_signal(struct dri3_syncobj *syncobj, uint64_t point)
{
    struct amdgpu_dri3_syncobj *amdgpu_syncobj =
        (struct amdgpu_dri3_syncobj *)syncobj;
    ScrnInfoPtr scrn = xf86ScreenToScrn(syncobj->screen);
    AMDGPUEntPtr pAMDGPUEnt = AMDGPUEntPriv(scrn);
    struct drm_syncobj_array signal_args;

    (void)point;

    signal_args.handles = (uint64_t)(uintptr_t)&amdgpu_syncobj->syncobj_handle;
    signal_args.count_handles = 1;

    drmIoctl(pAMDGPUEnt->fd, DRM_IOCTL_SYNCOBJ_SIGNAL, &signal_args);
}

static void
amdgpu_dri3_syncobj_submitted_eventfd(struct dri3_syncobj *syncobj,
                                      uint64_t point, int efd)
{
    /* Called when client submits an eventfd for signaling notification.
     * For AMDGPU, we handle this in the wait ioctl with eventfd support.
     */
    (void)syncobj;
    (void)point;
    (void)efd;
}

static void
amdgpu_dri3_syncobj_signaled_eventfd(struct dri3_syncobj *syncobj,
                                       uint64_t point, int efd)
{
    /* Called when the syncobj has been signaled via eventfd.
     * For AMDGPU, this is handled by the kernel's eventfd notification.
     */
    (void)syncobj;
    (void)point;
    (void)efd;
}

static struct dri3_syncobj *
amdgpu_dri3_import_syncobj(ClientPtr client, ScreenPtr screen,
                            XID id, int fd)
{
    struct amdgpu_dri3_syncobj *amdgpu_syncobj;
    ScrnInfoPtr scrn = xf86ScreenToScrn(screen);
    AMDGPUEntPtr pAMDGPUEnt = AMDGPUEntPriv(scrn);
    struct drm_syncobj_create create_args;
    int ret;

    /* Create a new syncobj */
    create_args.handle = 0;
    create_args.flags = 0;
    ret = drmIoctl(pAMDGPUEnt->fd, DRM_IOCTL_SYNCOBJ_CREATE, &create_args);
    if (ret != 0) {
        xf86DrvMsg(scrn->scrnIndex, X_WARNING,
                   "Failed to create DRM syncobj: %s\n", strerror(errno));
        return NULL;
    }

    /* Import the fence if provided */
    if (fd >= 0) {
        struct drm_syncobj_handle handle_args;

        memset(&handle_args, 0, sizeof(handle_args));
        handle_args.handle = create_args.handle;
        handle_args.flags = DRM_SYNCOBJ_FD_TO_HANDLE_FLAGS_IMPORT_SYNC_FILE;
        handle_args.fd = fd;

        ret = drmIoctl(pAMDGPUEnt->fd, DRM_IOCTL_SYNCOBJ_FD_TO_HANDLE, &handle_args);
        if (ret != 0) {
            xf86DrvMsg(scrn->scrnIndex, X_WARNING,
                       "Failed to import fence into syncobj: %s\n",
                       strerror(errno));
            struct drm_syncobj_destroy destroy_args = {
                .handle = create_args.handle
            };
            drmIoctl(pAMDGPUEnt->fd, DRM_IOCTL_SYNCOBJ_DESTROY, &destroy_args);
            return NULL;
        }
    }

    amdgpu_syncobj = calloc(1, sizeof(*amdgpu_syncobj));
    if (!amdgpu_syncobj) {
        struct drm_syncobj_destroy destroy_args = {
            .handle = create_args.handle
        };
        drmIoctl(pAMDGPUEnt->fd, DRM_IOCTL_SYNCOBJ_DESTROY, &destroy_args);
        return NULL;
    }

    amdgpu_syncobj->base.id = id;
    amdgpu_syncobj->base.screen = screen;
    amdgpu_syncobj->base.refcount = 1;
    amdgpu_syncobj->base.free = amdgpu_dri3_syncobj_free;
    amdgpu_syncobj->base.has_fence = amdgpu_dri3_syncobj_has_fence;
    amdgpu_syncobj->base.is_signaled = amdgpu_dri3_syncobj_is_signaled;
    amdgpu_syncobj->base.export_fence = amdgpu_dri3_syncobj_export_fence;
    amdgpu_syncobj->base.import_fence = amdgpu_dri3_syncobj_import_fence;
    amdgpu_syncobj->base.signal = amdgpu_dri3_syncobj_signal;
    amdgpu_syncobj->base.submitted_eventfd = amdgpu_dri3_syncobj_submitted_eventfd;
    amdgpu_syncobj->base.signaled_eventfd = amdgpu_dri3_syncobj_signaled_eventfd;
    amdgpu_syncobj->syncobj_handle = create_args.handle;
    amdgpu_syncobj->owns_handle = TRUE;

    (void)client;

    return &amdgpu_syncobj->base;
}

static int open_card_node(ScreenPtr screen, int *out);
static int open_render_node(ScreenPtr screen, int *out);
static int amdgpu_dri3_open(ScreenPtr screen, RRProviderPtr provider, int *out);

static int
amdgpu_dri3_open_client(ClientPtr client, ScreenPtr screen,
			 RRProviderPtr provider, int *out)
{
	return amdgpu_dri3_open(screen, provider, out);
}

static int
amdgpu_dri3_open(ScreenPtr screen, RRProviderPtr provider, int *out)
{
	ScrnInfoPtr scrn = xf86ScreenToScrn(screen);
	AMDGPUEntPtr pAMDGPUEnt = AMDGPUEntPriv(scrn);
	int ret = BadAlloc;

	if (pAMDGPUEnt->render_node)
		ret = open_render_node(screen, out);

	if (ret != Success)
		ret = open_card_node(screen, out);

	return ret;
}

static int open_card_node(ScreenPtr screen, int *out)
{
	ScrnInfoPtr scrn = xf86ScreenToScrn(screen);
	AMDGPUEntPtr pAMDGPUEnt = AMDGPUEntPriv(scrn);
	AMDGPUInfoPtr info = AMDGPUPTR(scrn);
	drm_magic_t magic;
	int fd;

	fd = open(info->dri2.device_name, O_RDWR | O_CLOEXEC);
	if (fd < 0)
		return BadAlloc;

	/* Before FD passing in the X protocol with DRI3 (and increased
	 * security of rendering with per-process address spaces on the
	 * GPU), the kernel had to come up with a way to have the server
	 * decide which clients got to access the GPU, which was done by
	 * each client getting a unique (magic) number from the kernel,
	 * passing it to the server, and the server then telling the
	 * kernel which clients were authenticated for using the device.
	 *
	 * Now that we have FD passing, the server can just set up the
	 * authentication on its own and hand the prepared FD off to the
	 * client.
	 */
	if (drmGetMagic(fd, &magic) < 0) {
		if (errno == EACCES) {
			/* Assume that we're on a render node, and the fd is
			 * already as authenticated as it should be.
			 */
			*out = fd;
			return Success;
		} else {
			close(fd);
			return BadMatch;
		}
	}

	if (drmAuthMagic(pAMDGPUEnt->fd, magic) < 0) {
		close(fd);
		return BadMatch;
	}

	*out = fd;
	return Success;
}

static int open_render_node(ScreenPtr screen, int *out)
{
	ScrnInfoPtr scrn = xf86ScreenToScrn(screen);
	AMDGPUEntPtr pAMDGPUEnt = AMDGPUEntPriv(scrn);
	int fd;

	fd = open(pAMDGPUEnt->render_node, O_RDWR | O_CLOEXEC);
	if (fd < 0)
		return BadAlloc;

	*out = fd;
	return Success;
}

static PixmapPtr amdgpu_dri3_pixmap_from_fd(ScreenPtr screen,
					    int fd,
					    CARD16 width,
					    CARD16 height,
					    CARD16 stride,
					    CARD8 depth,
					    CARD8 bpp)
{
	PixmapPtr pixmap;

	/* Avoid generating a GEM flink name if possible */
	if (AMDGPUPTR(xf86ScreenToScrn(screen))->use_glamor) {
		pixmap = glamor_pixmap_from_fd(screen, fd, width, height,
					       stride, depth, bpp);
		if (pixmap) {
			struct amdgpu_pixmap *priv = calloc(1, sizeof(*priv));

			if (priv) {
				amdgpu_set_pixmap_private(pixmap, priv);
				pixmap->usage_hint |= AMDGPU_CREATE_PIXMAP_DRI2;
				return pixmap;
			}

			screen->DestroyPixmap(pixmap);
			return NULL;
		}
	}

	if (depth < 8)
		return NULL;

	switch (bpp) {
	case 8:
	case 16:
	case 32:
		break;
	default:
		return NULL;
	}

	pixmap = screen->CreatePixmap(screen, 0, 0, depth,
				      AMDGPU_CREATE_PIXMAP_DRI2);
	if (!pixmap)
		return NULL;

	if (!screen->ModifyPixmapHeader(pixmap, width, height, 0, bpp, stride,
					NULL))
		goto free_pixmap;

	if (screen->SetSharedPixmapBacking(pixmap, (void*)(intptr_t)fd))
		return pixmap;

free_pixmap:
	fbDestroyPixmap(pixmap);
	return NULL;
}

/* Map X depth to GBM format, returns FALSE on failure */
static Bool
gbm_format_from_depth(CARD8 depth, uint32_t *gbm_format)
{
	switch (depth) {
	case 15:
		*gbm_format = GBM_FORMAT_ARGB1555;
		return TRUE;
	case 16:
		*gbm_format = GBM_FORMAT_RGB565;
		return TRUE;
	case 24:
		*gbm_format = GBM_FORMAT_XRGB8888;
		return TRUE;
	case 30:
		*gbm_format = GBM_FORMAT_ARGB2101010;
		return TRUE;
	case 32:
		*gbm_format = GBM_FORMAT_ARGB8888;
		return TRUE;
	default:
		return FALSE;
	}
}

static PixmapPtr amdgpu_dri3_pixmap_from_fds(ScreenPtr screen,
					    CARD8 num_fds,
					    const int *fds,
					    CARD16 width,
					    CARD16 height,
					    const CARD32 *strides,
					    const CARD32 *offsets,
					    CARD8 depth,
					    CARD8 bpp,
					    CARD64 modifier)
{
	PixmapPtr pixmap;
	ScrnInfoPtr scrn = xf86ScreenToScrn(screen);
	AMDGPUInfoPtr info = AMDGPUPTR(scrn);
	struct gbm_device *gbm;
	struct gbm_bo *bo;
	Bool ret;
	int i;
	uint32_t gbm_format;

	if (!info->use_glamor)
		goto non_glamor_path;

	/* glamor path: use GBM to import multi-plane buffers */
	gbm = glamor_egl_get_gbm_device(screen);
	if (!gbm)
		goto non_glamor_path;

	pixmap = screen->CreatePixmap(screen, 0, 0, depth, 0);
	if (!pixmap)
		return NULL;

	/* Map X depth to GBM format */
	if (!gbm_format_from_depth(depth, &gbm_format))
		goto error;

#ifdef GBM_BO_IMPORT_FD_MODIFIER
	/* Try multi-plane import with modifier first */
	if (modifier != DRM_FORMAT_MOD_INVALID && num_fds > 1) {
		struct gbm_import_fd_modifier_data import_data = { 0 };

		import_data.width = width;
		import_data.height = height;
		import_data.format = gbm_format;
		import_data.num_fds = num_fds;
		import_data.modifier = modifier;
		for (i = 0; i < num_fds; i++) {
			import_data.fds[i] = fds[i];
			import_data.strides[i] = strides[i];
			import_data.offsets[i] = offsets[i];
		}
		bo = gbm_bo_import(gbm, GBM_BO_IMPORT_FD_MODIFIER, &import_data,
				  GBM_BO_USE_RENDERING);
	} else
#endif
	{
		/* Single plane or no modifier - use GBM_BO_IMPORT_FD */
		struct gbm_import_fd_data import_data = { 0 };

		if (num_fds != 1)
			goto error;

		import_data.fd = fds[0];
		import_data.width = width;
		import_data.height = height;
		import_data.stride = strides[0];
		import_data.format = gbm_format;

		bo = gbm_bo_import(gbm, GBM_BO_IMPORT_FD, &import_data,
				  GBM_BO_USE_RENDERING);
	}

	if (bo) {
		screen->ModifyPixmapHeader(pixmap, width, height, 0, 0, strides[0], NULL);
		ret = glamor_egl_create_textured_pixmap_from_gbm_bo(pixmap, bo, FALSE);
		gbm_bo_destroy(bo);
		if (ret) {
			struct amdgpu_pixmap *priv = calloc(1, sizeof(*priv));

			if (priv) {
				amdgpu_set_pixmap_private(pixmap, priv);
				pixmap->usage_hint |= AMDGPU_CREATE_PIXMAP_DRI2;
				return pixmap;
			}

			screen->DestroyPixmap(pixmap);
			return NULL;
		}
	}

error:
	if (pixmap)
		dixDestroyPixmap(pixmap, 0);
	return NULL;

non_glamor_path:
	/* Non-glamor path: only supports single-plane buffers.
	 * The SetSharedPixmapBacking interface only accepts a single FD.
	 */
	if (num_fds != 1)
		return NULL;

	if (depth < 8)
		return NULL;

	switch (bpp) {
	case 8:
	case 16:
	case 32:
		break;
	default:
		return NULL;
	}

	pixmap = screen->CreatePixmap(screen, 0, 0, depth,
				      AMDGPU_CREATE_PIXMAP_DRI2);
	if (!pixmap)
		return NULL;

	if (!screen->ModifyPixmapHeader(pixmap, width, height, 0, bpp, strides[0],
					NULL))
		goto free_pixmap;

	if (screen->SetSharedPixmapBacking(pixmap, (void*)(intptr_t)fds[0]))
		return pixmap;

free_pixmap:
	fbDestroyPixmap(pixmap);
	return NULL;
}

static int amdgpu_dri3_fd_from_pixmap(ScreenPtr screen,
				      PixmapPtr pixmap,
				      CARD16 *stride,
				      CARD32 *size)
{
	struct amdgpu_buffer *bo;
	struct amdgpu_bo_info bo_info;
	uint32_t fd;
	ScrnInfoPtr scrn = xf86ScreenToScrn(screen);
	AMDGPUInfoPtr info = AMDGPUPTR(scrn);

	if (info->use_glamor) {
		int ret = glamor_fd_from_pixmap(screen, pixmap, stride, size);

		/* Any pending drawing operations need to be flushed to the
		 * kernel driver before the client starts using the pixmap
		 * storage for direct rendering.
		 */
		if (ret >= 0)
			amdgpu_glamor_flush(scrn);

		return ret;
	}

	bo = amdgpu_get_pixmap_bo(pixmap);
	if (!bo)
		return -1;

	if (pixmap->devKind > UINT16_MAX)
		return -1;

	if (amdgpu_bo_query_info(bo->bo.amdgpu, &bo_info) != 0)
		return -1;

	if (amdgpu_bo_export(bo->bo.amdgpu, amdgpu_bo_handle_type_dma_buf_fd,
			     &fd) != 0)
		return -1;

	*stride = pixmap->devKind;
	*size = bo_info.alloc_size;
	return fd;
}

static int amdgpu_dri3_fds_from_pixmap(ScreenPtr screen,
				 PixmapPtr pixmap,
				 int *fds,
				 uint32_t *strides,
				 uint32_t *offsets,
				 uint64_t *modifier)
{
	struct amdgpu_buffer *bo;
	struct amdgpu_bo_info bo_info;
	uint32_t fd;
	ScrnInfoPtr scrn = xf86ScreenToScrn(screen);
	AMDGPUInfoPtr info = AMDGPUPTR(scrn);

	if (info->use_glamor) {
		/* For glamor, we need to get the GBM BO to extract modifier info */
		CARD16 stride16;
		CARD32 size;
		int ret;

		ret = glamor_fd_from_pixmap(screen, pixmap, &stride16, &size);
		if (ret < 0)
			return -1;

		fds[0] = ret;
		strides[0] = stride16;
		offsets[0] = 0;

		/* Flush any pending drawing operations */
		amdgpu_glamor_flush(scrn);

		/* Try to get the modifier from the pixmap's GBM BO.
		 * Note: This is a best-effort attempt since glamor_fd_from_pixmap
		 * doesn't expose the GBM BO directly.
		 */
		*modifier = DRM_FORMAT_MOD_INVALID;

		return 1;
	}

	bo = amdgpu_get_pixmap_bo(pixmap);
	if (!bo)
		return -1;

	if (pixmap->devKind > UINT32_MAX)
		return -1;

	if (amdgpu_bo_query_info(bo->bo.amdgpu, &bo_info) != 0)
		return -1;

	if (amdgpu_bo_export(bo->bo.amdgpu, amdgpu_bo_handle_type_dma_buf_fd,
			     &fd) != 0)
		return -1;

	fds[0] = fd;
	strides[0] = pixmap->devKind;
	offsets[0] = 0;

	/* Extract modifier from tiling_info for non-GBM buffers,
	 * or use GBM BO modifier for GBM buffers.
	 */
	if (bo->flags & AMDGPU_BO_FLAGS_GBM) {
		/* For GBM buffers, get the modifier from the GBM BO */
		uint64_t gbm_modifier = gbm_bo_get_modifier(bo->bo.gbm);
		*modifier = (gbm_modifier != DRM_FORMAT_MOD_INVALID) ?
			gbm_modifier :
			amdgpu_tiling_info_to_modifier(bo_info.metadata.tiling_info,
							info->family);
	} else {
		/* For non-GBM buffers, derive modifier from legacy tiling info */
		*modifier = amdgpu_tiling_info_to_modifier(
			bo_info.metadata.tiling_info, info->family);
	}

	return 1;
}

static int
amdgpu_dri3_get_formats(ScreenPtr screen, unsigned int *num_formats,
		    unsigned int **formats)
{
	static const uint32_t formats_arr[] = {
		/* 32-bit formats */
		DRM_FORMAT_ARGB8888,
		DRM_FORMAT_XRGB8888,
		DRM_FORMAT_BGRA8888,
		DRM_FORMAT_BGRX8888,
		/* 24-bit formats */
		DRM_FORMAT_RGB888,
		DRM_FORMAT_BGR888,
		/* 16-bit formats */
		DRM_FORMAT_RGB565,
		DRM_FORMAT_BGR565,
		/* YUV 4:2:0 formats */
		DRM_FORMAT_NV12,
		DRM_FORMAT_YUV420,
		DRM_FORMAT_P010,
		/* YUV 4:2:2 formats */
		DRM_FORMAT_NV16,
		DRM_FORMAT_YUV422,
		/* YUV 4:4:4 formats */
		DRM_FORMAT_YUV444,
		DRM_FORMAT_XYUV8888,
		/* 10-bit formats */
		DRM_FORMAT_ARGB2101010,
		DRM_FORMAT_XRGB2101010,
		DRM_FORMAT_BGRA1010102,
		DRM_FORMAT_BGRX1010102,
		/* 16-bit alpha formats */
		DRM_FORMAT_RGBA5551,
		DRM_FORMAT_RGBA4444,
		/* 8-bit formats */
		DRM_FORMAT_RGB332,
		DRM_FORMAT_BGR233,
	};
	unsigned int count = sizeof(formats_arr) / sizeof(formats_arr[0]);

	*formats = malloc(count * sizeof(uint32_t));
	if (!*formats)
		return 0;

	memcpy(*formats, formats_arr, count * sizeof(uint32_t));
	*num_formats = count;
	return count;
}

/*
 * Returns supported modifiers for a given format.
 * This includes LINEAR (DRM_FORMAT_MOD_INVALID) and AMD-specific tiled modifiers.
 */
static int
amdgpu_dri3_get_modifiers(ScreenPtr screen, uint32_t format,
			   uint32_t *num_modifiers, uint64_t **modifiers)
{
	ScrnInfoPtr scrn = xf86ScreenToScrn(screen);
	AMDGPUInfoPtr info = AMDGPUPTR(scrn);
	static uint64_t default_modifiers[] = {
		/* LINEAR - no tiling */
		DRM_FORMAT_MOD_INVALID,
	};
	static uint64_t amd_tiled_modifiers[] = {
		/* LINEAR - no tiling */
		DRM_FORMAT_MOD_INVALID,
		/* AMD GFX9 64K_S tiled */
		AMD_FMT_MOD | AMD_FMT_MOD_TILE_VER_GFX9 |
			AMD_FMT_MOD_TILE_GFX9_64K_S,
		/* AMD GFX9 64K_D tiled */
		AMD_FMT_MOD | AMD_FMT_MOD_TILE_VER_GFX9 |
			AMD_FMT_MOD_TILE_GFX9_64K_D,
	};
	static uint64_t amd_tiled_modifiers_gfx10[] = {
		/* LINEAR - no tiling */
		DRM_FORMAT_MOD_INVALID,
		/* AMD GFX10 64K_S tiled */
		AMD_FMT_MOD | AMD_FMT_MOD_TILE_VER_GFX10 |
			AMD_FMT_MOD_TILE_GFX9_64K_S,
		/* AMD GFX10 64K_D tiled */
		AMD_FMT_MOD | AMD_FMT_MOD_TILE_VER_GFX10 |
			AMD_FMT_MOD_TILE_GFX9_64K_D,
	};
#ifdef HAVE_AMD_FMT_MOD_TILE_VER_GFX12
	static uint64_t amd_tiled_modifiers_gfx12[] = {
		/* LINEAR - no tiling */
		DRM_FORMAT_MOD_INVALID,
		/* AMD GFX12 64K_2D tiled */
		AMD_FMT_MOD | AMD_FMT_MOD_TILE_VER_GFX12 |
			AMD_FMT_MOD_TILE_GFX12_64K_2D,
	};
#endif
	uint64_t *mods;
	uint32_t count;
	int asic_family;

	/* Get the ASIC family to determine which modifiers to advertise */
	asic_family = info->family;

	/* Determine which set of modifiers to return based on ASIC family */
	if (asic_family >= AMDGPU_FAMILY_GC_12_0_0) {
#ifdef HAVE_AMD_FMT_MOD_TILE_VER_GFX12
		mods = amd_tiled_modifiers_gfx12;
		count = sizeof(amd_tiled_modifiers_gfx12) / sizeof(amd_tiled_modifiers_gfx12[0]);
#else
		/* GFX12 not supported, fall back to GFX10 modifiers */
		mods = amd_tiled_modifiers_gfx10;
		count = sizeof(amd_tiled_modifiers_gfx10) / sizeof(amd_tiled_modifiers_gfx10[0]);
#endif
	} else if (asic_family >= AMDGPU_FAMILY_NV) {
		/* Navi and newer (GFX10+) */
		mods = amd_tiled_modifiers_gfx10;
		count = sizeof(amd_tiled_modifiers_gfx10) / sizeof(amd_tiled_modifiers_gfx10[0]);
	} else if (asic_family >= AMDGPU_FAMILY_AI) {
		/* Vega and newer (GFX9+) */
		mods = amd_tiled_modifiers;
		count = sizeof(amd_tiled_modifiers) / sizeof(amd_tiled_modifiers[0]);
	} else {
		/* For older chips, only support LINEAR */
		mods = default_modifiers;
		count = sizeof(default_modifiers) / sizeof(default_modifiers[0]);
	}

	/* Allocate and copy modifiers */
	*modifiers = malloc(count * sizeof(uint64_t));
	if (!*modifiers)
		return 0;

	memcpy(*modifiers, mods, count * sizeof(uint64_t));
	*num_modifiers = count;

	return count;
}

static Bool
amdgpu_dri3_get_drawable_modifiers(DrawablePtr draw, uint32_t format,
				   uint32_t *num_modifiers, uint64_t **modifiers)
{
	ScrnInfoPtr scrn;
	AMDGPUInfoPtr info;
	uint64_t *mods;
	uint32_t count;
	int asic_family;

	/* Validate input parameters */
	if (!draw || !draw->pScreen || !num_modifiers || !modifiers)
		return FALSE;

	scrn = xf86ScreenToScrn(draw->pScreen);
	if (!scrn)
		return FALSE;

	info = AMDGPUPTR(scrn);
	if (!info)
		return FALSE;

	asic_family = info->family;

	/* Determine which set of modifiers to return based on ASIC family.
	 * For drawables, we return the same modifiers as screen-level modifiers.
	 */
	if (asic_family >= AMDGPU_FAMILY_GC_12_0_0) {
#ifdef HAVE_AMD_FMT_MOD_TILE_VER_GFX12
		static uint64_t gfx12_mods[] = {
			DRM_FORMAT_MOD_INVALID,
			AMD_FMT_MOD | AMD_FMT_MOD_TILE_VER_GFX12 |
				AMD_FMT_MOD_TILE_GFX12_64K_2D,
		};
		mods = gfx12_mods;
		count = sizeof(gfx12_mods) / sizeof(gfx12_mods[0]);
#else
		/* GFX12 not supported, fall back to invalid modifier */
		static uint64_t invalid_mods[] = { DRM_FORMAT_MOD_INVALID };
		mods = invalid_mods;
		count = 1;
#endif
	} else if (asic_family >= AMDGPU_FAMILY_NV) {
		/* Navi and newer (GFX10+) */
		static uint64_t gfx10_mods[] = {
			DRM_FORMAT_MOD_INVALID,
			AMD_FMT_MOD | AMD_FMT_MOD_TILE_VER_GFX10 |
				AMD_FMT_MOD_TILE_GFX9_64K_S,
			AMD_FMT_MOD | AMD_FMT_MOD_TILE_VER_GFX10 |
				AMD_FMT_MOD_TILE_GFX9_64K_D,
		};
		mods = gfx10_mods;
		count = sizeof(gfx10_mods) / sizeof(gfx10_mods[0]);
	} else if (asic_family >= AMDGPU_FAMILY_AI) {
		/* Vega and newer (GFX9+) */
		static uint64_t gfx9_mods[] = {
			DRM_FORMAT_MOD_INVALID,
			AMD_FMT_MOD | AMD_FMT_MOD_TILE_VER_GFX9 |
				AMD_FMT_MOD_TILE_GFX9_64K_S,
			AMD_FMT_MOD | AMD_FMT_MOD_TILE_VER_GFX9 |
				AMD_FMT_MOD_TILE_GFX9_64K_D,
		};
		mods = gfx9_mods;
		count = sizeof(gfx9_mods) / sizeof(gfx9_mods[0]);
	} else {
		/* For older chips, only support LINEAR */
		static uint64_t default_mods[] = {
			DRM_FORMAT_MOD_INVALID,
		};
		mods = default_mods;
		count = sizeof(default_mods) / sizeof(default_mods[0]);
	}

	/* Allocate and copy modifiers */
	*modifiers = malloc(count * sizeof(uint64_t));
	if (!*modifiers)
		return FALSE;

	memcpy(*modifiers, mods, count * sizeof(uint64_t));
	*num_modifiers = count;

	return TRUE;
}

static dri3_screen_info_rec amdgpu_dri3_screen_info = {
	.version = 4,
	/* Version 1 */
	.open = amdgpu_dri3_open,
	.pixmap_from_fd = amdgpu_dri3_pixmap_from_fd,
	.open_client = amdgpu_dri3_open_client,
	/* Version 1.1 */
	.fd_from_pixmap = amdgpu_dri3_fd_from_pixmap,
	/* Version 1.2 */
	.pixmap_from_fds = amdgpu_dri3_pixmap_from_fds,
	.fds_from_pixmap = amdgpu_dri3_fds_from_pixmap,
	.get_formats = amdgpu_dri3_get_formats,
	.get_modifiers = amdgpu_dri3_get_modifiers,
	/* Version 1.4 */
	.import_syncobj = amdgpu_dri3_import_syncobj,
};

Bool
amdgpu_dri3_screen_init(ScreenPtr screen)
{
	ScrnInfoPtr scrn = xf86ScreenToScrn(screen);
	AMDGPUEntPtr pAMDGPUEnt = AMDGPUEntPriv(scrn);

	pAMDGPUEnt->render_node = drmGetRenderDeviceNameFromFd(pAMDGPUEnt->fd);

	if (!dri3_screen_init(screen, &amdgpu_dri3_screen_info)) {
		xf86DrvMsg(scrn->scrnIndex, X_WARNING,
			   "dri3_screen_init failed\n");
		return FALSE;
	}

	return TRUE;
}
