/****************************************************************************
 * drivers/virtio/virtio-gpu.c
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <debug.h>

#include <nuttx/kmalloc.h>
#include <nuttx/percpu.h>
#include <nuttx/semaphore.h>
#include <nuttx/video/fb.h>
#include <nuttx/virtio/virtio.h>
#include <nuttx/kmalloc.h>

#include "virtio-gpu.h"

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

#define VIRTIO_GPU_BPP          32
#define VIRTIO_GPU_FB_FMT       FB_FMT_RGB32
#define VIRTIO_GPU_FMT          VIRTIO_GPU_FORMAT_B8G8R8X8_UNORM

#define VIRTIO_GPU_CTL          0
#define VIRTIO_GPU_NUM          1

#define VIRTIO_GPU_MAX_DISP     4
#define VIRTIO_GPU_MAX_PLANE    1
#define VIRTIO_GPU_MAX_NENTS    4

#define VIRTIO_GPU_FB_NUMS      2
#define VIRTIO_GPU_VSYNC_PERIOD 16

#define VIRTIO_GPU_MAP_ERR(e)   ((e) == VIRTIO_GPU_RESP_ERR_OUT_OF_MEMORY ? \
                                -ENOMEM : -EINVAL)

/****************************************************************************
 * Private Types
 ****************************************************************************/

union virtio_gpu_cmd_u
{
  FAR union virtio_gpu_cmd_u *next;
  struct virtio_gpu_resource_flush flush;
};

struct virtio_gpu_priv_s
{
  struct fb_vtable_s vtable;           /* Must be cast compatible with virtio_gpu_priv_s */
  FAR struct virtio_device *vdev;      /* Contained virtio device */
  FAR union virtio_gpu_cmd_u *freecmd; /* Command data free list */
  FAR uint8_t *fbmem;                  /* Allocated framebuffer */
  size_t fblen;                        /* Size of the framebuffer in bytes */
  fb_coord_t xres;                     /* Horizontal resolution in pixel columns */
  fb_coord_t yres;                     /* Vertical resolution in pixel rows */
  fb_coord_t stride;                   /* Width of a row in bytes */
  uint8_t display;                     /* Display number */
  spinlock_t lock;                     /* Lock */
  struct work_s work;                  /* Work structure for vsync loop */
};

struct virtio_gpu_backing_s
{
  struct virtio_gpu_resource_attach_backing cmd;
  struct virtio_gpu_mem_entry ents[VIRTIO_GPU_MAX_NENTS];
};

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

static int virtio_gpu_send_cmd(FAR struct virtqueue *vq,
                               FAR struct virtqueue_buf *buf_list,
                               int readable, int writable, FAR void *cookie);
static void virtio_gpu_done(FAR struct virtqueue *vq);
static int virtio_gpu_init(FAR struct virtio_gpu_priv_s *priv,
                           FAR struct virtio_device *vdev);
static int virtio_gpu_get_display_info(FAR struct virtio_gpu_priv_s *priv);
static int virtio_gpu_create_2d(FAR struct virtio_gpu_priv_s *priv,
                                int resource_id, int width, int height);
static int virtio_gpu_attach_backing(FAR struct virtio_gpu_priv_s *priv,
                                     int resource_id,
                                     FAR struct virtio_gpu_mem_entry *ents,
                                     uint32_t nents);
static int virtio_gpu_set_scanout(FAR struct virtio_gpu_priv_s *priv,
                                  int scanout_id, int resource_id,
                                  int width, int height);
static int virtio_gpu_transfer_to_host_2d(FAR struct virtio_gpu_priv_s *priv,
                                          int resource_id, int x, int y,
                                          int width, int height);
static int virtio_gpu_flush_resource(FAR struct virtio_gpu_priv_s *priv,
                                     int resource_id, int x, int y,
                                     int width, int height);
static int virtio_gpu_probe(FAR struct virtio_device *vdev);
static void virtio_gpu_remove(FAR struct virtio_device *vdev);
static int virtio_gpu_getvideoinfo(FAR struct fb_vtable_s *vtable,
                                   FAR struct fb_videoinfo_s *vinfo);
static int virtio_gpu_getplaneinfo(FAR struct fb_vtable_s *vtable,
                                   int planeno,
                                   FAR struct fb_planeinfo_s *pinfo);
#ifdef CONFIG_FB_UPDATE
static int virtio_gpu_updatearea(FAR struct fb_vtable_s *vtable,
                                 FAR const struct fb_area_s *area);
#endif
static int virtio_gpu_init_fbmem(FAR struct virtio_device *vdev,
                                 FAR struct virtio_gpu_priv_s *priv,
                                 int fb_count);
static int virtio_gpu_flush_output(FAR struct fb_vtable_s *vtable,
                                   int resource_id);

/****************************************************************************
 * Private Data
 ****************************************************************************/

static DEFINE_PER_CPU_BMP(struct virtio_driver, g_virtio_gpu_driver) =
{
  .device = VIRTIO_ID_GPU,     /* device id */
  .probe  = virtio_gpu_probe,  /* probe */
  .remove = virtio_gpu_remove, /* remove */
};
#define g_virtio_gpu_driver this_cpu_var_bmp(g_virtio_gpu_driver)

static DEFINE_PER_CPU_BMP(FAR struct virtio_gpu_priv_s *,
                          g_virtio_gpu[VIRTIO_GPU_MAX_DISP]);
#define g_virtio_gpu this_cpu_var_bmp(g_virtio_gpu)

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Name: virtio_gpu_zalloc_cmd
 ****************************************************************************/

static FAR void *virtio_gpu_zalloc_cmd(FAR struct virtio_gpu_priv_s *priv)
{
  irqstate_t flags = spin_lock_irqsave(&priv->lock);
  FAR union virtio_gpu_cmd_u *cmd = priv->freecmd;
  if (cmd != NULL)
    {
      priv->freecmd = cmd->next;
    }

  spin_unlock_irqrestore(&priv->lock, flags);

  if (cmd != NULL)
    {
      memset(cmd, 0, sizeof(*cmd));
    }
  else
    {
      cmd = virtio_zalloc_buf(priv->vdev, sizeof(*cmd), 16);
    }

  return cmd;
}

/****************************************************************************
 * Name: virtio_gpu_free_cmd
 ****************************************************************************/

static void virtio_gpu_free_cmd(FAR struct virtio_gpu_priv_s *priv,
                                FAR void *cmd)
{
  FAR union virtio_gpu_cmd_u *node = cmd;
  irqstate_t flags = spin_lock_irqsave(&priv->lock);
  node->next = priv->freecmd;
  priv->freecmd = node;
  spin_unlock_irqrestore(&priv->lock, flags);
}

/****************************************************************************
 * Name: virtio_gpu_send_cmd
 * Note: the caller should not touch `buf` after calling this, as it will be
 *       freed either here or in virtio_gpu_done().
 ****************************************************************************/

static int virtio_gpu_send_cmd(FAR struct virtqueue *vq,
                               FAR struct virtqueue_buf *buf_list,
                               int readable, int writable, FAR void *buf)
{
  FAR struct virtio_gpu_priv_s *priv = vq->vq_dev->priv;
  irqstate_t flags;
  int ret;

  if (writable > 0)
    {
      sem_t sem;

      if (buf)
        {
          virtio_gpu_free_cmd(priv, buf);
        }

      nxsem_init(&sem, 0, 0);
      flags = spin_lock_irqsave(&priv->lock);
      ret = virtqueue_add_buffer(vq, buf_list, readable, writable,
                                 (FAR void *)((uintptr_t)&sem | 0x01));
      if (ret >= 0)
        {
          virtqueue_kick(vq);
          spin_unlock_irqrestore(&priv->lock, flags);
          nxsem_wait_uninterruptible(&sem);
        }
      else
        {
          spin_unlock_irqrestore(&priv->lock, flags);
        }

      nxsem_destroy(&sem);
    }
  else
    {
      flags = spin_lock_irqsave(&priv->lock);
      ret = virtqueue_add_buffer(vq, buf_list, readable, writable, buf);
      if (ret >= 0)
        {
          virtqueue_kick(vq);
        }

      spin_unlock_irqrestore(&priv->lock, flags);

      if (buf && ret < 0)
        {
          virtio_gpu_free_cmd(priv, buf);
        }
    }

  return ret;
}

/****************************************************************************
 * Name: virtio_gpu_done
 ****************************************************************************/

static void virtio_gpu_done(FAR struct virtqueue *vq)
{
  FAR struct virtio_gpu_priv_s *priv = vq->vq_dev->priv;
  FAR void *cookie;

  while ((cookie =
          virtqueue_get_buffer_lock(vq, NULL, NULL, &priv->lock)) != NULL)
    {
      if ((uintptr_t)cookie & 0x01)
        {
          nxsem_post((FAR sem_t *)((uintptr_t)cookie ^ 0x01));
        }
      else
        {
          virtio_gpu_free_cmd(priv, cookie);
        }
    }
}

/****************************************************************************
 * Name: virtio_gpu_init
 ****************************************************************************/

static int virtio_gpu_init(FAR struct virtio_gpu_priv_s *priv,
                           FAR struct virtio_device *vdev)
{
  FAR const char *vqnames[VIRTIO_GPU_NUM];
  vq_callback callbacks[VIRTIO_GPU_NUM];
  int ret;

  spin_lock_init(&priv->lock);
  priv->vdev = vdev;
  vdev->priv = priv;

  /* Initialize the virtio device */

  virtio_set_status(vdev, VIRTIO_CONFIG_STATUS_DRIVER);
  virtio_set_features(vdev, 0);
  virtio_set_status(vdev, VIRTIO_CONFIG_FEATURES_OK);

  vqnames[VIRTIO_GPU_CTL]   = "virtio_gpu_ctl";
  callbacks[VIRTIO_GPU_CTL] = virtio_gpu_done;
  ret = virtio_create_virtqueues(vdev, 0, VIRTIO_GPU_NUM, vqnames,
                                 callbacks, NULL);
  if (ret < 0)
    {
      vrterr("virtio_device_create_virtqueue failed, ret=%d", ret);
      return ret;
    }

  virtio_set_status(vdev, VIRTIO_CONFIG_STATUS_DRIVER_OK);
  return OK;
}

/****************************************************************************
 * Name: virtio_gpu_get_display_info
 ****************************************************************************/

static int virtio_gpu_get_display_info(FAR struct virtio_gpu_priv_s *priv)
{
  FAR struct virtqueue *vq = priv->vdev->vrings_info[VIRTIO_GPU_CTL].vq;
  struct virtio_gpu_ctrl_hdr cmd;
  struct virtio_gpu_resp_display_info info;
  struct virtqueue_buf vb[2];
  int ret;

  memset(&cmd, 0, sizeof(cmd));
  cmd.type = VIRTIO_GPU_CMD_GET_DISPLAY_INFO;

  vb[0].buf = &cmd;
  vb[0].len = sizeof(cmd);
  vb[1].buf = &info;
  vb[1].len = sizeof(info);

  ret = virtio_gpu_send_cmd(vq, vb, 1, 1, NULL);
  if (ret < 0)
    {
      return ret;
    }

  if (info.hdr.type != VIRTIO_GPU_RESP_OK_DISPLAY_INFO)
    {
      return VIRTIO_GPU_MAP_ERR(info.hdr.type);
    }

  priv->xres = info.pmodes[0].r.width;
  priv->yres = info.pmodes[0].r.height;
  vrtinfo("Setting resolution: (%d,%d)", priv->xres, priv->yres);
  return OK;
}

/****************************************************************************
 * Name: virtio_gpu_create_2d
 ****************************************************************************/

static int virtio_gpu_create_2d(FAR struct virtio_gpu_priv_s *priv,
                                int resource_id, int width, int height)
{
  FAR struct virtqueue *vq = priv->vdev->vrings_info[VIRTIO_GPU_CTL].vq;
  struct virtio_gpu_resource_create_2d cmd;
  struct virtio_gpu_ctrl_hdr resp;
  struct virtqueue_buf vb[2];
  int ret;

  memset(&cmd, 0, sizeof(cmd));
  cmd.hdr.type = VIRTIO_GPU_CMD_RESOURCE_CREATE_2D;
  cmd.resource_id = resource_id;
  cmd.format = VIRTIO_GPU_FMT;
  cmd.width = width;
  cmd.height = height;

  vb[0].buf = &cmd;
  vb[0].len = sizeof(cmd);
  vb[1].buf = &resp;
  vb[1].len = sizeof(resp);

  ret = virtio_gpu_send_cmd(vq, vb, 1, 1, NULL);
  if (ret >= 0 && resp.type != VIRTIO_GPU_RESP_OK_NODATA)
    {
      ret = VIRTIO_GPU_MAP_ERR(resp.type);
    }

  return ret;
}

/****************************************************************************
 * Name: virtio_gpu_attach_backing
 ****************************************************************************/

static int virtio_gpu_attach_backing(FAR struct virtio_gpu_priv_s *priv,
                                     int resource_id,
                                     FAR struct virtio_gpu_mem_entry *ents,
                                     uint32_t nents)
{
  FAR struct virtqueue *vq = priv->vdev->vrings_info[VIRTIO_GPU_CTL].vq;
  struct virtio_gpu_backing_s backing;
  struct virtio_gpu_ctrl_hdr resp;
  struct virtqueue_buf vb[2];
  size_t i;
  int ret;

  if (nents > VIRTIO_GPU_MAX_NENTS)
    {
      vrterr("ERROR: Backing memory entries count %" PRId32 "exceeds %d",
             nents, VIRTIO_GPU_MAX_NENTS);
      return -E2BIG;
    }

  memset(&backing.cmd, 0, sizeof(backing.cmd));
  backing.cmd.hdr.type = VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING;
  backing.cmd.resource_id = resource_id;
  backing.cmd.nr_entries = nents;

  for (i = 0; i < nents; i++)
    {
      backing.ents[i] = ents[i];
    }

  vb[0].buf = &backing;
  vb[0].len = sizeof(backing.cmd) + nents * sizeof(backing.ents[0]);
  vb[1].buf = &resp;
  vb[1].len = sizeof(resp);

  ret = virtio_gpu_send_cmd(vq, vb, 1, 1, NULL);
  if (ret >= 0 && resp.type != VIRTIO_GPU_RESP_OK_NODATA)
    {
      ret = VIRTIO_GPU_MAP_ERR(resp.type);
    }

  return ret;
}

/****************************************************************************
 * Name: virtio_gpu_set_scanout
 ****************************************************************************/

static int virtio_gpu_set_scanout(FAR struct virtio_gpu_priv_s *priv,
                                  int scanout_id, int resource_id,
                                  int width, int height)
{
  FAR struct virtqueue *vq = priv->vdev->vrings_info[VIRTIO_GPU_CTL].vq;
  struct virtio_gpu_set_scanout cmd;
  struct virtio_gpu_ctrl_hdr resp;
  struct virtqueue_buf vb[2];
  int ret;

  memset(&cmd, 0, sizeof(cmd));
  cmd.hdr.type = VIRTIO_GPU_CMD_SET_SCANOUT;
  cmd.scanout_id = scanout_id;
  cmd.resource_id = resource_id;
  cmd.r.width = width;
  cmd.r.height = height;

  vb[0].buf = &cmd;
  vb[0].len = sizeof(cmd);
  vb[1].buf = &resp;
  vb[1].len = sizeof(resp);

  ret = virtio_gpu_send_cmd(vq, vb, 1, 1, NULL);
  if (ret >= 0 && resp.type != VIRTIO_GPU_RESP_OK_NODATA)
    {
      ret = VIRTIO_GPU_MAP_ERR(resp.type);
    }

  return ret;
}

/****************************************************************************
 * Name: virtio_gpu_transfer_to_host_2d
 ****************************************************************************/

static int virtio_gpu_transfer_to_host_2d(FAR struct virtio_gpu_priv_s *priv,
                                          int resource_id, int x, int y,
                                          int width, int height)
{
  FAR struct virtqueue *vq = priv->vdev->vrings_info[VIRTIO_GPU_CTL].vq;
  struct virtio_gpu_transfer_to_host_2d cmd;
  struct virtio_gpu_ctrl_hdr resp;
  struct virtqueue_buf vb[2];
  int ret;

  memset(&cmd, 0, sizeof(cmd));

  cmd.hdr.type = VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D;
  cmd.resource_id = resource_id;
  cmd.offset = y * priv->stride + x * (VIRTIO_GPU_BPP >> 3);
  cmd.r.x = x;
  cmd.r.y = y;
  cmd.r.width = width;
  cmd.r.height = height;

  vb[0].buf = &cmd;
  vb[0].len = sizeof(cmd);
  vb[1].buf = &resp;
  vb[1].len = sizeof(resp);

  ret = virtio_gpu_send_cmd(vq, vb, 1, 1, NULL);
  if (ret >= 0 && resp.type != VIRTIO_GPU_RESP_OK_NODATA)
    {
      ret = VIRTIO_GPU_MAP_ERR(resp.type);
    }

  return ret;
}

/****************************************************************************
 * Name: virtio_gpu_flush_resource
 ****************************************************************************/

static int virtio_gpu_flush_resource(FAR struct virtio_gpu_priv_s *priv,
                                     int resource_id, int x, int y,
                                     int width, int height)
{
  FAR struct virtqueue *vq = priv->vdev->vrings_info[VIRTIO_GPU_CTL].vq;
  FAR struct virtio_gpu_resource_flush *cmd;
  struct virtqueue_buf vb;

  cmd = virtio_gpu_zalloc_cmd(priv);
  if (cmd == NULL)
    {
      vrterr("ERROR: Failed to allocate cmd buffer");
      return -ENOMEM;
    }

  cmd->hdr.type = VIRTIO_GPU_CMD_RESOURCE_FLUSH;
  cmd->resource_id = resource_id;
  cmd->r.x = x;
  cmd->r.y = y;
  cmd->r.width = width;
  cmd->r.height = height;

  vb.buf = cmd;
  vb.len = sizeof(*cmd);

  return virtio_gpu_send_cmd(vq, &vb, 1, 0, cmd);
}

/****************************************************************************
 * Name: virtio_gpu_flush_output
 ****************************************************************************/

static int virtio_gpu_flush_output(FAR struct fb_vtable_s *vtable,
                                   int resource_id)
{
  FAR struct virtio_gpu_priv_s *priv =
    (FAR struct virtio_gpu_priv_s *)vtable;
  int ret;

  ret = virtio_gpu_set_scanout(priv, 0, resource_id, priv->xres, priv->yres);
  if (ret < 0)
    {
      vrterr("virtio_gpu_set_scanout failed: %d", ret);
      return ret;
    }

  return virtio_gpu_flush_resource(priv, resource_id, 0, 0,
                                   priv->xres, priv->yres);
}

/****************************************************************************
 * Name: virtio_gpu_vsync_loop
 ****************************************************************************/

static void virtio_gpu_vsync_loop(FAR void *arg)
{
  FAR struct virtio_gpu_priv_s *priv = (FAR struct virtio_gpu_priv_s *)arg;
  union fb_paninfo_u info;

  fb_notify_vsync(&priv->vtable);

  if (fb_peek_paninfo(&priv->vtable, &info, FB_NO_OVERLAY) == OK)
    {
      virtio_gpu_flush_output(&priv->vtable,
                              info.planeinfo.yoffset == 0 ? 1 : 2);
      fb_remove_paninfo(&priv->vtable, FB_NO_OVERLAY);
    }
}

/****************************************************************************
 * Name: virtio_gpu_vsync_interrupt
 ****************************************************************************/

static void virtio_gpu_vsync_interrupt(FAR void *arg)
{
  FAR struct virtio_gpu_priv_s *priv = (FAR struct virtio_gpu_priv_s *)arg;
  virtio_gpu_vsync_loop(arg);
  work_queue(HPWORK, &priv->work, (void *)virtio_gpu_vsync_interrupt,
             arg, VIRTIO_GPU_VSYNC_PERIOD);
}

/****************************************************************************
 * Name: virtio_gpu_init_fbmem
 ****************************************************************************/

static int virtio_gpu_init_fbmem(FAR struct virtio_device *vdev,
                                 FAR struct virtio_gpu_priv_s *priv,
                                 int fb_count)
{
  struct virtio_gpu_mem_entry ent;
  size_t fblen;
  int ret;
  int i;

  priv->stride = priv->xres * VIRTIO_GPU_BPP >> 3;
  fblen = priv->stride * priv->yres;

#ifdef CONFIG_BUILD_PROTECTED
  priv->fbmem = (FAR uint8_t *)kumm_calloc(fblen * fb_count, 16);
#else
  priv->fbmem = (FAR uint8_t *)virtio_zalloc_buf(vdev, fblen * fb_count, 16);
#endif
  if (priv->fbmem == NULL)
    {
      vrterr("ERROR: Failed to allocate frame buffer memory");
      return -ENOMEM;
    }

  for (i = 1; i <= fb_count; i++)
    {
      ret = virtio_gpu_create_2d(priv, i, priv->xres, priv->yres);
      if (ret < 0)
        {
          vrterr("virtio_gpu_create_2d error");
          goto error;
        }

      ent.addr = up_addrenv_va_to_pa(priv->fbmem + fblen * (i - 1));
      ent.length = fblen;

      ret = virtio_gpu_attach_backing(priv, i, &ent, 1);
      if (ret < 0)
        {
          vrterr("virtio_gpu_attach_backing error");
          goto error;
        }

      ret = virtio_gpu_transfer_to_host_2d(priv, i, 0, 0,
                                           priv->xres, priv->yres);
      if (ret < 0)
        {
          vrterr("virtio_gpu_transfer_to_host_2d failed: %d", ret);
          goto error;
        }
    }

  priv->fblen = fblen * fb_count;
  return ret;

error:
#ifdef CONFIG_BUILD_PROTECTED
  kumm_free(priv->fbmem);
#else
  virtio_free_buf(vdev, priv->fbmem);
#endif
  return ret;
}

/****************************************************************************
 * Name: virtio_gpu_probe
 ****************************************************************************/

static int virtio_gpu_probe(FAR struct virtio_device *vdev)
{
  FAR struct virtio_gpu_priv_s *priv;
  int disp;
  int ret;

  for (disp = 0; disp < VIRTIO_GPU_MAX_DISP; disp++)
    {
      if (g_virtio_gpu[disp] == NULL)
        {
          break;
        }
    }

  if (disp == VIRTIO_GPU_MAX_DISP)
    {
      return -EMFILE;
    }

  priv = kmm_zalloc(sizeof(*priv));
  if (priv == NULL)
    {
      return -ENOMEM;
    }

  ret = virtio_gpu_init(priv, vdev);
  if (ret < 0)
    {
      goto err_out;
    }

  ret = virtio_gpu_get_display_info(priv);
  if (ret < 0)
    {
      goto err_init;
    }

  /* Initialize the LCD-independent fields of the state structure */

  priv->vtable.getvideoinfo = virtio_gpu_getvideoinfo,
  priv->vtable.getplaneinfo = virtio_gpu_getplaneinfo,
#ifdef CONFIG_FB_UPDATE
  priv->vtable.updatearea   = virtio_gpu_updatearea,
#endif

  /* Allocate (and clear) the framebuffer */

  ret = virtio_gpu_init_fbmem(vdev, priv, VIRTIO_GPU_FB_NUMS);
  if (ret < 0)
    {
      vrterr("virtio_gpu_init_fbmem error");
      goto err_init;
    }

  ret = virtio_gpu_flush_output(&priv->vtable, 1);
  if (ret < 0)
    {
      vrterr("virtio_gpu_flush_output error");
      goto err_init_fb;
    }

  g_virtio_gpu[disp] = priv;
  priv->display = disp;

  ret = virtio_gpu_fb_register(disp);
  if (ret < 0)
    {
      vrterr("ERROR: Failed to initialize framebuffer driver, ret=%d",
             ret);
      g_virtio_gpu[disp] = NULL;
      goto err_init_fb;
    }

  work_queue(HPWORK, &priv->work, (void *)virtio_gpu_vsync_interrupt,
             priv, VIRTIO_GPU_VSYNC_PERIOD);

  return ret;

err_init_fb:
  virtio_free_buf(vdev, priv->fbmem);
err_init:
  virtio_reset_device(vdev);
  virtio_delete_virtqueues(vdev);
err_out:
  kmm_free(priv);
  return ret;
}

/****************************************************************************
 * Name: virtio_gpu_remove
 ****************************************************************************/

static void virtio_gpu_remove(FAR struct virtio_device *vdev)
{
  FAR struct virtio_gpu_priv_s *priv = vdev->priv;

  virtio_reset_device(vdev);
  virtio_delete_virtqueues(vdev);
  g_virtio_gpu[priv->display] = NULL;
  virtio_free_buf(vdev, priv->fbmem);
  kmm_free(priv);
}

/****************************************************************************
 * Name: virtio_gpu_getvideoinfo
 ****************************************************************************/

static int virtio_gpu_getvideoinfo(FAR struct fb_vtable_s *vtable,
                                   FAR struct fb_videoinfo_s *vinfo)
{
  FAR struct virtio_gpu_priv_s *priv =
    (FAR struct virtio_gpu_priv_s *)vtable;

  vinfo->fmt = VIRTIO_GPU_FB_FMT;
  vinfo->nplanes = VIRTIO_GPU_MAX_PLANE;
  vinfo->xres = priv->xres;
  vinfo->yres = priv->yres;
  return OK;
}

/****************************************************************************
 * Name: virtio_gpu_getplaneinfo
 ****************************************************************************/

static int virtio_gpu_getplaneinfo(FAR struct fb_vtable_s *vtable,
                                   int planeno,
                                   FAR struct fb_planeinfo_s *pinfo)
{
  FAR struct virtio_gpu_priv_s *priv =
    (FAR struct virtio_gpu_priv_s *)vtable;

  if (planeno >= VIRTIO_GPU_MAX_PLANE)
    {
      vrterr("ERROR: plane number %d exceeds %d",
             planeno, VIRTIO_GPU_MAX_PLANE - 1);
      return -EINVAL;
    }

  memset(pinfo, 0, sizeof(*pinfo));
  pinfo->bpp = VIRTIO_GPU_BPP;
  pinfo->display = priv->display;
  pinfo->fblen = priv->fblen;
  pinfo->fbmem = priv->fbmem;
  pinfo->stride = priv->stride;
  pinfo->xres_virtual = priv->xres;
  pinfo->yres_virtual = priv->yres * VIRTIO_GPU_FB_NUMS;
  return OK;
}

#ifdef CONFIG_FB_UPDATE
/****************************************************************************
 * Name: virtio_gpu_updatearea
 ****************************************************************************/

static int virtio_gpu_updatearea(FAR struct fb_vtable_s *vtable,
                                 FAR const struct fb_area_s *area)
{
  FAR struct virtio_gpu_priv_s *priv =
    (FAR struct virtio_gpu_priv_s *)vtable;
  int resource_id = 1;
  int ret = OK;

  vrtinfo("update disp %d:(%d %d)[%d %d]", priv->display,
          area->x, area->y, area->w, area->h);

  if (area->y >= priv->yres)
    {
      resource_id = 2;
    }

  ret = virtio_gpu_transfer_to_host_2d(priv, resource_id, 0, 0,
                                       priv->xres, priv->yres);
  if (ret < 0)
    {
      vrterr("virtio_gpu_transfer_to_host_2d failed: %d", ret);
    }

  return ret;
}
#endif

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: virtio_register_gpu_driver
 ****************************************************************************/

int virtio_register_gpu_driver(void)
{
  return virtio_register_driver(&g_virtio_gpu_driver);
}

/****************************************************************************
 * Name: virtio_gpu_fb_register
 *
 * Description:
 *   Initialize the framebuffer video hardware associated with the display.
 *
 * Input Parameters:
 *   display - The display number for the case of boards supporting multiple
 *             displays or for hardware that supports multiple
 *             layers (each layer is consider a display).  Typically zero.
 *
 * Returned Value:
 *   Zero (OK) is returned success; a negated errno value is returned on any
 *   failure.
 *
 ****************************************************************************/

int virtio_gpu_fb_register(int display)
{
  FAR struct fb_vtable_s *vtable;

  if (display < 0 || display >= VIRTIO_GPU_MAX_DISP ||
     !g_virtio_gpu[display])
    {
      vrterr("ERROR: display number %d is out of range [%d, %d]",
             display, 0, VIRTIO_GPU_MAX_DISP - 1);
      return -EINVAL;
    }

  vtable =  &g_virtio_gpu[display]->vtable;

  if (vtable == NULL)
    {
      vrterr("ERROR: get vtable failed\n");
      return -EINVAL;
    }

  return fb_register_device(display, 0, vtable);
}