/****************************************************************************
 * drivers/rpmsg/rpmsg_virtio.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 <nuttx/config.h>

#include <debug.h>
#include <stdio.h>
#include <sys/param.h>

#include <metal/cache.h>
#include <nuttx/kmalloc.h>
#include <nuttx/kthread.h>
#include <nuttx/nuttx.h>
#include <nuttx/power/pm.h>
#include <nuttx/semaphore.h>
#include <nuttx/rpmsg/rpmsg_virtio.h>
#include <rpmsg/rpmsg_internal.h>

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

#define RPMSG_VIRTIO_TIMEOUT_MS      20

#define RPMSG_VIRTIO_VDEV_NOTIFYID   0
#define RPMSG_VIRTIO_VRING0_NOTIFYID 1
#define RPMSG_VIRTIO_VRING1_NOTIFYID 2

#ifdef CONFIG_OPENAMP_CACHE
#  define RPMSG_VIRTIO_INVALIDATE(x) metal_cache_invalidate(&x, sizeof(x))
#else
#  define RPMSG_VIRTIO_INVALIDATE(x)
#endif

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

struct rpmsg_virtio_priv_s
{
  struct rpmsg_s                rpmsg;
  struct rpmsg_virtio_device    rvdev;
  FAR struct rpmsg_virtio_s     *dev;
  FAR struct rpmsg_virtio_rsc_s *rsc;
  struct virtio_device          vdev;
  struct rpmsg_virtio_shm_pool  pool[2];
  struct virtio_vring_info      rvrings[2];
  sem_t                         semtx;
  sem_t                         semrx;
  pid_t                         tid;
  uint16_t                      headrx;
#ifdef CONFIG_RPMSG_VIRTIO_PM
  struct pm_wakelock_s          wakelock;
  struct wdog_s                 wdog;
#endif
};

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

static int rpmsg_virtio_wait(FAR struct rpmsg_s *rpmsg, FAR sem_t *sem);
static int rpmsg_virtio_post(FAR struct rpmsg_s *rpmsg, FAR sem_t *sem);
static void rpmsg_virtio_panic(FAR struct rpmsg_s *rpmsg);
static void rpmsg_virtio_dump(FAR struct rpmsg_s *rpmsg);
static FAR const char *
rpmsg_virtio_get_local_cpuname(FAR struct rpmsg_s *rpmsg);
static FAR const char *rpmsg_virtio_get_cpuname(FAR struct rpmsg_s *rpmsg);

static int rpmsg_virtio_create_virtqueues_(FAR struct virtio_device *vdev,
                                           unsigned int flags,
                                           unsigned int nvqs,
                                           FAR const char *names[],
                                           vq_callback callbacks[],
                                           FAR void *callback_args[]);
static uint8_t rpmsg_virtio_get_status_(FAR struct virtio_device *dev);
static void rpmsg_virtio_set_status_(FAR struct virtio_device *dev,
                                     uint8_t status);
static uint64_t rpmsg_virtio_get_features_(FAR struct virtio_device *dev);
static void rpmsg_virtio_set_features(FAR struct virtio_device *dev,
                                      uint64_t feature);
static void rpmsg_virtio_notify(FAR struct virtqueue *vq);

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

static const struct rpmsg_ops_s g_rpmsg_virtio_ops =
{
  .wait               = rpmsg_virtio_wait,
  .post               = rpmsg_virtio_post,
  .panic              = rpmsg_virtio_panic,
  .dump               = rpmsg_virtio_dump,
  .get_local_cpuname  = rpmsg_virtio_get_local_cpuname,
  .get_cpuname        = rpmsg_virtio_get_cpuname,
};

static const struct virtio_dispatch g_rpmsg_virtio_dispatch =
{
  .create_virtqueues = rpmsg_virtio_create_virtqueues_,
  .get_status        = rpmsg_virtio_get_status_,
  .set_status        = rpmsg_virtio_set_status_,
  .get_features      = rpmsg_virtio_get_features_,
  .set_features      = rpmsg_virtio_set_features,
  .notify            = rpmsg_virtio_notify,
};

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

static int rpmsg_virtio_buffer_nused(FAR struct rpmsg_virtio_device *rvdev,
                                     bool rx)
{
  FAR struct virtqueue *vq = rx ? rvdev->rvq : rvdev->svq;
  uint16_t nused = vq->vq_ring.avail->idx - vq->vq_ring.used->idx;
  bool is_host = rpmsg_virtio_get_role(rvdev) == RPMSG_HOST;

  if (is_host)
    {
      RPMSG_VIRTIO_INVALIDATE(vq->vq_ring.used->idx);
    }
  else
    {
      RPMSG_VIRTIO_INVALIDATE(vq->vq_ring.avail->idx);
    }

  if (is_host ^ rx)
    {
      return nused;
    }
  else
    {
      return vq->vq_nentries - nused;
    }
}

static void rpmsg_virtio_wakeup_tx(FAR struct rpmsg_virtio_priv_s *priv)
{
  int semcount;

  nxsem_get_value(&priv->semtx, &semcount);
  while (semcount++ < 1)
    {
      nxsem_post(&priv->semtx);
    }
}

#ifdef CONFIG_RPMSG_VIRTIO_PM

#ifdef CONFIG_RPMSG_VIRTIO_PM_AUTORELAX
static void rpmsg_virtio_pm_callback(wdparm_t arg)
{
  FAR struct rpmsg_virtio_priv_s *priv =
    (FAR struct rpmsg_virtio_priv_s *)arg;

  if (rpmsg_virtio_buffer_nused(&priv->rvdev, false))
    {
      rpmsg_virtio_wakeup_tx(priv);

      wd_start(&priv->wdog, MSEC2TICK(RPMSG_VIRTIO_TIMEOUT_MS),
               rpmsg_virtio_pm_callback, (wdparm_t)priv);
    }
  else
    {
      pm_wakelock_relax(&priv->wakelock);
    }
}
#endif

static inline void
rpmsg_virtio_pm_action(FAR struct rpmsg_virtio_priv_s *priv, bool stay)
{
  irqstate_t flags;
  int count;

  flags = enter_critical_section();

  count = pm_wakelock_staycount(&priv->wakelock);
  if (stay && count == 0)
    {
      pm_wakelock_stay(&priv->wakelock);
#ifdef CONFIG_RPMSG_VIRTIO_PM_AUTORELAX
      wd_start(&priv->wdog, MSEC2TICK(RPMSG_VIRTIO_TIMEOUT_MS),
               rpmsg_virtio_pm_callback, (wdparm_t)priv);
#endif
    }

#ifndef CONFIG_RPMSG_VIRTIO_PM_AUTORELAX
  if (!stay && count > 0 &&
      rpmsg_virtio_buffer_nused(&priv->rvdev, false) == 0)
    {
      pm_wakelock_relax(&priv->wakelock);
    }
#endif

  leave_critical_section(flags);
}

static inline bool
rpmsg_virtio_available_rx(FAR struct rpmsg_virtio_priv_s *priv)
{
  FAR struct rpmsg_virtio_device *rvdev = &priv->rvdev;
  FAR struct virtqueue *rvq = rvdev->rvq;

  if (rpmsg_virtio_get_role(rvdev) == RPMSG_HOST)
    {
      return priv->headrx != rvq->vq_used_cons_idx;
    }
  else
    {
      return priv->headrx != rvq->vq_available_idx;
    }
}

#else
#  define rpmsg_virtio_pm_action(priv, stay)
#  define rpmsg_virtio_available_rx(priv) true
#endif

static inline void
rpmsg_virtio_update_rx(FAR struct rpmsg_virtio_priv_s *priv)
{
  FAR struct rpmsg_virtio_device *rvdev = &priv->rvdev;
  FAR struct virtqueue *rvq = rvdev->rvq;

  if (rpmsg_virtio_get_role(rvdev) == RPMSG_HOST)
    {
      RPMSG_VIRTIO_INVALIDATE(rvq->vq_ring.used->idx);
      priv->headrx = rvq->vq_ring.used->idx;
    }
  else
    {
      RPMSG_VIRTIO_INVALIDATE(rvq->vq_ring.avail->idx);
      priv->headrx = rvq->vq_ring.avail->idx;
    }
}

static FAR struct rpmsg_virtio_priv_s *
rpmsg_virtio_get_priv(FAR struct virtio_device *vdev)
{
  FAR struct rpmsg_virtio_device *rvdev = vdev->priv;

  return metal_container_of(rvdev, struct rpmsg_virtio_priv_s, rvdev);
}

static int rpmsg_virtio_create_virtqueues_(FAR struct virtio_device *vdev,
                                           unsigned int flags,
                                           unsigned int nvqs,
                                           FAR const char *names[],
                                           vq_callback callbacks[],
                                           FAR void *callback_args[])
{
  int ret;
  int i;

  if (nvqs > vdev->vrings_num)
    {
      return ERROR_VQUEUE_INVLD_PARAM;
    }

  /* Initialize virtqueue for each vring */

  for (i = 0; i < nvqs; i++)
    {
      FAR struct virtio_vring_info *vinfo = &vdev->vrings_info[i];
      FAR struct vring_alloc_info *valloc = &vinfo->info;
#ifndef CONFIG_OPENAMP_VIRTIO_DEVICE_ONLY
      if (vdev->role == VIRTIO_DEV_DRIVER)
        {
          size_t offset;

          offset = metal_io_virt_to_offset(vinfo->io, valloc->vaddr);
          metal_io_block_set(vinfo->io, offset, 0,
                             vring_size(valloc->num_descs, valloc->align));
        }
#endif

      ret = virtqueue_create(vdev, i, names[i], valloc,
                             callbacks[i], vdev->func->notify,
                             vinfo->vq);
      if (ret < 0)
        {
          return ret;
        }
    }

  return 0;
}

static uint8_t rpmsg_virtio_get_status_(FAR struct virtio_device *vdev)
{
  FAR struct rpmsg_virtio_priv_s *priv = rpmsg_virtio_get_priv(vdev);

  return priv->rsc->rpmsg_vdev.status;
}

static void rpmsg_virtio_set_status_(FAR struct virtio_device *vdev,
                                     uint8_t status)
{
  FAR struct rpmsg_virtio_priv_s *priv = rpmsg_virtio_get_priv(vdev);

  priv->rsc->rpmsg_vdev.status = status;
}

static uint64_t rpmsg_virtio_get_features_(FAR struct virtio_device *vdev)
{
  FAR struct rpmsg_virtio_priv_s *priv = rpmsg_virtio_get_priv(vdev);

  return priv->rsc->rpmsg_vdev.dfeatures;
}

static void rpmsg_virtio_set_features(FAR struct virtio_device *vdev,
                                      uint64_t features)
{
  FAR struct rpmsg_virtio_priv_s *priv = rpmsg_virtio_get_priv(vdev);

  priv->rsc->rpmsg_vdev.gfeatures = features;
}

static void rpmsg_virtio_notify(FAR struct virtqueue *vq)
{
  FAR struct virtio_device *vdev = vq->vq_dev;
  FAR struct rpmsg_virtio_priv_s *priv = rpmsg_virtio_get_priv(vdev);
  FAR struct virtqueue *svq = priv->rvdev.svq;

  if (vdev->vrings_info->notifyid ==
      vdev->vrings_info[svq->vq_queue_index].notifyid)
    {
      rpmsg_virtio_pm_action(priv, true);
    }

  if (priv->rvdev.svq == vq)
    {
      rpmsg_virtio_pm_action(priv, true);
    }

  RPMSG_VIRTIO_NOTIFY(priv->dev,
                      vdev->vrings_info[vq->vq_queue_index].notifyid);
}

static bool rpmsg_virtio_is_recursive(FAR struct rpmsg_virtio_priv_s *priv)
{
  return nxsched_gettid() == priv->tid;
}

static int rpmsg_virtio_wait(FAR struct rpmsg_s *rpmsg, FAR sem_t *sem)
{
  FAR struct rpmsg_virtio_priv_s *priv =
      (FAR struct rpmsg_virtio_priv_s *)rpmsg;
  int ret;

  if (!rpmsg_virtio_is_recursive(priv))
    {
      return nxsem_wait_uninterruptible(sem);
    }

  while (1)
    {
      ret = nxsem_trywait(sem);
      if (ret >= 0)
        {
          break;
        }

      nxsem_wait(&priv->semtx);
      virtqueue_notification(priv->rvdev.rvq);
    }

  return ret;
}

static int rpmsg_virtio_post(FAR struct rpmsg_s *rpmsg, FAR sem_t *sem)
{
  FAR struct rpmsg_virtio_priv_s *priv =
      (FAR struct rpmsg_virtio_priv_s *)rpmsg;
  int semcount;
  int ret;

  nxsem_get_value(sem, &semcount);
  ret = nxsem_post(sem);

  if (priv && semcount >= 0)
    {
      rpmsg_virtio_wakeup_tx(priv);
    }

  return ret;
}

static void rpmsg_virtio_panic(FAR struct rpmsg_s *rpmsg)
{
  FAR struct rpmsg_virtio_priv_s *priv =
      (FAR struct rpmsg_virtio_priv_s *)rpmsg;
  FAR struct rpmsg_virtio_cmd_s *cmd = RPMSG_VIRTIO_RSC2CMD(priv->rsc);

  if (RPMSG_VIRTIO_IS_MASTER(priv->dev))
    {
      cmd->cmd_master = RPMSG_VIRTIO_CMD(RPMSG_VIRTIO_CMD_PANIC, 0);
    }
  else
    {
      cmd->cmd_slave = RPMSG_VIRTIO_CMD(RPMSG_VIRTIO_CMD_PANIC, 0);
    }

  rpmsg_virtio_notify(priv->vdev.vrings_info->vq);
}

#ifdef CONFIG_OPENAMP_DEBUG
static void rpmsg_virtio_dump_buffer(FAR struct rpmsg_virtio_device *rvdev,
                                     bool rx)
{
  FAR struct virtqueue *vq = rx ? rvdev->rvq : rvdev->svq;
  int num;
  int i;

  num = rpmsg_virtio_buffer_nused(rvdev, rx);
  metal_log(METAL_LOG_EMERGENCY,
            "    %s buffer, total %d, pending %d\n",
            rx ? "RX" : "TX", vq->vq_nentries, num);

  for (i = 0; i < num; i++)
    {
      FAR void *addr;
      int desc_idx;

      if ((rpmsg_virtio_get_role(rvdev) == RPMSG_HOST) ^ rx)
        {
          desc_idx = (vq->vq_ring.used->idx + i) & (vq->vq_nentries - 1);
          desc_idx = vq->vq_ring.avail->ring[desc_idx];
        }
      else
        {
          desc_idx = (vq->vq_ring.avail->idx + i) & (vq->vq_nentries - 1);
          desc_idx = vq->vq_ring.used->ring[desc_idx].id;
        }

      addr = metal_io_phys_to_virt(vq->shm_io,
                                   vq->vq_ring.desc[desc_idx].addr);
      if (addr)
        {
          FAR struct rpmsg_hdr *hdr = addr;
          FAR struct rpmsg_endpoint *ept;

          ept = rpmsg_get_ept_from_addr(&rvdev->rdev,
                                        rx ? hdr->dst : hdr->src);
          if (ept)
            {
              metal_log(METAL_LOG_EMERGENCY,
                        "      %s buffer %p hold by %s\n",
                        rx ? "RX" : "TX", hdr, ept->name);
            }
        }
    }
}

static void rpmsg_virtio_dump(FAR struct rpmsg_s *rpmsg)
{
  FAR struct rpmsg_virtio_priv_s *priv =
      (FAR struct rpmsg_virtio_priv_s *)rpmsg;
  FAR struct rpmsg_virtio_device *rvdev = &priv->rvdev;
  FAR struct rpmsg_device *rdev = rpmsg->rdev;
  FAR struct rpmsg_endpoint *ept;
  FAR struct metal_list *node;
  bool needlock = true;

  metal_log(METAL_LOG_EMERGENCY, "Remote: %s headrx %d\n",
            RPMSG_VIRTIO_GET_CPUNAME(priv->dev), priv->headrx);

  if (!rvdev->vdev)
    {
      return;
    }

  if (up_interrupt_context() || sched_idletask())
    {
      needlock = false;
    }

  if (needlock)
    {
      metal_mutex_acquire(&rdev->lock);
    }

  metal_log(METAL_LOG_EMERGENCY,
            "Dump rpmsg info between cpu (master: %s)%s <==> %s:\n",
            rpmsg_virtio_get_role(rvdev) == RPMSG_HOST ? "yes" : "no",
            CONFIG_RPMSG_LOCAL_CPUNAME, rpmsg_get_cpuname(rdev));

  metal_log(METAL_LOG_EMERGENCY, "rpmsg vq RX:\n");
  virtqueue_dump(rvdev->rvq);
  metal_log(METAL_LOG_EMERGENCY, "rpmsg vq TX:\n");
  virtqueue_dump(rvdev->svq);

  metal_log(METAL_LOG_EMERGENCY, "  rpmsg ept list:\n");

  metal_list_for_each(&rdev->endpoints, node)
    {
      ept = metal_container_of(node, struct rpmsg_endpoint, node);
      metal_log(METAL_LOG_EMERGENCY, "    ept %s\n", ept->name);
    }

  metal_log(METAL_LOG_EMERGENCY, "  rpmsg buffer list:\n");

  rpmsg_virtio_dump_buffer(rvdev, true);
  rpmsg_virtio_dump_buffer(rvdev, false);

  if (needlock)
    {
      metal_mutex_release(&rdev->lock);
    }
}
#else
static void rpmsg_virtio_dump(FAR struct rpmsg_s *rpmsg)
{
  /* Nothing */
}
#endif

static FAR const char *
rpmsg_virtio_get_local_cpuname(FAR struct rpmsg_s *rpmsg)
{
  FAR struct rpmsg_virtio_priv_s *priv =
    (FAR struct rpmsg_virtio_priv_s *)rpmsg;

  return RPMSG_VIRTIO_GET_LOCAL_CPUNAME(priv->dev);
}

static FAR const char *rpmsg_virtio_get_cpuname(FAR struct rpmsg_s *rpmsg)
{
  FAR struct rpmsg_virtio_priv_s *priv =
      (FAR struct rpmsg_virtio_priv_s *)rpmsg;

  return RPMSG_VIRTIO_GET_CPUNAME(priv->dev);
}

static void rpmsg_virtio_wakeup_rx(FAR struct rpmsg_virtio_priv_s *priv)
{
  int semcount;

  nxsem_get_value(&priv->semrx, &semcount);
  if (semcount < 1)
    {
      nxsem_post(&priv->semrx);
    }
}

static void rpmsg_virtio_command(FAR struct rpmsg_virtio_priv_s *priv)
{
  FAR struct rpmsg_virtio_cmd_s *rpmsg_virtio_cmd =
    RPMSG_VIRTIO_RSC2CMD(priv->rsc);
  uint32_t cmd;

  if (RPMSG_VIRTIO_IS_MASTER(priv->dev))
    {
      cmd = rpmsg_virtio_cmd->cmd_slave;
      rpmsg_virtio_cmd->cmd_slave = 0;
    }
  else
    {
      cmd = rpmsg_virtio_cmd->cmd_master;
      rpmsg_virtio_cmd->cmd_master = 0;
    }

  switch (RPMSG_VIRTIO_GET_CMD(cmd))
    {
      case RPMSG_VIRTIO_CMD_PANIC:
        PANIC();
        break;

      default:
        break;
    }
}

static int rpmsg_virtio_callback(FAR void *arg, uint32_t vqid)
{
  FAR struct rpmsg_virtio_priv_s *priv = arg;
  FAR struct rpmsg_virtio_device *rvdev = &priv->rvdev;
  FAR struct virtio_device *vdev = rvdev->vdev;
  FAR struct virtqueue *rvq = rvdev->rvq;
  FAR struct virtqueue *svq = rvdev->svq;

  rpmsg_virtio_command(priv);

  if (vqid == RPMSG_VIRTIO_NOTIFY_ALL ||
      vqid == vdev->vrings_info[rvq->vq_queue_index].notifyid)
    {
      rpmsg_virtio_update_rx(priv);
      rpmsg_virtio_wakeup_rx(priv);
    }

  if (vqid == RPMSG_VIRTIO_NOTIFY_ALL ||
      vqid == vdev->vrings_info[svq->vq_queue_index].notifyid)
    {
      rpmsg_virtio_wakeup_tx(priv);
      rpmsg_virtio_pm_action(priv, false);
    }

  return OK;
}

static int rpmsg_virtio_notify_wait(FAR struct rpmsg_device *rdev,
                                    uint32_t id)
{
  FAR struct rpmsg_virtio_priv_s *priv =
    metal_container_of(rdev, struct rpmsg_virtio_priv_s, rvdev.rdev);

  if (!rpmsg_virtio_is_recursive(priv))
    {
      return -EAGAIN;
    }

  /* Wait to wakeup */

  nxsem_tickwait(&priv->semtx, MSEC2TICK(RPMSG_VIRTIO_TIMEOUT_MS));
  virtqueue_notification(priv->rvdev.rvq);

  return 0;
}

static int rpmsg_virtio_start(FAR struct rpmsg_virtio_priv_s *priv)
{
  FAR struct virtio_vring_info *rvrings = priv->rvrings;
  FAR struct virtio_device *vdev = &priv->vdev;
  FAR struct rpmsg_virtio_rsc_s *rsc;
  struct rpmsg_virtio_config config;
  FAR void *shbuf0;
  FAR void *shbuf1;
  uint32_t align0;
  uint32_t align1;
  uint32_t tbsz;
  uint32_t v0sz;
  uint32_t v1sz;
  uint32_t shbufsz0;
  uint32_t shbufsz1;
  int ret;

  rsc = RPMSG_VIRTIO_GET_RESOURCE(priv->dev);
  if (!rsc)
    {
      return -EINVAL;
    }

  priv->rsc = rsc;

  vdev->notifyid = RPMSG_VIRTIO_VDEV_NOTIFYID;
  vdev->vrings_num = rsc->rpmsg_vdev.num_of_vrings;
  vdev->role = RPMSG_VIRTIO_IS_MASTER(priv->dev) ? RPMSG_HOST : RPMSG_REMOTE;
  vdev->func = &g_rpmsg_virtio_dispatch;

  align0 = rsc->rpmsg_vring0.align;
  align1 = rsc->rpmsg_vring1.align;
  tbsz = ALIGN_UP(sizeof(struct rpmsg_virtio_rsc_s), MAX(align0, align1));
  v0sz = ALIGN_UP(vring_size(rsc->rpmsg_vring0.num, align0), align0);
  v1sz = ALIGN_UP(vring_size(rsc->rpmsg_vring1.num, align1), align1);

  shbuf0   = (FAR char *)rsc + tbsz + v0sz + v1sz;
  shbufsz0 = rsc->config.r2h_buf_size * rsc->rpmsg_vring0.num;
  shbuf1   = shbuf0 + shbufsz0;
  shbufsz1 = rsc->config.h2r_buf_size * rsc->rpmsg_vring1.num;

  rvrings[0].io = metal_io_get_region();
  rvrings[0].info.vaddr = (FAR char *)rsc + tbsz;
  rvrings[0].info.num_descs = rsc->rpmsg_vring0.num;
  rvrings[0].info.align = rsc->rpmsg_vring0.align;
  rvrings[0].notifyid = RPMSG_VIRTIO_VRING0_NOTIFYID;
  rvrings[0].vq = virtqueue_allocate(rsc->rpmsg_vring0.num);
  if (rvrings[0].vq == NULL)
    {
      return -ENOMEM;
    }

  rvrings[1].io = metal_io_get_region();
  rvrings[1].info.vaddr = (FAR char *)rsc + tbsz + v0sz;
  rvrings[1].info.num_descs = rsc->rpmsg_vring1.num;
  rvrings[1].info.align = rsc->rpmsg_vring1.align;
  rvrings[1].notifyid = RPMSG_VIRTIO_VRING1_NOTIFYID;
  rvrings[1].vq = virtqueue_allocate(rsc->rpmsg_vring1.num);
  if (rvrings[1].vq == NULL)
    {
      ret = -ENOMEM;
      goto err_vq0;
    }

  vdev->vrings_info = &rvrings[0];

  rpmsg_virtio_init_shm_pool(&priv->pool[0], shbuf0, shbufsz0);
  rpmsg_virtio_init_shm_pool(&priv->pool[1], shbuf1, shbufsz1);

  config.h2r_buf_size = rsc->config.h2r_buf_size;
  config.r2h_buf_size = rsc->config.r2h_buf_size;
  config.split_shpool = true;

  ret = rpmsg_init_vdev_with_config(&priv->rvdev, vdev, rpmsg_ns_bind,
                                    metal_io_get_region(),
                                    priv->pool, &config);
  if (ret != 0)
    {
      rpmsgerr("rpmsg_init_vdev failed %d\n", ret);
      ret = -ENOMEM;
      goto err_vq1;
    }

  priv->rvdev.rdev.ns_unbind_cb = rpmsg_ns_unbind;
  priv->rvdev.notify_wait_cb = rpmsg_virtio_notify_wait;

  RPMSG_VIRTIO_REGISTER_CALLBACK(priv->dev, rpmsg_virtio_callback, priv);

  rpmsg_virtio_update_rx(priv);
  rpmsg_virtio_wakeup_rx(priv);

  /* Broadcast device_created to all registers */

  rpmsg_device_created(&priv->rpmsg);

  return 0;

err_vq1:
  virtqueue_free(rvrings[1].vq);
err_vq0:
  virtqueue_free(rvrings[0].vq);
  return ret;
}

static int rpmsg_virtio_thread(int argc, FAR char *argv[])
{
  FAR struct rpmsg_virtio_priv_s *priv = (FAR struct rpmsg_virtio_priv_s *)
    ((uintptr_t)strtoul(argv[2], NULL, 16));
  int ret;

  priv->tid = nxsched_gettid();

  ret = rpmsg_virtio_start(priv);
  if (ret < 0)
    {
      rpmsgerr("rpmsg virtio thread start failed %d\n", ret);
      return ret;
    }

  while (1)
    {
      nxsem_wait_uninterruptible(&priv->semrx);
      if (rpmsg_virtio_available_rx(priv))
        {
          virtqueue_notification(priv->rvdev.rvq);
        }
    }

  return 0;
}

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

int rpmsg_virtio_initialize(FAR struct rpmsg_virtio_s *dev)
{
  FAR struct rpmsg_virtio_priv_s *priv;
  FAR char *argv[3];
  char arg1[32];
  char name[32];
  int ret;

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

  priv->dev = dev;
  nxsem_init(&priv->semrx, 0, 0);
  nxsem_init(&priv->semtx, 0, 0);

  snprintf(name, sizeof(name), "/dev/rpmsg/%s",
           RPMSG_VIRTIO_GET_CPUNAME(dev));
  ret = rpmsg_register(name, &priv->rpmsg, &g_rpmsg_virtio_ops);
  if (ret < 0)
    {
      goto err_driver;
    }

  snprintf(arg1, sizeof(arg1), "%p", priv);
  argv[0] = (FAR char *)RPMSG_VIRTIO_GET_CPUNAME(dev);
  argv[1] = arg1;
  argv[2] = NULL;

  ret = kthread_create("rpmsg_virtio", CONFIG_RPMSG_VIRTIO_PRIORITY,
                       CONFIG_RPMSG_VIRTIO_STACKSIZE,
                       rpmsg_virtio_thread, argv);
  if (ret < 0)
    {
      goto err_thread;
    }

#ifdef CONFIG_RPMSG_VIRTIO_PM
  snprintf(name, sizeof(name), "rpmsg-%s", RPMSG_VIRTIO_GET_CPUNAME(dev));
  pm_wakelock_init(&priv->wakelock, name, PM_IDLE_DOMAIN, PM_IDLE);
#endif

  return OK;

err_thread:
  rpmsg_unregister(name, &priv->rpmsg);

err_driver:
  nxsem_destroy(&priv->semrx);
  nxsem_destroy(&priv->semtx);
  kmm_free(priv);

  return ret;
}