/****************************************************************************
 * drivers/usrsock/usrsock_dev.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.
 *
 ****************************************************************************/

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

#include <nuttx/config.h>
#if defined(CONFIG_NET_USRSOCK_DEVICE)

#include <sys/types.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stdint.h>
#include <unistd.h>
#include <string.h>
#include <poll.h>
#include <errno.h>
#include <assert.h>
#include <debug.h>

#include <arch/irq.h>

#include <nuttx/random.h>
#include <nuttx/fs/fs.h>
#include <nuttx/mutex.h>
#include <nuttx/net/net.h>
#include <nuttx/net/usrsock.h>

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

#ifndef CONFIG_NET_USRSOCKDEV_NPOLLWAITERS
#  define CONFIG_NET_USRSOCKDEV_NPOLLWAITERS 1
#endif

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

struct usrsockdev_s
{
  mutex_t devlock; /* Lock for device node */
  uint8_t ocount;  /* The number of times the device has been opened */
  struct
  {
    FAR const struct iovec *iov;    /* Pending request buffers */
    int                     iovcnt; /* Number of request buffers */
    size_t                  pos;    /* Reader position on request buffer */
  } req;
  FAR struct pollfd *pollfds[CONFIG_NET_USRSOCKDEV_NPOLLWAITERS];
};

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

/* Character driver methods */

static ssize_t usrsockdev_read(FAR struct file *filep, FAR char *buffer,
                               size_t len);

static ssize_t usrsockdev_write(FAR struct file *filep,
                                FAR const char *buffer, size_t len);

static off_t usrsockdev_seek(FAR struct file *filep, off_t offset,
                             int whence);

static int usrsockdev_open(FAR struct file *filep);

static int usrsockdev_close(FAR struct file *filep);

static int usrsockdev_poll(FAR struct file *filep, FAR struct pollfd *fds,
                           bool setup);

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

static const struct file_operations g_usrsockdevops =
{
  usrsockdev_open,    /* open */
  usrsockdev_close,   /* close */
  usrsockdev_read,    /* read */
  usrsockdev_write,   /* write */
  usrsockdev_seek,    /* seek */
  NULL,               /* ioctl */
  NULL,               /* mmap */
  NULL,               /* truncate */
  usrsockdev_poll     /* poll */
};

static struct usrsockdev_s g_usrsockdev =
{
  NXMUTEX_INITIALIZER
};

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

/****************************************************************************
 * Name: usrsockdev_is_opened
 ****************************************************************************/

static bool usrsockdev_is_opened(FAR struct usrsockdev_s *dev)
{
  bool ret = true;

  if (dev->ocount == 0)
    {
      ret = false; /* No usrsock daemon running. */
    }

  return ret;
}

/****************************************************************************
 * Name: usrsockdev_read
 ****************************************************************************/

static ssize_t usrsockdev_read(FAR struct file *filep, FAR char *buffer,
                               size_t len)
{
  FAR struct inode *inode = filep->f_inode;
  FAR struct usrsockdev_s *dev;
  int ret;

  if (len == 0)
    {
      return 0;
    }

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

  dev = inode->i_private;

  DEBUGASSERT(dev);

  ret = nxmutex_lock(&dev->devlock);
  if (ret < 0)
    {
      return ret;
    }

  /* Is request available? */

  if (dev->req.iov)
    {
      ssize_t rlen;

      /* Copy request to user-space. */

      rlen = usrsock_iovec_get(buffer, len, dev->req.iov, dev->req.iovcnt,
                               dev->req.pos, NULL);
      if (rlen < 0)
        {
          /* Tried reading beyond buffer. */

          len = 0;
        }
      else
        {
          dev->req.pos += rlen;
          len = rlen;
        }
    }
  else
    {
      len = 0;
    }

  nxmutex_unlock(&dev->devlock);
  return len;
}

/****************************************************************************
 * Name: usrsockdev_seek
 ****************************************************************************/

static off_t usrsockdev_seek(FAR struct file *filep, off_t offset,
                             int whence)
{
  FAR struct inode *inode = filep->f_inode;
  FAR struct usrsockdev_s *dev;
  off_t pos;
  int ret;

  if (whence != SEEK_CUR && whence != SEEK_SET)
    {
      return -EINVAL;
    }

  dev = inode->i_private;

  DEBUGASSERT(dev);

  ret = nxmutex_lock(&dev->devlock);
  if (ret < 0)
    {
      return ret;
    }

  /* Is request available? */

  if (dev->req.iov)
    {
      ssize_t rlen;

      if (whence == SEEK_CUR)
        {
          pos = dev->req.pos + offset;
        }
      else
        {
          pos = offset;
        }

      /* Copy request to user-space. */

      rlen = usrsock_iovec_get(NULL, 0, dev->req.iov, dev->req.iovcnt,
                               pos, NULL);
      if (rlen < 0)
        {
          /* Tried seek beyond buffer. */

          pos = -EINVAL;
        }
      else
        {
          dev->req.pos = pos;
        }
    }
  else
    {
      pos = 0;
    }

  nxmutex_unlock(&dev->devlock);
  return pos;
}

/****************************************************************************
 * Name: usrsockdev_write
 ****************************************************************************/

static ssize_t usrsockdev_write(FAR struct file *filep,
                                FAR const char *buffer, size_t len)
{
  FAR struct inode *inode = filep->f_inode;
  FAR struct usrsockdev_s *dev;
  bool req_done = false;
  ssize_t ret = 0;

  if (len == 0)
    {
      return 0;
    }

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

  dev = inode->i_private;

  DEBUGASSERT(dev);

  ret = nxmutex_lock(&dev->devlock);
  if (ret < 0)
    {
      return ret;
    }

  ret = usrsock_response(buffer, len, &req_done);
  if (req_done && dev->req.iov)
    {
      dev->req.iov = NULL;
      dev->req.pos = 0;
      dev->req.iovcnt = 0;
    }

  nxmutex_unlock(&dev->devlock);
  return ret;
}

/****************************************************************************
 * Name: usrsockdev_open
 ****************************************************************************/

static int usrsockdev_open(FAR struct file *filep)
{
  FAR struct inode        *inode = filep->f_inode;
  FAR struct usrsockdev_s *dev;
  int ret;
  int tmp;

  dev = inode->i_private;

  DEBUGASSERT(dev);

  ret = nxmutex_lock(&dev->devlock);
  if (ret < 0)
    {
      return ret;
    }

  ninfo("opening /dev/usrsock\n");

  /* Increment the count of references to the device. */

  tmp = dev->ocount + 1;
  if (tmp > 1)
    {
      /* Only one reference is allowed. */

      nwarn("failed to open\n");

      ret = -EPERM;
    }
  else
    {
      dev->ocount = tmp;
      ret = OK;
    }

  nxmutex_unlock(&dev->devlock);
  return ret;
}

/****************************************************************************
 * Name: usrsockdev_close
 ****************************************************************************/

static int usrsockdev_close(FAR struct file *filep)
{
  FAR struct inode *inode = filep->f_inode;
  FAR struct usrsockdev_s *dev;
  int ret;

  dev = inode->i_private;

  DEBUGASSERT(dev);

  ret = nxmutex_lock(&dev->devlock);
  if (ret < 0)
    {
      return ret;
    }

  ninfo("closing /dev/usrsock\n");

  /* Decrement the references to the driver. */

  dev->ocount--;
  DEBUGASSERT(dev->ocount == 0);
  ret = OK;
  dev->req.iov = NULL;
  dev->req.iovcnt = 0;
  dev->req.pos = 0;

  nxmutex_unlock(&dev->devlock);
  usrsock_abort();

  return ret;
}

/****************************************************************************
 * Name: usrsockdev_poll
 ****************************************************************************/

static int usrsockdev_poll(FAR struct file *filep, FAR struct pollfd *fds,
                           bool setup)
{
  FAR struct inode *inode = filep->f_inode;
  FAR struct usrsockdev_s *dev;
  pollevent_t eventset;
  int ret;
  int i;

  dev = inode->i_private;

  DEBUGASSERT(dev);

  /* Some sanity checking */

  if (!dev || !fds)
    {
      return -ENODEV;
    }

  /* Are we setting up the poll?  Or tearing it down? */

  ret = nxmutex_lock(&dev->devlock);
  if (ret < 0)
    {
      return ret;
    }

  if (setup)
    {
      /* This is a request to set up the poll.  Find an available
       * slot for the poll structure reference
       */

      for (i = 0; i < nitems(dev->pollfds); i++)
        {
          /* Find an available slot */

          if (!dev->pollfds[i])
            {
              /* Bind the poll structure and this slot */

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

      if (i >= nitems(dev->pollfds))
        {
          fds->priv = NULL;
          ret = -EBUSY;
          goto errout;
        }

      /* Should immediately notify on any of the requested events? */

      eventset = 0;

      /* Notify the POLLIN event if pending request. */

      if (dev->req.iov != NULL &&
          !(usrsock_iovec_get(NULL, 0, dev->req.iov,
                              dev->req.iovcnt, dev->req.pos, NULL) < 0))
        {
          eventset |= POLLIN;
        }

      poll_notify(dev->pollfds, nitems(dev->pollfds), eventset);
    }
  else
    {
      /* This is a request to tear down the poll. */

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

      if (!slot)
        {
          ret = -EIO;
          goto errout;
        }

      /* Remove all memory of the poll setup */

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

errout:
  nxmutex_unlock(&dev->devlock);
  return ret;
}

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

/****************************************************************************
 * Name: usrsock_request
 ****************************************************************************/

int usrsock_request(FAR struct iovec *iov, unsigned int iovcnt)
{
  FAR struct usrsockdev_s *dev = &g_usrsockdev;
  int ret = 0;

  /* Set outstanding request for daemon to handle. */

  net_mutex_lock(&dev->devlock);

  if (usrsockdev_is_opened(dev))
    {
      DEBUGASSERT(dev->req.iov == NULL);
      dev->req.iov = iov;
      dev->req.pos = 0;
      dev->req.iovcnt = iovcnt;

      /* Notify daemon of new request. */

      poll_notify(dev->pollfds, nitems(dev->pollfds), POLLIN);
    }
  else
    {
      ninfo("daemon abruptly closed /dev/usrsock.\n");
      ret = -ENETDOWN;
    }

  nxmutex_unlock(&dev->devlock);
  return ret;
}

/****************************************************************************
 * Name: usrsock_register
 *
 * Description:
 *   Register /dev/usrsock
 *
 ****************************************************************************/

void usrsock_register(void)
{
  register_driver("/dev/usrsock", &g_usrsockdevops, 0666,
                  &g_usrsockdev);
}

#endif /* CONFIG_NET_USRSOCK_DEVICE */