/*
 * Virtio vhost-user GPU Device
 *
 * Copyright Red Hat, Inc. 2013-2018
 *
 * Authors:
 *     Dave Airlie <airlied@redhat.com>
 *     Gerd Hoffmann <kraxel@redhat.com>
 *     Marc-André Lureau <marcandre.lureau@redhat.com>
 *
 * This work is licensed under the terms of the GNU GPL, version 2 or later.
 * See the COPYING file in the top-level directory.
 */

#ifndef VUGPU_H
#define VUGPU_H


#include "libvhost-user-glib.h"
#include "standard-headers/linux/virtio_gpu.h"

#include "qemu/queue.h"
#include "qemu/iov.h"
#include "qemu/bswap.h"
#include "vugbm.h"

typedef enum VhostUserGpuRequest {
    VHOST_USER_GPU_NONE = 0,
    VHOST_USER_GPU_GET_PROTOCOL_FEATURES,
    VHOST_USER_GPU_SET_PROTOCOL_FEATURES,
    VHOST_USER_GPU_GET_DISPLAY_INFO,
    VHOST_USER_GPU_CURSOR_POS,
    VHOST_USER_GPU_CURSOR_POS_HIDE,
    VHOST_USER_GPU_CURSOR_UPDATE,
    VHOST_USER_GPU_SCANOUT,
    VHOST_USER_GPU_UPDATE,
    VHOST_USER_GPU_DMABUF_SCANOUT,
    VHOST_USER_GPU_DMABUF_UPDATE,
} VhostUserGpuRequest;

typedef struct VhostUserGpuDisplayInfoReply {
    struct virtio_gpu_resp_display_info info;
} VhostUserGpuDisplayInfoReply;

typedef struct VhostUserGpuCursorPos {
    uint32_t scanout_id;
    uint32_t x;
    uint32_t y;
} QEMU_PACKED VhostUserGpuCursorPos;

typedef struct VhostUserGpuCursorUpdate {
    VhostUserGpuCursorPos pos;
    uint32_t hot_x;
    uint32_t hot_y;
    uint32_t data[64 * 64];
} QEMU_PACKED VhostUserGpuCursorUpdate;

typedef struct VhostUserGpuScanout {
    uint32_t scanout_id;
    uint32_t width;
    uint32_t height;
} QEMU_PACKED VhostUserGpuScanout;

typedef struct VhostUserGpuUpdate {
    uint32_t scanout_id;
    uint32_t x;
    uint32_t y;
    uint32_t width;
    uint32_t height;
    uint8_t data[];
} QEMU_PACKED VhostUserGpuUpdate;

typedef struct VhostUserGpuDMABUFScanout {
    uint32_t scanout_id;
    uint32_t x;
    uint32_t y;
    uint32_t width;
    uint32_t height;
    uint32_t fd_width;
    uint32_t fd_height;
    uint32_t fd_stride;
    uint32_t fd_flags;
    int fd_drm_fourcc;
} QEMU_PACKED VhostUserGpuDMABUFScanout;

typedef struct VhostUserGpuMsg {
    uint32_t request; /* VhostUserGpuRequest */
    uint32_t flags;
    uint32_t size; /* the following payload size */
    union {
        VhostUserGpuCursorPos cursor_pos;
        VhostUserGpuCursorUpdate cursor_update;
        VhostUserGpuScanout scanout;
        VhostUserGpuUpdate update;
        VhostUserGpuDMABUFScanout dmabuf_scanout;
        struct virtio_gpu_resp_display_info display_info;
        uint64_t u64;
    } payload;
} QEMU_PACKED VhostUserGpuMsg;

static VhostUserGpuMsg m __attribute__ ((unused));
#define VHOST_USER_GPU_HDR_SIZE \
    (sizeof(m.request) + sizeof(m.flags) + sizeof(m.size))

#define VHOST_USER_GPU_MSG_FLAG_REPLY 0x4

struct virtio_gpu_scanout {
    uint32_t width, height;
    int x, y;
    int invalidate;
    uint32_t resource_id;
};

typedef struct VuGpu {
    VugDev dev;
    struct virtio_gpu_config virtio_config;
    struct vugbm_device gdev;
    int sock_fd;
    int drm_rnode_fd;
    GSource *renderer_source;
    guint wait_in;

    bool virgl;
    bool virgl_inited;
    uint32_t inflight;

    struct virtio_gpu_scanout scanout[VIRTIO_GPU_MAX_SCANOUTS];
    QTAILQ_HEAD(, virtio_gpu_simple_resource) reslist;
    QTAILQ_HEAD(, virtio_gpu_ctrl_command) fenceq;
} VuGpu;

enum {
    VG_CMD_STATE_NEW,
    VG_CMD_STATE_PENDING,
    VG_CMD_STATE_FINISHED,
};

struct virtio_gpu_ctrl_command {
    VuVirtqElement elem;
    VuVirtq *vq;
    struct virtio_gpu_ctrl_hdr cmd_hdr;
    uint32_t error;
    int state;
    QTAILQ_ENTRY(virtio_gpu_ctrl_command) next;
};

#define VUGPU_FILL_CMD(out) do {                                \
        size_t s;                                               \
        s = iov_to_buf(cmd->elem.out_sg, cmd->elem.out_num, 0,  \
                       &out, sizeof(out));                      \
        if (s != sizeof(out)) {                                 \
            g_critical("%s: command size incorrect %zu vs %zu", \
                       __func__, s, sizeof(out));               \
            return;                                             \
        }                                                       \
    } while (0)


void    vg_ctrl_response(VuGpu *g,
                         struct virtio_gpu_ctrl_command *cmd,
                         struct virtio_gpu_ctrl_hdr *resp,
                         size_t resp_len);

void    vg_ctrl_response_nodata(VuGpu *g,
                                struct virtio_gpu_ctrl_command *cmd,
                                enum virtio_gpu_ctrl_type type);

int     vg_create_mapping_iov(VuGpu *g,
                              struct virtio_gpu_resource_attach_backing *ab,
                              struct virtio_gpu_ctrl_command *cmd,
                              struct iovec **iov);
void    vg_cleanup_mapping_iov(VuGpu *g, struct iovec *iov, uint32_t count);
void    vg_get_display_info(VuGpu *vg, struct virtio_gpu_ctrl_command *cmd);

void    vg_wait_ok(VuGpu *g);

void    vg_send_msg(VuGpu *g, const VhostUserGpuMsg *msg, int fd);

bool    vg_recv_msg(VuGpu *g, uint32_t expect_req, uint32_t expect_size,
                    gpointer payload);


#endif
