/****************************************************************************
 * fs/vfs/fs_poll.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>

#include <poll.h>
#include <time.h>
#include <assert.h>
#include <errno.h>
#include <debug.h>

#include <nuttx/clock.h>
#include <nuttx/semaphore.h>
#include <nuttx/cancelpt.h>
#include <nuttx/fs/fs.h>
#include <nuttx/net/net.h>

#include <arch/irq.h>

#include "inode/inode.h"

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

/****************************************************************************
 * Name: poll_setup
 *
 * Description:
 *   Setup the poll operation for each descriptor in the list.
 *
 ****************************************************************************/

static inline int poll_setup(FAR struct pollfd *fds, nfds_t nfds,
                             FAR sem_t *sem)
{
  unsigned int i;
  unsigned int j;
  int ret = OK;

  /* Process each descriptor in the list */

  for (i = 0; i < nfds; i++)
    {
      /* Setup the poll descriptor
       *
       * REVISIT: In a multi-threaded environment, one use case might be to
       * share a single, array of struct pollfd in poll() calls on different
       * threads.  That use case is not supportable here because the non-
       * standard internal fields get reset here on each call to poll()
       * on each thread.
       */

      fds[i].arg     = sem;
      fds[i].cb      = poll_default_cb;
      fds[i].revents = 0;
      fds[i].priv    = NULL;

      /* Check for invalid descriptors. "If the value of fd is less than 0,
       * events shall be ignored, and revents shall be set to 0 in that entry
       * on return from poll()."
       *
       * NOTE:  There is a potential problem here.  If there is only one fd
       * and if it is negative, then poll will hang.  From my reading of the
       * spec, that appears to be the correct behavior.
       */

      if (fds[i].fd >= 0)
        {
          ret = poll_fdsetup(fds[i].fd, &fds[i], true);
        }

      if (ret < 0)
        {
          /* Setup failed for fds[i]. We now need to teardown previously
           * setup fds[0 .. (i - 1)] to release allocated resources and
           * to prevent memory corruption by access to freed/released 'fds'
           * and 'sem'.
           */

          for (j = 0; j < i; j++)
            {
              poll_fdsetup(fds[j].fd, &fds[j], false);
            }

          /* Indicate an error on the file descriptor */

          fds[i].revents |= POLLERR;
          return ret;
        }
    }

  return OK;
}

/****************************************************************************
 * Name: poll_teardown
 *
 * Description:
 *   Teardown the poll operation for each descriptor in the list and return
 *   the count of non-zero poll events.
 *
 ****************************************************************************/

static inline int poll_teardown(FAR struct pollfd *fds, nfds_t nfds,
                                FAR int *count, int ret)
{
  unsigned int i;
  int status = OK;

  /* Process each descriptor in the list */

  *count = 0;
  for (i = 0; i < nfds; i++)
    {
      if (fds[i].fd >= 0)
        {
          status = poll_fdsetup(fds[i].fd, &fds[i], false);
        }

      if (status < 0)
        {
          ret = status;
        }

      /* Check if any events were posted */

      if (fds[i].revents != 0)
        {
          (*count)++;
        }

      /* Un-initialize the poll structure */

      fds[i].arg = NULL;
      fds[i].cb  = NULL;
    }

  return ret;
}

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

/****************************************************************************
 * Name: poll_fdsetup
 *
 * Description:
 *   Configure (or unconfigure) one file/socket descriptor for the poll
 *   operation.  If fds and sem are non-null, then the poll is being setup.
 *   if fds and sem are NULL, then the poll is being torn down.
 *
 ****************************************************************************/

int poll_fdsetup(int fd, FAR struct pollfd *fds, bool setup)
{
  FAR struct file *filep;
  int ret;

  /* Get the file pointer corresponding to this file descriptor */

  ret = fs_getfilep(fd, &filep);
  if (ret < 0)
    {
      return ret;
    }

  DEBUGASSERT(filep != NULL);

  /* Let file_poll() do the rest */

  return file_poll(filep, fds, setup);
}

/****************************************************************************
 * Name: poll_default_cb
 *
 * Description:
 *   The default poll callback function, this function do the final step of
 *   poll notification.
 *
 * Input Parameters:
 *   fds - The fds
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

void poll_default_cb(FAR struct pollfd *fds)
{
  int semcount = 0;
  FAR sem_t *pollsem;

  if (fds->arg != NULL)
    {
      pollsem = (FAR sem_t *)fds->arg;
      nxsem_get_value(pollsem, &semcount);
      if (semcount < 1)
        {
          nxsem_post(pollsem);
        }
    }
}

/****************************************************************************
 * Name: poll_notify
 *
 * Description:
 *   Notify the poll, this function should be called by drivers to notify
 *   the caller the poll is ready.
 *
 * Input Parameters:
 *   afds     - The fds array
 *   nfds     - Number of fds array
 *   eventset - List of events to check for activity
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

void poll_notify(FAR struct pollfd **afds, int nfds, pollevent_t eventset)
{
  int i;
  FAR struct pollfd *fds;

  DEBUGASSERT(afds != NULL && nfds >= 1);

  for (i = 0; i < nfds && eventset; i++)
    {
      fds = afds[i];
      if (fds != NULL)
        {
          /* The error event must be set in fds->revents */

          fds->revents |= eventset & (fds->events | POLLERR | POLLHUP);
          if ((fds->revents & (POLLERR | POLLHUP)) != 0)
            {
              /* Error or Hung up, clear POLLOUT event */

              fds->revents &= ~POLLOUT;
            }

          if ((fds->revents != 0 || (fds->events & POLLALWAYS) != 0) &&
              fds->cb != NULL)
            {
              finfo("Report events: %08" PRIx32 "\n", fds->revents);
              fds->cb(fds);
            }
        }
    }
}

/****************************************************************************
 * Name: file_poll
 *
 * Description:
 *   Low-level poll operation based on struct file.  This is used both to (1)
 *   support detached file, and also (2) by poll_fdsetup() to perform all
 *   normal operations on file descriptors.
 *
 * Input Parameters:
 *   file     File structure instance
 *   fds   - The structure describing the events to be monitored, OR NULL if
 *           this is a request to stop monitoring events.
 *   setup - true: Setup up the poll; false: Teardown the poll
 *
 * Returned Value:
 *  0: Success; Negated errno on failure
 *
 ****************************************************************************/

int file_poll(FAR struct file *filep, FAR struct pollfd *fds, bool setup)
{
  FAR struct inode *inode;
  int ret = -ENOSYS;

  DEBUGASSERT(filep != NULL);
  inode = filep->f_inode;

  if (inode != NULL)
    {
      /* Is a driver registered? Does it support the poll method?
       * If not, return -ENOSYS
       */

      if ((INODE_IS_DRIVER(inode) || INODE_IS_MQUEUE(inode) ||
          INODE_IS_SOCKET(inode) || INODE_IS_PIPE(inode)) &&
          inode->u.i_ops != NULL && inode->u.i_ops->poll != NULL)
        {
          /* Yes, it does... Setup the poll */

          ret = inode->u.i_ops->poll(filep, fds, setup);
        }
#ifndef CONFIG_DISABLE_MOUNTPOINT
      else if (INODE_IS_MOUNTPT(inode) && inode->u.i_mops != NULL &&
               inode->u.i_mops->poll != NULL)
        {
          ret = inode->u.i_mops->poll(filep, fds, setup);
        }
#endif

      /* Regular files (and block devices) are always readable and
       * writable. Open Group: "Regular files shall always poll TRUE for
       * reading and writing."
       */

      else if (INODE_IS_MOUNTPT(inode) || INODE_IS_BLOCK(inode) ||
               INODE_IS_MTD(inode))
        {
          if (setup)
            {
              poll_notify(&fds, 1, POLLIN | POLLOUT);
            }

          ret = OK;
        }
    }
  else
    {
      poll_notify(&fds, 1, POLLERR | POLLHUP);
      ret = OK;
    }

  return ret;
}

/****************************************************************************
 * Name: poll
 *
 * Description:
 *   poll() waits for one of a set of file descriptors to become ready to
 *   perform I/O.  If none of the events requested (and no error) has
 *   occurred for any of  the  file  descriptors,  then  poll() blocks until
 *   one of the events occurs.
 *
 * Input Parameters:
 *   fds  - List of structures describing file descriptors to be monitored
 *   nfds - The number of entries in the list
 *   timeout - Specifies an upper limit on the time for which poll() will
 *     block in milliseconds.  A negative value of timeout means an infinite
 *     timeout.
 *
 * Returned Value:
 *   On success, the number of structures that have non-zero revents fields.
 *   A value of 0 indicates that the call timed out and no file descriptors
 *   were ready.  On error, -1 is returned, and errno is set appropriately:
 *
 *   EBADF  - An invalid file descriptor was given in one of the sets.
 *   EFAULT - The fds address is invalid
 *   EINTR  - A signal occurred before any requested event.
 *   EINVAL - The nfds value exceeds a system limit.
 *   ENOMEM - There was no space to allocate internal data structures.
 *   ENOSYS - One or more of the drivers supporting the file descriptor
 *     does not support the poll method.
 *
 ****************************************************************************/

int poll(FAR struct pollfd *fds, nfds_t nfds, int timeout)
{
  FAR struct pollfd *kfds;
  sem_t sem;
  int count = 0;
  int ret2;
  int ret;

  DEBUGASSERT(nfds == 0 || fds != NULL);

  /* poll() is a cancellation point */

  enter_cancellation_point();

#ifdef CONFIG_BUILD_KERNEL
  /* Allocate kernel memory for the fds */

  kfds = kmm_malloc(nfds * sizeof(struct pollfd));
  if (!kfds)
    {
      /* Out of memory */

      ret = -ENOMEM;
      goto out_with_cancelpt;
    }

  /* Copy the user fds to neutral kernel memory */

  memcpy(kfds, fds, nfds * sizeof(struct pollfd));
#else
  /* Can use the user fds directly */

  kfds = fds;
#endif

  nxsem_init(&sem, 0, 0);
  ret = poll_setup(kfds, nfds, &sem);
  if (ret >= 0)
    {
      if (timeout == 0)
        {
          /* Poll returns immediately whether we have a poll event or not. */

          ret = OK;
        }
      else if (timeout > 0)
        {
          clock_t ticks;

          /* "Implementations may place limitations on the granularity of
           * timeout intervals. If the requested timeout interval requires
           * a finer granularity than the implementation supports, the
           * actual timeout interval will be rounded up to the next
           * supported value." -- opengroup.org
           *
           * Round timeout up to next full tick.
           */

#if (MSEC_PER_TICK * USEC_PER_MSEC) != USEC_PER_TICK && \
    defined(CONFIG_HAVE_LONG_LONG)
          ticks = (((unsigned long long)timeout * USEC_PER_MSEC) +
                   (USEC_PER_TICK - 1)) /
                  USEC_PER_TICK;
#else
          ticks = ((unsigned int)timeout + (MSEC_PER_TICK - 1)) /
                  MSEC_PER_TICK;
#endif

          /* Either wait for either a poll event(s), for a signal to occur,
           * or for the specified timeout to elapse with no event.
           *
           * NOTE: If a poll event is pending (i.e., the semaphore has
           * already been incremented), nxsem_tickwait() will not wait, but
           * will return immediately.
           */

          ret = nxsem_tickwait(&sem, ticks);
          if (ret < 0)
            {
              if (ret == -ETIMEDOUT)
                {
                  /* Return zero (OK) in the event of a timeout */

                  ret = OK;
                }

              /* EINTR is the only other error expected in normal operation */
            }
        }
      else
        {
          /* Wait for the poll event or signal with no timeout */

          ret = nxsem_wait(&sem);
        }

      /* Teardown the poll operation and get the count of events.  Zero will
       * be returned in the case of a timeout.
       *
       * Preserve ret, if negative, since it holds the result of the wait.
       */

      ret2 = poll_teardown(kfds, nfds, &count, ret);
      if (ret2 < 0 && ret >= 0)
        {
          ret = ret2;
        }
    }

  nxsem_destroy(&sem);

#ifdef CONFIG_BUILD_KERNEL
  /* Copy the events back to user */

  if (ret == OK)
    {
      int i;
      for (i = 0; i < nfds; i++)
        {
          fds[i].revents = kfds[i].revents;
        }
    }

  /* Free the temporary buffer */

  kmm_free(kfds);

out_with_cancelpt:
#endif

  leave_cancellation_point();

  if (ret < 0)
    {
      set_errno(-ret);
      return ERROR;
    }
  else
    {
      return count;
    }
}