/****************************************************************************
 * drivers/misc/goldfish_pipe.c
 *
 * 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.
 *
 ****************************************************************************/

/* Usage from the guest is simple:
 *
 *    int fd = open("/dev/goldfish_pipe", O_RDWR);
 *    .... write() or read() through the pipe.
 *
 * Connect to a specific qemu service:
 *
 *    // Do this immediately after opening the fd
 *    const char* msg = "<pipename>";
 *    if (write(fd, msg, strlen(msg) + 1) < 0) {
 *       ... could not connect to <pipename> service
 *       close(fd);
 *    }
 *
 * After this, simply read() and write() to communicate with the service.
 */

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

#include <errno.h>
#include <fcntl.h>
#include <poll.h>

#include <nuttx/fs/fs.h>
#include <nuttx/irq.h>
#include <nuttx/kmalloc.h>
#include <nuttx/mutex.h>
#include <nuttx/semaphore.h>
#include <nuttx/spinlock.h>

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

/* List of bitflags returned in status of GOLDFISH_PIPE_CMD_POLL command */

#define GOLDFISH_PIPE_POLL_IN                 (1 << 0)
#define GOLDFISH_PIPE_POLL_OUT                (1 << 1)
#define GOLDFISH_PIPE_POLL_HUP                (1 << 2)

/* Possible status values used to signal errors */

#define GOLDFISH_PIPE_ERROR_INVAL             -1
#define GOLDFISH_PIPE_ERROR_AGAIN             -2
#define GOLDFISH_PIPE_ERROR_NOMEM             -3
#define GOLDFISH_PIPE_ERROR_IO                -4

/* Bit-flags used to signal events from the emulator */

#define GOLDFISH_PIPE_WAKE_CLOSED             (1 << 0) /* Emulator closed pipe */
#define GOLDFISH_PIPE_WAKE_READ               (1 << 1) /* Pipe can now be read from */
#define GOLDFISH_PIPE_WAKE_WRITE              (1 << 2) /* Pipe can now be written to */
#define GOLDFISH_PIPE_WAKE_UNLOCK_DMA         (1 << 3) /* Unlock this pipe's DMA buffer */
#define GOLDFISH_PIPE_WAKE_UNLOCK_DMA_SHARED  (1 << 4) /* Unlock DMA buffer of the pipe shared to this pipe */

/* Possible pipe closing reasons */

#define GOLDFISH_PIPE_CLOSE_GRACEFUL          0 /* Guest sent a close command */
#define GOLDFISH_PIPE_CLOSE_REBOOT            1 /* Guest rebooted, we're closing the pipes */
#define GOLDFISH_PIPE_CLOSE_LOAD_SNAPSHOT     2 /* Close old pipes on snapshot load */
#define GOLDFISH_PIPE_CLOSE_ERROR             3 /* Some unrecoverable error on the pipe */

/* Register offset */

#define GOLDFISH_PIPE_REG_CMD                 0
#define GOLDFISH_PIPE_REG_SIGNAL_BUFFER_HIGH  4
#define GOLDFISH_PIPE_REG_SIGNAL_BUFFER       8
#define GOLDFISH_PIPE_REG_SIGNAL_BUFFER_COUNT 12
#define GOLDFISH_PIPE_REG_OPEN_BUFFER_HIGH    20
#define GOLDFISH_PIPE_REG_OPEN_BUFFER         24
#define GOLDFISH_PIPE_REG_VERSION             36
#define GOLDFISH_PIPE_REG_GET_SIGNALLED       48

/* Possible pipe command */

#define GOLDFISH_PIPE_CMD_OPEN                1
#define GOLDFISH_PIPE_CMD_CLOSE               2
#define GOLDFISH_PIPE_CMD_POLL                3
#define GOLDFISH_PIPE_CMD_WRITE               4
#define GOLDFISH_PIPE_CMD_WAKE_ON_WRITE       5
#define GOLDFISH_PIPE_CMD_READ                6
#define GOLDFISH_PIPE_CMD_WAKE_ON_READ        7
#define GOLDFISH_PIPE_CMD_WAKE_ON_DONE_IO     8

/* Version number */

#define GOLDFISH_PIPE_DRIVER_VERSION          4
#define GOLDFISH_PIPE_CURRENT_DEVICE_VERSION  2

#define GOLDFISH_PIPE_MAX_POLL_WAITERS        2
#define GOLDFISH_PIPE_MAX_COMMAND_BUFFERS     1
#define GOLDFISH_PIPE_MAX_SIGNALLED           16
#define GOLDFISH_PIPE_MAX_PIPES               32

#define goldfish_pipe_putreg32(v, x)          (*(FAR volatile uint32_t *)(x) = (v))
#define goldfish_pipe_getreg32(x)             (*(FAR volatile uint32_t *)(x))

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

/* A per-pipe command structure, shared with the host */

struct goldfish_pipe_command_s
{
  int32_t cmd;      /* PipeCmdCode, guest -> host */
  int32_t id;       /* Pipe id, guest -> host */
  int32_t status;   /* Command execution status, host -> guest */
  int32_t reserved; /* To pad to 64-bit boundary */

  struct /* Parameters for GOLDFISH_PIPE_CMD_[READ|WRITE] */
    {
      uint32_t buffers_count; /* Number of buffers, guest -> host */
      int32_t consumed_size;  /* Number of consumed bytes, host -> guest */

      uint64_t ptrs[GOLDFISH_PIPE_MAX_COMMAND_BUFFERS];  /* Buffer pointers, guest -> host */
      uint32_t sizes[GOLDFISH_PIPE_MAX_COMMAND_BUFFERS]; /* Buffer sizes, guest -> host */
    }
  rw_params;
};

/* A single signalled pipe information */

struct goldfish_pipe_signalled_s
{
  uint32_t id;
  uint32_t flags;
};

/* Parameters for the GOLDFISH_PIPE_CMD_OPEN command */

struct goldfish_pipe_open_param_s
{
  uint64_t command_buffer_ptr;
  uint32_t rw_params_max_count;
};

/* This data type models a given pipe instance */

struct goldfish_pipe_dev_s;

struct goldfish_pipe_s
{
  uint32_t id;
  bool closed;
  struct goldfish_pipe_command_s command;
  mutex_t lock;
  sem_t wait_for_write;
  sem_t wait_for_read;
  FAR struct goldfish_pipe_dev_s *dev;
  FAR struct pollfd *fds[GOLDFISH_PIPE_MAX_POLL_WAITERS];
};

struct goldfish_pipe_dev_s
{
  spinlock_t lock;
  FAR struct goldfish_pipe_s *pipes[GOLDFISH_PIPE_MAX_PIPES];
  struct goldfish_pipe_open_param_s open_params;
  struct goldfish_pipe_signalled_s
    pipes_signalled[GOLDFISH_PIPE_MAX_SIGNALLED];
  FAR uint8_t *base;
};

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

static ssize_t goldfish_pipe_read(FAR struct file *filp, FAR char *buffer,
                                  size_t bufflen);
static ssize_t goldfish_pipe_write(FAR struct file *filp,
                                   FAR const char *buffer, size_t bufflen);
static int goldfish_pipe_poll(FAR struct file *filp,
                              FAR struct pollfd *fds, bool setup);
static int goldfish_pipe_open(FAR struct file *filep);
static int goldfish_pipe_close(FAR struct file *filep);

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

static const struct file_operations g_goldfish_pipe_fops =
{
  .read  = goldfish_pipe_read,
  .write = goldfish_pipe_write,
  .poll  = goldfish_pipe_poll,
  .open  = goldfish_pipe_open,
  .close = goldfish_pipe_close,
};

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

static int goldfish_pipe_convert_status(int status)
{
  switch (status)
    {
      case GOLDFISH_PIPE_ERROR_INVAL:
        return -EINVAL;
      case GOLDFISH_PIPE_ERROR_AGAIN:
        return -EAGAIN;
      case GOLDFISH_PIPE_ERROR_NOMEM:
        return -ENOMEM;
      case GOLDFISH_PIPE_ERROR_IO:
        return -EIO;
      default:
        return status;
    }
}

static int goldfish_pipe_command_locked(FAR struct goldfish_pipe_s *pipe,
                                        int cmd)
{
  pipe->command.cmd = cmd;
  pipe->command.status = GOLDFISH_PIPE_ERROR_INVAL;
  goldfish_pipe_putreg32(pipe->id, pipe->dev->base + GOLDFISH_PIPE_REG_CMD);
  return goldfish_pipe_convert_status(pipe->command.status);
}

static int goldfish_pipe_command(FAR struct goldfish_pipe_s *pipe, int cmd)
{
  int ret;

  ret = nxmutex_lock(&pipe->lock);
  if (ret < 0)
    {
      return ret;
    }

  ret = goldfish_pipe_command_locked(pipe, cmd);
  nxmutex_unlock(&pipe->lock);

  return ret;
}

static int goldfish_pipe_wait(FAR struct goldfish_pipe_s *pipe,
                              bool is_write)
{
  int ret;

  ret = goldfish_pipe_command(pipe, is_write ?
                              GOLDFISH_PIPE_CMD_WAKE_ON_WRITE :
                              GOLDFISH_PIPE_CMD_WAKE_ON_READ);
  if (ret < 0)
    {
      return ret;
    }

  return nxsem_wait(is_write ? &pipe->wait_for_write : &pipe->wait_for_read);
}

static ssize_t goldfish_pipe_transfer_one(FAR struct goldfish_pipe_s *pipe,
                                          FAR char *buffer, size_t buflen,
                                          bool is_write)
{
  ssize_t ret;

  ret = nxmutex_lock(&pipe->lock);
  if (ret < 0)
    {
      return ret;
    }

  pipe->command.rw_params.ptrs[0] = up_addrenv_va_to_pa(buffer);
  pipe->command.rw_params.sizes[0] = buflen;
  pipe->command.rw_params.buffers_count = 1;

  ret = goldfish_pipe_command_locked(pipe,
          is_write ? GOLDFISH_PIPE_CMD_WRITE : GOLDFISH_PIPE_CMD_READ);
  if (ret >= 0)
    {
      ret = pipe->command.rw_params.consumed_size;
    }

  nxmutex_unlock(&pipe->lock);
  return ret;
}

static ssize_t goldfish_pipe_transfer(FAR struct file *filp,
                                      FAR char *buffer, size_t bufflen,
                                      bool is_write)
{
  FAR struct goldfish_pipe_s *pipe = filp->f_priv;
  size_t count = 0;
  int ret = 0;

  while (bufflen > 0)
    {
      /* If the emulator already closed the pipe, no need to go further */

      if (pipe->closed)
        {
          ret = -EIO;
          break;
        }

      ret = goldfish_pipe_transfer_one(pipe, buffer, bufflen, is_write);
      if (ret > 0)
        {
          buffer += ret;
          bufflen -= ret;
          count += ret;
          continue;
        }

      if (ret != -EAGAIN || count || (filp->f_oflags & O_NONBLOCK))
        {
          break;
        }

      ret = goldfish_pipe_wait(pipe, is_write);
      if (ret < 0)
        {
          break;
        }
    }

  return count > 0 ? count : ret;
}

static ssize_t goldfish_pipe_read(FAR struct file *filp, FAR char *buffer,
                                  size_t bufflen)
{
  return goldfish_pipe_transfer(filp, buffer, bufflen, false);
}

static ssize_t goldfish_pipe_write(FAR struct file *filp,
                                   FAR const char *buffer, size_t bufflen)
{
  return goldfish_pipe_transfer(filp, (FAR char *)buffer, bufflen, true);
}

static int goldfish_pipe_poll(FAR struct file *filp,
                              FAR struct pollfd *fds, bool setup)
{
  if (setup)
    {
      FAR struct goldfish_pipe_s *pipe = filp->f_priv;
      FAR struct goldfish_pipe_dev_s *dev = pipe->dev;
      pollevent_t eventset = 0;
      irqstate_t flags;
      int ret;
      int i;

      ret = goldfish_pipe_command(pipe, GOLDFISH_PIPE_CMD_POLL);
      if (ret < 0)
        {
          return ret;
        }

      flags = spin_lock_irqsave(&dev->lock);

      /* This is a request to set up the poll.  Find an available
       * slot for the poll structure reference
       */

      for (i = 0; i < GOLDFISH_PIPE_MAX_POLL_WAITERS; i++)
        {
          /* Find an available slot */

          if (pipe->fds[i] == NULL)
            {
              /* Bind the poll structure and this slot */

              pipe->fds[i] = fds;
              fds->priv = &pipe->fds[i];
              break;
            }
        }

      spin_unlock_irqrestore(&dev->lock, flags);
      if (i >= GOLDFISH_PIPE_MAX_POLL_WAITERS)
        {
          return -EBUSY;
        }

      if (ret & GOLDFISH_PIPE_POLL_IN)
        {
          eventset |= POLLIN;
        }

      if (ret & GOLDFISH_PIPE_POLL_OUT)
        {
          eventset |= POLLOUT;
        }

      if (ret & GOLDFISH_PIPE_POLL_HUP)
        {
          eventset |= POLLHUP;
        }

      if (pipe->closed)
        {
          eventset |= POLLERR;
        }

      if (eventset)
        {
          poll_notify(&fds, 1, eventset);
        }
      else
        {
          goldfish_pipe_command(pipe, GOLDFISH_PIPE_CMD_WAKE_ON_READ);
        }
    }
  else
    {
      /* This is a request to tear down the poll. */

      FAR struct pollfd **slot = (FAR struct pollfd **)fds->priv;

      if (slot == NULL)
        {
          return -EINVAL;
        }

      *slot = NULL;
      fds->priv = NULL;
    }

  return 0;
}

static int goldfish_pipe_open(FAR struct file *filep)
{
  FAR struct goldfish_pipe_dev_s *dev = filep->f_inode->i_private;
  FAR struct goldfish_pipe_s *pipe;
  irqstate_t flags;
  int ret = -ENFILE;
  int id;

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

  pipe->dev = dev;
  nxmutex_init(&pipe->lock);
  nxsem_init(&pipe->wait_for_read, 0, 0);
  nxsem_init(&pipe->wait_for_write, 0, 0);

  flags = spin_lock_irqsave(&dev->lock);

  for (id = 0; id < GOLDFISH_PIPE_MAX_PIPES; id++)
    {
      if (dev->pipes[id] == NULL)
        {
          break;
        }
    }

  if (id >= GOLDFISH_PIPE_MAX_PIPES)
    {
      goto out;
    }

  pipe->id = id;
  pipe->command.id = id;

  /* Now tell the emulator we're opening a new pipe. */

  dev->open_params.command_buffer_ptr = up_addrenv_va_to_pa(&pipe->command);
  dev->open_params.rw_params_max_count = GOLDFISH_PIPE_MAX_COMMAND_BUFFERS;

  ret = goldfish_pipe_command_locked(pipe, GOLDFISH_PIPE_CMD_OPEN);
  if (ret < 0)
    {
      goto out;
    }

  dev->pipes[id] = pipe;
  spin_unlock_irqrestore(&dev->lock, flags);

  filep->f_priv = pipe;
  return 0;

out:
  spin_unlock_irqrestore(&dev->lock, flags);
  nxsem_destroy(&pipe->wait_for_write);
  nxsem_destroy(&pipe->wait_for_read);
  nxmutex_destroy(&pipe->lock);
  kmm_free(pipe);
  return ret;
}

static int goldfish_pipe_close(FAR struct file *filp)
{
  FAR struct goldfish_pipe_s *pipe = filp->f_priv;
  FAR struct goldfish_pipe_dev_s *dev = pipe->dev;
  irqstate_t flags;

  goldfish_pipe_command(pipe, GOLDFISH_PIPE_CMD_CLOSE);

  flags = spin_lock_irqsave(&dev->lock);
  dev->pipes[pipe->id] = NULL;
  spin_unlock_irqrestore(&dev->lock, flags);

  filp->f_priv = NULL;

  nxsem_destroy(&pipe->wait_for_write);
  nxsem_destroy(&pipe->wait_for_read);
  nxmutex_destroy(&pipe->lock);
  kmm_free(pipe);

  return 0;
}

static void goldfish_pipe_wake(FAR struct goldfish_pipe_s *pipe,
                               bool is_write)
{
  FAR sem_t *sem = is_write ? &pipe->wait_for_write : &pipe->wait_for_read;
  int sval;

  while (nxsem_get_value(sem, &sval) >= 0 && sval <= 0)
    {
      nxsem_post(sem);
    }
}

static int goldfish_pipe_interrupt(int irq, FAR void *context, FAR void *arg)
{
  FAR struct goldfish_pipe_dev_s *dev = arg;
  irqstate_t irqflags;
  uint32_t count;
  uint32_t i;

  irqflags = spin_lock_irqsave(&dev->lock);

  count = goldfish_pipe_getreg32(dev->base +
                                 GOLDFISH_PIPE_REG_GET_SIGNALLED);
  if (count > GOLDFISH_PIPE_MAX_SIGNALLED)
    {
      count = GOLDFISH_PIPE_MAX_SIGNALLED;
    }

  for (i = 0; i < count; i++)
    {
      uint32_t id = dev->pipes_signalled[i].id;
      uint32_t flags = dev->pipes_signalled[i].flags;
      FAR struct goldfish_pipe_s *pipe = dev->pipes[id];
      pollevent_t eventset = 0;

      if (pipe == NULL)
        {
          continue;
        }

      if (flags & GOLDFISH_PIPE_WAKE_READ)
        {
          eventset |= POLLIN;
        }

      if (flags & GOLDFISH_PIPE_WAKE_WRITE)
        {
          eventset |= POLLOUT;
        }

      if (flags & GOLDFISH_PIPE_WAKE_CLOSED)
        {
          eventset |= POLLERR;
          dev->pipes[id]->closed = true;
        }

      if (eventset & (POLLIN | POLLERR))
        {
          goldfish_pipe_wake(pipe, false);
        }

      if (eventset & (POLLOUT | POLLERR))
        {
          goldfish_pipe_wake(pipe, true);
        }

      if (eventset)
        {
          poll_notify(pipe->fds, GOLDFISH_PIPE_MAX_POLL_WAITERS, eventset);
        }
    }

  spin_unlock_irqrestore(&dev->lock, irqflags);
  return 0;
}

static void goldfish_pipe_write_addr(FAR void *addr,
                                     FAR void *portl, FAR void *porth)
{
  uintptr_t paddr = up_addrenv_va_to_pa(addr);
  goldfish_pipe_putreg32((paddr >> 16) >> 16, porth);
  goldfish_pipe_putreg32(paddr, portl);
}

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

/****************************************************************************
 * Name: goldfish_pipe_register
 *
 * Description:
 *   register /dev/goldfish_pipe device
 *
 ****************************************************************************/

int goldfish_pipe_register(FAR void *base, int irq)
{
  FAR struct goldfish_pipe_dev_s *dev;
  uint32_t version;
  int ret = -ENOTSUP;

  /* Allocate and initialize a new device structure instance */

  dev = (FAR struct goldfish_pipe_dev_s *)kmm_zalloc(sizeof(*dev));
  if (dev == NULL)
    {
      return -ENOMEM;
    }

  spin_lock_init(&dev->lock);
  dev->base = (FAR uint8_t *)base;

  /* Exchange the versions with the host device */

  goldfish_pipe_putreg32(GOLDFISH_PIPE_DRIVER_VERSION,
                         dev->base + GOLDFISH_PIPE_REG_VERSION);
  version = goldfish_pipe_getreg32(dev->base + GOLDFISH_PIPE_REG_VERSION);
  if (version < GOLDFISH_PIPE_CURRENT_DEVICE_VERSION)
    {
      goto out;
    }

  ret = irq_attach(irq, goldfish_pipe_interrupt, dev);
  if (ret < 0)
    {
      goto out;
    }

  up_enable_irq(irq);

  /* Send the buffer addresses to the host */

  goldfish_pipe_write_addr(&dev->pipes_signalled,
                           dev->base + GOLDFISH_PIPE_REG_SIGNAL_BUFFER,
                           dev->base + GOLDFISH_PIPE_REG_SIGNAL_BUFFER_HIGH);

  goldfish_pipe_putreg32(GOLDFISH_PIPE_MAX_SIGNALLED,
                         dev->base + GOLDFISH_PIPE_REG_SIGNAL_BUFFER_COUNT);

  goldfish_pipe_write_addr(&dev->open_params,
                           dev->base + GOLDFISH_PIPE_REG_OPEN_BUFFER,
                           dev->base + GOLDFISH_PIPE_REG_OPEN_BUFFER_HIGH);

  /* Register the pipe device */

  return register_driver("/dev/goldfish_pipe",
                         &g_goldfish_pipe_fops, 0666, dev);

out:
  kmm_free(dev);
  return ret;
}