/*
 * Copyright © 2013 Keith Packard
 *
 * 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 "dri3_priv.h"

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <linux/dma-heap.h>
#include <os.h>

#include <drm_fourcc.h>

#include <sys/mman.h>
#include "fb.h"

#define unused __attribute__((unused))
#define dri3_wrap(priv, real, mem, func) { priv = real->mem; real->mem = func; }
#define dri3_unwrap(priv, real, mem) { real->mem = priv; }

typedef struct dma_pixmap {
    void *map;
    int fd;
    size_t size;
} dma_pixmap;

#define MAX_TEXPIXMAPS 32
static PixmapPtr texpixmaps[MAX_TEXPIXMAPS];
static uint32_t num_texpixmaps;
static CARD32 update_texpixmaps(OsTimerPtr timer, CARD32 time, void *arg);
static OsTimerPtr texpixmaptimer;

void kasm_sync_dri3_textures(void);

static ScreenPtr pScreenPtr = NULL;
static DestroyPixmapProcPtr destoryPixmap = NULL;
static DevPrivateKeyRec loriePixmapPrivateKeyRec;

static void add_texpixmap(PixmapPtr pix)
{
    uint32_t i;
    for (i = 0; i < MAX_TEXPIXMAPS; i++) {
        if (texpixmaps[i] == pix)
            return;
    }

    for (i = 0; i < MAX_TEXPIXMAPS; i++) {
        if (!texpixmaps[i]) {
            texpixmaps[i] = pix;
            pix->refcnt++;
            num_texpixmaps++;
            // start if not running
            if (!texpixmaptimer)
                texpixmaptimer = TimerSet(NULL, 0, 16, update_texpixmaps, NULL);
            return;
        }
    }

    ErrorF("Max number of texpixmaps reached\n");
}

static int
os_dupfd_cloexec(int fd)
{
    int minfd = 3;
    int newfd = fcntl(fd, F_DUPFD_CLOEXEC, minfd);

    if (newfd >= 0)
        return newfd;

    if (errno != EINVAL)
        return -1;

    newfd = fcntl(fd, F_DUPFD, minfd);

    if (newfd < 0)
        return -1;

    long flags = fcntl(newfd, F_GETFD);
    if (flags == -1) {
        close(newfd);
        return -1;
    }

    if (fcntl(newfd, F_SETFD, flags | FD_CLOEXEC) == -1) {
        close(newfd);
        return -1;
    }

    return newfd;
}

static int
safe_ioctl(int fd, unsigned long request, void *arg)
{
    int ret;

    do {
       ret = ioctl(fd, request, arg);
    } while (ret == -1 && (errno == EINTR || errno == EAGAIN));

    return ret;
}

static int
dmabuf_alloc(uint64_t size)
{
    int ret;
    int dma_heap = open("/dev/dma_heap/system", O_RDONLY);

    if (dma_heap < 0) {
        int ion_heap = open("/dev/ion", O_RDONLY);

        if (ion_heap < 0)
            return -1;

        struct ion_allocation_data {
            __u64 len;
            __u32 heap_id_mask;
            __u32 flags;
            __u32 fd;
            __u32 unused;
        } alloc_data = {
            .len = size,
            /* ION_HEAP_SYSTEM | ION_SYSTEM_HEAP_ID */
            .heap_id_mask = (1U << 0) | (1U << 25),
            .flags = 0, /* uncached */
        };

        ret = safe_ioctl(ion_heap, _IOWR('I', 0, struct ion_allocation_data),
                        &alloc_data);

        close(ion_heap);

        if (ret)
            return -1;

        return alloc_data.fd;
    } else {
        struct dma_heap_allocation_data alloc_data = {
            .len = size,
            .fd_flags = O_RDWR | O_CLOEXEC,
        };

        ret = safe_ioctl(dma_heap, DMA_HEAP_IOCTL_ALLOC, &alloc_data);

        close(dma_heap);

        if (ret)
            return -1;

        return alloc_data.fd;
    }
}

static int lorieGetDrawableModifiers(DrawablePtr draw, uint32_t format, uint32_t *num_modifiers, uint64_t **modifiers) { return FALSE; }

static Bool
lorieDestroyPixmap(PixmapPtr pPixmap) {
    Bool ret;
    dma_pixmap *dp = NULL;
    //size_t size = 0;

    if (pPixmap->refcnt == 1) {
        dp = dixLookupPrivate(&pPixmap->devPrivates, &loriePixmapPrivateKeyRec);
    }

    dri3_unwrap(destoryPixmap, pScreenPtr, DestroyPixmap)
    ret = (*pScreenPtr->DestroyPixmap) (pPixmap);
    dri3_wrap(destoryPixmap, pScreenPtr, DestroyPixmap, lorieDestroyPixmap)

    if (dp)
    {
        if (dp->map)
            munmap(dp->map, dp->size);
        if (dp->fd != -1)
            close(dp->fd);
        free(dp);
    }
    return ret;
}

static int
lorieFdsFromPixmap(ScreenPtr screen, PixmapPtr pixmap, int *fds,
                     CARD32 *strides, CARD32 *offsets,
                     CARD64 *modifier)
{
    dma_pixmap *dp = dixLookupPrivate(&pixmap->devPrivates, &loriePixmapPrivateKeyRec);

    if (!dp) {
        dp = calloc(1, sizeof(dma_pixmap));
        if (!dp)
            return 0;

        dp->size = pixmap->devKind * pixmap->drawable.height;
        dp->fd = dmabuf_alloc(dp->size);
        if (dp->fd == -1) {
            ErrorF("DRI3: Failed to allocate dmabuf");
            return 0;
        }

        dixSetPrivate(&pixmap->devPrivates, &loriePixmapPrivateKeyRec, dp);
    }

    fds[0] = os_dupfd_cloexec(dp->fd);
    strides[0] = pixmap->devKind;
    offsets[0] = 0;
    *modifier = DRM_FORMAT_MOD_INVALID;

    add_texpixmap(pixmap);

    return 1;
}

static PixmapPtr
loriePixmapFromFds(ScreenPtr screen, CARD8 num_fds, const int *fds, CARD16 width, CARD16 height,
                                    const CARD32 *strides, const CARD32 *offsets, CARD8 depth, unused CARD8 bpp, CARD64 modifier) {
    const CARD64 RAW_MMAPPABLE_FD = 1274;
    dma_pixmap *dp;
    PixmapPtr pixmap;
    void *addr = NULL;

    dp = calloc(1, sizeof(dma_pixmap));
    if (!dp)
    {
        ErrorF("DRI3: Failed to calloc dma_pixmap");
        return NULL;
    }

    if (num_fds != 1 || modifier != RAW_MMAPPABLE_FD) {
        ErrorF("DRI3: More than 1 fd or modifier is not RAW_MMAPPABLE_FD");
        goto fail;
    }

    addr = mmap(NULL, strides[0] * height, PROT_READ, MAP_SHARED, fds[0], offsets[0]);
    if (!addr || addr == MAP_FAILED) {
        ErrorF("DRI3: mmap failed");
        goto fail;
    }

    pixmap = fbCreatePixmap(screen, 0, 0, depth, 0);
    if (!pixmap) {
        ErrorF("DRI3: failed to create pixmap");
        munmap(addr, strides[0] * height);
        goto fail;
    }

    dp->map = addr;
    dp->fd = -1;
    dp->size = strides[0] * height;

    dixSetPrivate(&pixmap->devPrivates, &loriePixmapPrivateKeyRec, dp);
    screen->ModifyPixmapHeader(pixmap, width, height, 0, 0, strides[0], addr);

    return pixmap;

fail:
    free(dp);
    return NULL;
}

static int
lorieGetFormats(unused ScreenPtr screen, CARD32 *num_formats, CARD32 **formats) {
    *num_formats = 0;
    *formats = NULL;
    return TRUE;
}

static int
lorieGetModifiers(unused ScreenPtr screen, unused uint32_t format, uint32_t *num_modifiers, uint64_t **modifiers) {
    *num_modifiers = 0;
    *modifiers = NULL;
    return TRUE;
}

static dri3_screen_info_rec dri3Info = {
            .version = 2,
            .fds_from_pixmap = lorieFdsFromPixmap,
            .pixmap_from_fds = loriePixmapFromFds,
            .get_formats = lorieGetFormats,
            .get_modifiers = lorieGetModifiers,
            .get_drawable_modifiers = lorieGetDrawableModifiers
};

void kasm_sync_dri3_textures(void)
{
    // Sync the tracked pixmaps into their textures (bos)
    // This is a bit of an ugly solution, but we don't know
    // when the pixmaps have changed nor when the textures are read.
    //
    // This is called both from the global damage report and the timer,
    // to account for cases that do not use the damage report.

    uint32_t i, y;
    dma_pixmap *dp;
    uint8_t *src, *dst;
    uint32_t srcstride, dststride;

    for (i = 0; i < MAX_TEXPIXMAPS; i++) {
        if (!texpixmaps[i])
            continue;
        if (texpixmaps[i]->refcnt == 1) {
            // We are the only user left, delete it
            dp = dixLookupPrivate(&texpixmaps[i]->devPrivates, &loriePixmapPrivateKeyRec);
            if (dp)
            {
                if (dp->map && dp->map != -1)
                    munmap(dp->map, dp->size);
                if (dp->fd != -1)
                    close(dp->fd);
                free(dp);
            }
            texpixmaps[i]->drawable.pScreen->DestroyPixmap(texpixmaps[i]);
            texpixmaps[i] = NULL;
            num_texpixmaps--;
            continue;
        }

        dp = dixLookupPrivate(&texpixmaps[i]->devPrivates, &loriePixmapPrivateKeyRec);
        dst = mmap(NULL, dp->size, PROT_READ | PROT_WRITE, MAP_SHARED, dp->fd, 0);
        if (dst == -1) {
            ErrorF("dma map failed, errno %d\n", errno);
            continue;
        }

        dp->map = dst;
        dststride = srcstride = texpixmaps[i]->devKind;
        src = texpixmaps[i]->devPrivate.ptr;

        for (y = 0; y < texpixmaps[i]->drawable.height; y++) {
            memcpy(dst, src, srcstride);
            dst += dststride;
            src += srcstride;
        }

        munmap(dp->map, dp->size);
    }
}

static CARD32 update_texpixmaps(OsTimerPtr timer, CARD32 time, void *arg)
{
    kasm_sync_dri3_textures();

    if (!num_texpixmaps) {
        TimerFree(texpixmaptimer);
        texpixmaptimer = NULL;
        return 0;
    }

    return 16; // Reschedule next tick
}

static Bool
init_dri3(ScreenPtr screen) {
    pScreenPtr = screen;
    dri3_wrap(destoryPixmap, screen, DestroyPixmap, lorieDestroyPixmap)
    return (!dri3_screen_init(screen, &dri3Info) ||
           !dixRegisterPrivateKey(&loriePixmapPrivateKeyRec, PRIVATE_PIXMAP, 0));
}


static int dri3_request;
DevPrivateKeyRec dri3_screen_private_key;

static int dri3_screen_generation;

static Bool
dri3_close_screen(ScreenPtr screen)
{
    dri3_screen_priv_ptr screen_priv = dri3_screen_priv(screen);

    unwrap(screen_priv, screen, CloseScreen);

    free(screen_priv);
    return (*screen->CloseScreen) (screen);
}

Bool
dri3_screen_init(ScreenPtr screen, const dri3_screen_info_rec *info)
{
    dri3_screen_generation = serverGeneration;

    if (!dixRegisterPrivateKey(&dri3_screen_private_key, PRIVATE_SCREEN, 0))
        return FALSE;

    if (!dri3_screen_priv(screen)) {
        dri3_screen_priv_ptr screen_priv = calloc(1, sizeof (dri3_screen_priv_rec));
        if (!screen_priv)
            return FALSE;

        wrap(screen_priv, screen, CloseScreen, dri3_close_screen);

        screen_priv->info = info;

        dixSetPrivate(&screen->devPrivates, &dri3_screen_private_key, screen_priv);
    }

    return TRUE;
}

void
dri3_extension_init(void)
{
    ExtensionEntry *extension;

#ifdef PANORAMIX
    if (!noPanoramiXExtension)
        FatalError("[DRI3]Failed: if (!noPanoramiXExtension)\n");
#endif

    extension = AddExtension(DRI3_NAME, DRI3NumberEvents, DRI3NumberErrors,
                             proc_dri3_dispatch, sproc_dri3_dispatch,
                             NULL, StandardMinorOpcode);
    if (!extension)
        goto bail;

    dri3_request = extension->base;

    if (screenInfo.numScreens != 1)
    {
        ErrorF("[DRI3]Multi-screen isn't supported\n");
        goto bail;
    }
    if (init_dri3(screenInfo.screens[0]))
        goto bail;

    ErrorF("DRI3 enable successfully\n");
    return;

bail:
    FatalError("Cannot initialize DRI3 extension");
}

uint32_t
drm_format_for_depth(uint32_t depth, uint32_t bpp)
{
    switch (bpp) {
        case 16:
            return DRM_FORMAT_RGB565;
        case 24:
            return DRM_FORMAT_XRGB8888;
        case 30:
            return DRM_FORMAT_XRGB2101010;
        case 32:
            return DRM_FORMAT_ARGB8888;
        default:
            return 0;
    }
}
