/****************************************************************************
 * drivers/serial/serial.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 <sys/types.h>
#include <stdint.h>
#include <stdbool.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <poll.h>
#include <assert.h>
#include <errno.h>
#include <debug.h>

#include <nuttx/irq.h>
#include <nuttx/arch.h>
#include <nuttx/clock.h>
#include <nuttx/sched.h>
#include <nuttx/signal.h>
#include <nuttx/fs/fs.h>
#include <nuttx/cancelpt.h>
#include <nuttx/serial/serial.h>
#include <nuttx/fs/ioctl.h>
#include <nuttx/power/pm.h>
#include <nuttx/wqueue.h>
#include <nuttx/kthread.h>

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

/* Check watermark levels */

#if defined(CONFIG_SERIAL_IFLOWCONTROL) && \
    defined(CONFIG_SERIAL_IFLOWCONTROL_WATERMARKS)
#  if CONFIG_SERIAL_IFLOWCONTROL_LOWER_WATERMARK < 1
#    warning CONFIG_SERIAL_IFLOWCONTROL_LOWER_WATERMARK too small
#  endif
#  if CONFIG_SERIAL_IFLOWCONTROL_UPPER_WATERMARK > 99
#    warning CONFIG_SERIAL_IFLOWCONTROL_UPPER_WATERMARK too large
#  endif
#  if CONFIG_SERIAL_IFLOWCONTROL_LOWER_WATERMARK >= CONFIG_SERIAL_IFLOWCONTROL_UPPER_WATERMARK
#    warning CONFIG_SERIAL_IFLOWCONTROL_LOWER_WATERMARK too large
#    warning Must be less than CONFIG_SERIAL_IFLOWCONTROL_UPPER_WATERMARK
#  endif
#endif

/* Timing */

#define POLL_DELAY_USEC 1000

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

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

/* Write support */

static int     uart_putxmitchar(FAR uart_dev_t *dev, int ch,
                                bool oktoblock);
static inline ssize_t uart_irqwrite(FAR uart_dev_t *dev,
                                    FAR const char *buffer,
                                    size_t buflen);
static int     uart_tcdrain(FAR uart_dev_t *dev,
                            bool cancelable, clock_t timeout);

/* Character driver methods */

static int     uart_open(FAR struct file *filep);
static int     uart_close(FAR struct file *filep);
static ssize_t uart_read(FAR struct file *filep,
                         FAR char *buffer, size_t buflen);
static ssize_t uart_write(FAR struct file *filep,
                          FAR const char *buffer,
                          size_t buflen);
static int     uart_ioctl(FAR struct file *filep,
                          int cmd, unsigned long arg);
static int     uart_poll(FAR struct file *filep,
                         FAR struct pollfd *fds, bool setup);

/****************************************************************************
 * Public Function Prototypes
 ****************************************************************************/

#ifdef CONFIG_TTY_LAUNCH_ENTRY
/* Lanch program entry, this must be supplied by the application. */

int CONFIG_TTY_LAUNCH_ENTRYPOINT(int argc, char *argv[]);
#endif

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

static const struct file_operations g_serialops =
{
  uart_open,  /* open */
  uart_close, /* close */
  uart_read,  /* read */
  uart_write, /* write */
  NULL,       /* seek */
  uart_ioctl, /* ioctl */
  uart_poll   /* poll */
#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
  , NULL      /* unlink */
#endif
};

#ifdef CONFIG_TTY_LAUNCH
static struct work_s g_serial_work;
#endif

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

/****************************************************************************
 * Name: uart_putxmitchar
 ****************************************************************************/

static int uart_putxmitchar(FAR uart_dev_t *dev, int ch, bool oktoblock)
{
  irqstate_t flags;
  int nexthead;
  int ret;

  /* Increment to see what the next head pointer will be.
   * We need to use the "next" head pointer to determine when the circular
   *  buffer would overrun
   */

  nexthead = dev->xmit.head + 1;
  if (nexthead >= dev->xmit.size)
    {
      nexthead = 0;
    }

  /* Loop until we are able to add the character to the TX buffer. */

  for (; ; )
    {
      /* Check if the TX buffer is full */

      if (nexthead != dev->xmit.tail)
        {
          /* No.. not full.  Add the character to the TX buffer and return. */

          dev->xmit.buffer[dev->xmit.head] = ch;
          dev->xmit.head = nexthead;
          return OK;
        }

      /* The TX buffer is full.  Should be block, waiting for the hardware
       * to remove some data from the TX buffer?
       */

      else if (oktoblock)
        {
          /* The following steps must be atomic with respect to serial
           * interrupt handling.
           */

          flags = enter_critical_section();

          /* Check again...  In certain race conditions an interrupt may
           * have occurred between the test at the top of the loop and
           * entering the critical section and the TX buffer may no longer
           * be full.
           *
           * NOTE: On certain devices, such as USB CDC/ACM, the entire TX
           * buffer may have been emptied in this race condition.  In that
           * case, the logic would hang below waiting for space in the TX
           * buffer without this test.
           */

          if (nexthead != dev->xmit.tail)
            {
              ret = OK;
            }

#ifdef CONFIG_SERIAL_REMOVABLE
          /* Check if the removable device is no longer connected while we
           * have interrupts off.  We do not want the transition to occur
           * as a race condition before we begin the wait.
           */

          else if (dev->disconnected)
            {
              ret = -ENOTCONN;
            }
#endif
          else
            {
              /* Inform the interrupt level logic that we are waiting. */

              dev->xmitwaiting = true;

              /* Wait for some characters to be sent from the buffer with
               * the TX interrupt enabled.  When the TX interrupt is enabled,
               * uart_xmitchars() should execute and remove some of the data
               * from the TX buffer.
               *
               * NOTE that interrupts will be re-enabled while we wait for
               * the semaphore.
               */

#ifdef CONFIG_SERIAL_TXDMA
              uart_dmatxavail(dev);
#endif
              uart_enabletxint(dev);
              ret = nxsem_wait(&dev->xmitsem);
              uart_disabletxint(dev);
            }

          leave_critical_section(flags);

#ifdef CONFIG_SERIAL_REMOVABLE
          /* Check if the removable device was disconnected while we were
           * waiting.
           */

          if (dev->disconnected)
            {
              return -ENOTCONN;
            }
#endif

          /* Check if we were awakened by signal. */

          if (ret < 0)
            {
              /* A signal received while waiting for the xmit buffer to
               * become non-full will abort the transfer.
               */

              return -EINTR;
            }
        }

      /* The caller has request that we not block for data.  So return the
       * EAGAIN error to signal this situation.
       */

      else
        {
          return -EAGAIN;
        }
    }

  /* We won't get here.  Some compilers may complain that this code is
   * unreachable.
   */

  return OK;
}

/****************************************************************************
 * Name: uart_putc
 ****************************************************************************/

static inline void uart_putc(FAR uart_dev_t *dev, int ch)
{
  while (!uart_txready(dev))
    {
    }

  uart_send(dev, ch);
}

/****************************************************************************
 * Name: uart_irqwrite
 ****************************************************************************/

static inline ssize_t uart_irqwrite(FAR uart_dev_t *dev,
                                    FAR const char *buffer,
                                    size_t buflen)
{
  ssize_t ret = buflen;

  /* Force each character through the low level interface */

  for (; buflen; buflen--)
    {
      int ch = *buffer++;

#ifdef CONFIG_SERIAL_TERMIOS
      /* Do output post-processing */

      if ((dev->tc_oflag & OPOST) != 0)
        {
          /* Mapping CR to NL? */

          if ((ch == '\r') && (dev->tc_oflag & OCRNL) != 0)
            {
              ch = '\n';
            }

          /* Are we interested in newline processing? */

          if ((ch == '\n') && (dev->tc_oflag & (ONLCR | ONLRET)) != 0)
            {
              uart_putc(dev, '\r');
            }
        }

#else /* !CONFIG_SERIAL_TERMIOS */
      /* If this is the console, then we should replace LF with CR-LF */

      if (dev->isconsole && ch == '\n')
        {
          uart_putc(dev, '\r');
        }
#endif

      /* Output the character, using the low-level direct UART interfaces */

      uart_putc(dev, ch);
    }

  return ret;
}

/****************************************************************************
 * Name: uart_tcdrain
 *
 * Description:
 *   Block further TX input.
 *   Wait until all data has been transferred from the TX buffer and
 *   until the hardware TX FIFOs are empty.
 *
 ****************************************************************************/

static int uart_tcdrain(FAR uart_dev_t *dev,
                        bool cancelable, clock_t timeout)
{
  int ret;

  /* tcdrain is a cancellation point */

  if (cancelable && enter_cancellation_point())
    {
#ifdef CONFIG_CANCELLATION_POINTS
      /* If there is a pending cancellation, then do not perform
       * the wait.  Exit now with ECANCELED.
       */

      leave_cancellation_point();
      return -ECANCELED;
#endif
    }

  /* Get exclusive access to the to dev->tmit.  We cannot permit new data to
   * be written while we are trying to flush the old data.
   *
   * A signal received while waiting for access to the xmit.head will abort
   * the operation with EINTR.
   */

  ret = nxmutex_lock(&dev->xmit.lock);
  if (ret >= 0)
    {
      irqstate_t flags;
      clock_t start;

      /* Trigger emission to flush the contents of the tx buffer */

      flags = enter_critical_section();

#ifdef CONFIG_SERIAL_REMOVABLE
      /* Check if the removable device is no longer connected while we have
       * interrupts off.  We do not want the transition to occur as a race
       * condition before we begin the wait.
       */

      if (dev->disconnected)
        {
          dev->xmit.tail = dev->xmit.head;  /* Drop the buffered TX data */
          ret = -ENOTCONN;
        }
      else
#endif
        {
          /* Continue waiting while the TX buffer is not empty.
           *
           * NOTE: There is no timeout on the following loop.  In
           * situations were this loop could hang (with hardware flow
           * control, as an example),  the caller should call
           * tcflush() first to discard this buffered Tx data.
           */

          ret = OK;
          while (ret >= 0 && dev->xmit.head != dev->xmit.tail)
            {
              /* Inform the interrupt level logic that we are waiting. */

              dev->xmitwaiting = true;

              /* Wait for some characters to be sent from the buffer with
               * the TX interrupt enabled.  When the TX interrupt is
               * enabled, uart_xmitchars() should execute and remove some
               * of the data from the TX buffer.  We may have to wait several
               * times for the TX buffer to be entirely emptied.
               *
               * NOTE that interrupts will be re-enabled while we wait for
               * the semaphore.
               */

#ifdef CONFIG_SERIAL_TXDMA
              uart_dmatxavail(dev);
#endif
              uart_enabletxint(dev);
              ret = nxsem_wait(&dev->xmitsem);
              uart_disabletxint(dev);
            }
        }

      leave_critical_section(flags);

      /* The TX buffer is empty (or an error occurred).  But there still may
       * be data in the UART TX FIFO.  We get no asynchronous indication of
       * this event, so we have to do a busy wait poll.
       */

      /* Set up for the timeout
       *
       * REVISIT:  This is a kludge.  The correct fix would be add an
       * interface to the lower half driver so that the tcflush() operation
       * all also cause the lower half driver to clear and reset the Tx FIFO.
       */

      start = clock_systime_ticks();

      if (ret >= 0)
        {
          while (!uart_txempty(dev))
            {
              clock_t elapsed;

              nxsig_usleep(POLL_DELAY_USEC);

              /* Check for a timeout */

              elapsed = clock_systime_ticks() - start;
              if (elapsed >= timeout)
                {
                  nxmutex_unlock(&dev->xmit.lock);
                  return -ETIMEDOUT;
                }
            }
        }

      nxmutex_unlock(&dev->xmit.lock);
    }

  if (cancelable)
    {
      leave_cancellation_point();
    }

  return ret;
}

/****************************************************************************
 * Name: uart_open
 *
 * Description:
 *   This routine is called whenever a serial port is opened.
 *
 ****************************************************************************/

static int uart_open(FAR struct file *filep)
{
  FAR struct inode *inode = filep->f_inode;
  FAR uart_dev_t   *dev   = inode->i_private;
  uint8_t           tmp;
  int               ret;

  /* If the port is the middle of closing, wait until the close is finished.
   * If a signal is received while we are waiting, then return EINTR.
   */

  ret = nxmutex_lock(&dev->closelock);
  if (ret < 0)
    {
      /* A signal received while waiting for the last close operation. */

      return ret;
    }

#ifdef CONFIG_SERIAL_REMOVABLE
  /* If the removable device is no longer connected, refuse to open the
   * device.  We check this after obtaining the close semaphore because
   * we might have been waiting when the device was disconnected.
   */

  if (dev->disconnected)
    {
      ret = -ENOTCONN;
      goto errout_with_lock;
    }
#endif

  /* Start up serial port */

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

  tmp = dev->open_count + 1;
  if (tmp == 0)
    {
      /* More than 255 opens; uint8_t overflows to zero */

      ret = -EMFILE;
      goto errout_with_lock;
    }

  /* Check if this is the first time that the driver has been opened. */

  if (tmp == 1)
    {
      irqstate_t flags = enter_critical_section();

      /* If this is the console, then the UART has already been
       * initialized.
       */

      if (!dev->isconsole)
        {
          /* Perform one time hardware initialization */

          ret = uart_setup(dev);
          if (ret < 0)
            {
              leave_critical_section(flags);
              goto errout_with_lock;
            }
        }

      /* In any event, we do have to configure for interrupt driven mode of
       * operation.  Attach the hardware IRQ(s). Hmm.. should shutdown() the
       * the device in the rare case that uart_attach() fails, tmp==1, and
       * this is not the console.
       */

      ret = uart_attach(dev);
      if (ret < 0)
        {
          uart_shutdown(dev);
          leave_critical_section(flags);
          goto errout_with_lock;
        }

#ifdef CONFIG_SERIAL_RXDMA
      /* Notify DMA that there is free space in the RX buffer */

      uart_dmarxfree(dev);
#endif

      /* Enable the RX interrupt */

      uart_enablerxint(dev);
      leave_critical_section(flags);
    }

  /* Save the new open count on success */

  dev->open_count = tmp;

errout_with_lock:
  nxmutex_unlock(&dev->closelock);
  return ret;
}

/****************************************************************************
 * Name: uart_close
 *
 * Description:
 *   This routine is called when the serial port gets closed.
 *   It waits for the last remaining data to be sent.
 *
 ****************************************************************************/

static int uart_close(FAR struct file *filep)
{
  FAR struct inode *inode = filep->f_inode;
  FAR uart_dev_t   *dev   = inode->i_private;
  irqstate_t        flags;

  /* Get exclusive access to the close semaphore (to synchronize open/close
   * operations.
   * NOTE: that we do not let this wait be interrupted by a signal.
   * Technically, we should, but almost no one every checks the return value
   * from close() so we avoid a potential memory leak by ignoring signals in
   * this case.
   */

  nxmutex_lock(&dev->closelock);
  if (dev->open_count > 1)
    {
      dev->open_count--;
      nxmutex_unlock(&dev->closelock);
      return OK;
    }

  /* There are no more references to the port */

  dev->open_count = 0;

  /* Stop accepting input */

  uart_disablerxint(dev);

  /* Prevent blocking if the device is opened with O_NONBLOCK */

  if ((filep->f_oflags & O_NONBLOCK) == 0)
    {
      /* Now we wait for the transmit buffer(s) to clear */

      uart_tcdrain(dev, false, 4 * TICK_PER_SEC);
    }

  /* Free the IRQ and disable the UART */

  flags = enter_critical_section();  /* Disable interrupts */
  uart_detach(dev);                  /* Detach interrupts */
  if (!dev->isconsole)               /* Check for the serial console UART */
    {
      uart_shutdown(dev);            /* Disable the UART */
    }

  leave_critical_section(flags);

  /* Wake up read and poll functions */

  uart_datareceived(dev);

  /* We need to re-initialize the semaphores if this is the last close
   * of the device, as the close might be caused by pthread_cancel() of
   * a thread currently blocking on any of them.
   */

  uart_reset_sem(dev);
  nxmutex_unlock(&dev->closelock);
  return OK;
}

/****************************************************************************
 * Name: uart_read
 ****************************************************************************/

static ssize_t uart_read(FAR struct file *filep,
                         FAR char *buffer, size_t buflen)
{
  FAR struct inode *inode = filep->f_inode;
  FAR uart_dev_t *dev = inode->i_private;
  FAR struct uart_buffer_s *rxbuf = &dev->recv;
#ifdef CONFIG_SERIAL_IFLOWCONTROL_WATERMARKS
  unsigned int nbuffered;
  unsigned int watermark;
#endif
  irqstate_t flags;
  ssize_t recvd = 0;
  int16_t tail;
  char ch;
  int ret;

  /* Only one user can access rxbuf->tail at a time */

  ret = nxmutex_lock(&dev->recv.lock);
  if (ret < 0)
    {
      /* A signal received while waiting for access to the recv.tail will
       * abort the transfer.  After the transfer has started, we are
       * committed and signals will be ignored.
       */

      return ret;
    }

  /* Loop while we still have data to copy to the receive buffer.
   * we add data to the head of the buffer; uart_xmitchars takes the
   * data from the end of the buffer.
   */

  while ((size_t)recvd < buflen)
    {
#ifdef CONFIG_SERIAL_REMOVABLE
      /* If the removable device is no longer connected, refuse to read any
       * further from the device.
       */

      if (dev->disconnected)
        {
          if (recvd == 0)
            {
              recvd = -ENOTCONN;
            }

          break;
        }
#endif

      /* Check if there is more data to return in the circular buffer.
       * NOTE: Rx interrupt handling logic may asynchronously increment
       * the head index but must not modify the tail index.  The tail
       * index is only modified in this function.  Therefore, no
       * special handshaking is required here.
       *
       * The head and tail pointers are 16-bit values.  The only time that
       * the following could be unsafe is if the CPU made two non-atomic
       * 8-bit accesses to obtain the 16-bit head index.
       */

      tail = rxbuf->tail;
      if (rxbuf->head != tail)
        {
          /* Take the next character from the tail of the buffer */

          ch = rxbuf->buffer[tail];

          /* Increment the tail index.  Most operations are done using the
           * local variable 'tail' so that the final rxbuf->tail update
           * is atomic.
           */

          if (++tail >= rxbuf->size)
            {
              tail = 0;
            }

          rxbuf->tail = tail;

#ifdef CONFIG_SERIAL_TERMIOS
          /* Do input processing if any is enabled */

          if (dev->tc_iflag & (INLCR | IGNCR | ICRNL))
            {
              /* \n -> \r or \r -> \n translation? */

              if ((ch == '\n') && (dev->tc_iflag & INLCR))
                {
                  ch = '\r';
                }
              else if ((ch == '\r') && (dev->tc_iflag & ICRNL))
                {
                  ch = '\n';
                }

              /* Discarding \r ? */

              if ((ch == '\r') & (dev->tc_iflag & IGNCR))
                {
                  continue;
                }
            }

          /* Specifically not handled:
           *
           * All of the local modes; echo, line editing, etc.
           * Anything to do with break or parity errors.
           * ISTRIP - we should be 8-bit clean.
           * IUCLC - Not Posix
           * IXON/OXOFF - no xon/xoff flow control.
           */
#endif

          /* Store the received character */

          *buffer++ = ch;
          recvd++;
        }

#ifdef CONFIG_DEV_SERIAL_FULLBLOCKS
      /* No... then we would have to wait to get receive more data.
       * If the user has specified the O_NONBLOCK option, then just
       * return what we have.
       */

      else if ((filep->f_oflags & O_NONBLOCK) != 0)
        {
          /* If nothing was transferred, then return the -EAGAIN
           * error (not zero which means end of file).
           */

          if (recvd < 1)
            {
              recvd = -EAGAIN;
            }

          break;
        }
#else
      /* No... the circular buffer is empty.  Have we returned anything
       * to the caller?
       */

      else if (recvd > 0)
        {
          /* Yes.. break out of the loop and return the number of bytes
           * received up to the wait condition.
           */

          break;
        }

      else if (filep->f_inode == 0)
        {
          /* File has been closed.
           * Descriptor is not valid.
           */

          recvd = -EBADFD;
          break;
        }

      /* No... then we would have to wait to get receive some data.
       * If the user has specified the O_NONBLOCK option, then do not
       * wait.
       */

      else if ((filep->f_oflags & O_NONBLOCK) != 0)
        {
          /* Break out of the loop returning -EAGAIN */

          recvd = -EAGAIN;
          break;
        }
#endif

      /* Otherwise we are going to have to wait for data to arrive */

      else
        {
          /* Disable all interrupts and test again... */

          flags = enter_critical_section();

          /* Disable Rx interrupts and test again... */

          uart_disablerxint(dev);

          /* If the Rx ring buffer still empty?  Bytes may have been added
           * between the last time that we checked and when we disabled
           * interrupts.
           */

          if (rxbuf->head == rxbuf->tail)
            {
              /* Yes.. the buffer is still empty.  We will need to wait for
               * additional data to be received.
               */

#ifdef CONFIG_SERIAL_RXDMA
              /* Notify DMA that there is free space in the RX buffer */

              uart_dmarxfree(dev);
#endif
              /* Wait with the RX interrupt re-enabled.  All interrupts are
               * disabled briefly to assure that the following operations
               * are atomic.
               */

              /* Re-enable UART Rx interrupts */

              uart_enablerxint(dev);

              /* Check again if the RX buffer is empty.  The UART driver
               * might have buffered data received between disabling the
               * RX interrupt and entering the critical section.  Some
               * drivers (looking at you, cdcacm...) will push the buffer
               * to the receive queue during uart_enablerxint().
               * Just continue processing the RX queue if this happens.
               */

              if (rxbuf->head != rxbuf->tail)
                {
                  leave_critical_section(flags);
                  continue;
                }

#ifdef CONFIG_SERIAL_REMOVABLE
              /* Check again if the removable device is still connected
               * while we have interrupts off.  We do not want the transition
               * to occur as a race condition before we begin the wait.
               */

              if (dev->disconnected)
                {
                  ret = -ENOTCONN;
                }
              else
#endif
                {
                  /* Now wait with the Rx interrupt enabled.  NuttX will
                   * automatically re-enable global interrupts when this
                   * thread goes to sleep.
                   */

                  dev->recvwaiting = true;
                  ret = nxsem_wait(&dev->recvsem);
                }

              leave_critical_section(flags);

              /* Was a signal received while waiting for data to be
               * received?  Was a removable device disconnected while
               * we were waiting?
               */

#ifdef CONFIG_SERIAL_REMOVABLE
              if (ret < 0 || dev->disconnected)
#else
              if (ret < 0)
#endif
                {
                  /* POSIX requires that we return after a signal is
                   * received.
                   * If some bytes were read, we need to return the
                   * number of bytes read; if no bytes were read, we
                   * need to return -1 with the errno set correctly.
                   */

                  if (recvd == 0)
                    {
                      /* No bytes were read, return -EINTR
                       * (the VFS layer will set the errno value
                       * appropriately).
                       */

#ifdef CONFIG_SERIAL_REMOVABLE
                      recvd = dev->disconnected ? -ENOTCONN : -EINTR;
#else
                      recvd = -EINTR;
#endif
                    }

                  break;
                }
            }
          else
            {
              /* No... the ring buffer is no longer empty.  Just re-enable Rx
               * interrupts and accept the new data on the next time through
               * the loop.
               */

              leave_critical_section(flags);

              uart_enablerxint(dev);
            }
        }
    }

#ifdef CONFIG_SERIAL_RXDMA
  /* Notify DMA that there is free space in the RX buffer */

  flags = enter_critical_section();
  uart_dmarxfree(dev);
  leave_critical_section(flags);
#endif

  /* RX interrupt could be disabled by RX buffer overflow. Enable it now. */

  uart_enablerxint(dev);

#ifdef CONFIG_SERIAL_IFLOWCONTROL
#ifdef CONFIG_SERIAL_IFLOWCONTROL_WATERMARKS
  /* How many bytes are now buffered */

  rxbuf = &dev->recv;
  if (rxbuf->head >= rxbuf->tail)
    {
      nbuffered = rxbuf->head - rxbuf->tail;
    }
  else
    {
      nbuffered = rxbuf->size - rxbuf->tail + rxbuf->head;
    }

  /* Is the level now below the watermark level that we need to report? */

  watermark = (CONFIG_SERIAL_IFLOWCONTROL_LOWER_WATERMARK *
               rxbuf->size) / 100;
  if (nbuffered <= watermark)
    {
      /* Let the lower level driver know that the watermark level has been
       * crossed.  It will probably deactivate RX flow control.
       */

      uart_rxflowcontrol(dev, nbuffered, false);
    }
#else
  /* Is the RX buffer empty? */

  if (rxbuf->head == rxbuf->tail)
    {
      /* Deactivate RX flow control. */

      uart_rxflowcontrol(dev, 0, false);
    }
#endif
#endif

  nxmutex_unlock(&dev->recv.lock);
  return recvd;
}

/****************************************************************************
 * Name: uart_write
 ****************************************************************************/

static ssize_t uart_write(FAR struct file *filep, FAR const char *buffer,
                          size_t buflen)
{
  FAR struct inode *inode    = filep->f_inode;
  FAR uart_dev_t   *dev      = inode->i_private;
  ssize_t           nwritten = buflen;
  bool              oktoblock;
  int               ret;
  char              ch;

  /* We may receive serial writes through this path from interrupt handlers
   * and from debug output in the IDLE task!  In these cases, we will need to
   * do things a little differently.
   */

  if (up_interrupt_context() || sched_idletask())
    {
      irqstate_t flags;

#ifdef CONFIG_SERIAL_REMOVABLE
      /* If the removable device is no longer connected, refuse to write to
       * the device.
       */

      if (dev->disconnected)
        {
          return -ENOTCONN;
        }
#endif

      flags = enter_critical_section();
      ret = uart_irqwrite(dev, buffer, buflen);
      leave_critical_section(flags);

      return ret;
    }

  /* Only one user can access dev->xmit.head at a time */

  ret = nxmutex_lock(&dev->xmit.lock);
  if (ret < 0)
    {
      /* A signal received while waiting for access to the xmit.head will
       * abort the transfer.  After the transfer has started, we are
       * committed and signals will be ignored.
       */

      return ret;
    }

#ifdef CONFIG_SERIAL_REMOVABLE
  /* If the removable device is no longer connected, refuse to write to the
   * device.  This check occurs after taking the xmit.lock because the
   * disconnection event might have occurred while we were waiting for
   * access to the transmit buffers.
   */

  if (dev->disconnected)
    {
      nxmutex_unlock(&dev->xmit.lock);
      return -ENOTCONN;
    }
#endif

  /* Can the following loop block, waiting for space in the TX
   * buffer?
   */

  oktoblock = ((filep->f_oflags & O_NONBLOCK) == 0);

  /* Loop while we still have data to copy to the transmit buffer.
   * we add data to the head of the buffer; uart_xmitchars takes the
   * data from the end of the buffer.
   */

  uart_disabletxint(dev);
  for (; buflen; buflen--)
    {
      ch  = *buffer++;
      ret = OK;

#ifdef CONFIG_SERIAL_TERMIOS
      /* Do output post-processing */

      if ((dev->tc_oflag & OPOST) != 0)
        {
          /* Mapping CR to NL? */

          if ((ch == '\r') && (dev->tc_oflag & OCRNL) != 0)
            {
              ch = '\n';
            }

          /* Are we interested in newline processing? */

          if ((ch == '\n') && (dev->tc_oflag & (ONLCR | ONLRET)) != 0)
            {
              ret = uart_putxmitchar(dev, '\r', oktoblock);
            }

          /* Specifically not handled:
           *
           * OXTABS - primarily a full-screen terminal optimization
           * ONOEOT - Unix interoperability hack
           * OLCUC  - Not specified by POSIX
           * ONOCR  - low-speed interactive optimization
           */
        }

#else /* !CONFIG_SERIAL_TERMIOS */
      /* If this is the console, convert \n -> \r\n */

      if (dev->isconsole && ch == '\n')
        {
          ret = uart_putxmitchar(dev, '\r', oktoblock);
        }
#endif

      /* Put the character into the transmit buffer */

      if (ret >= 0)
        {
          ret = uart_putxmitchar(dev, ch, oktoblock);
        }

      /* uart_putxmitchar() might return an error under one of two
       * conditions:  (1) The wait for buffer space might have been
       * interrupted by a signal (ret should be -EINTR), (2) if
       * CONFIG_SERIAL_REMOVABLE is defined, then uart_putxmitchar()
       * might also return if the serial device was disconnected
       * (with -ENOTCONN), or (3) if O_NONBLOCK is specified, then
       * then uart_putxmitchar() might return -EAGAIN if the output
       * TX buffer is full.
       */

      if (ret < 0)
        {
          /* POSIX requires that we return -1 and errno set if no data was
           * transferred.  Otherwise, we return the number of bytes in the
           * interrupted transfer.
           */

          if (buflen < (size_t)nwritten)
            {
              /* Some data was transferred.  Return the number of bytes that
               * were successfully transferred.
               */

              nwritten -= buflen;
            }
          else
            {
              /* No data was transferred. Return the negated errno value.
               * The VFS layer will set the errno value appropriately).
               */

              nwritten = ret;
            }

          break;
        }
    }

  if (dev->xmit.head != dev->xmit.tail)
    {
#ifdef CONFIG_SERIAL_TXDMA
      uart_dmatxavail(dev);
#endif
      uart_enabletxint(dev);
    }

  nxmutex_unlock(&dev->xmit.lock);
  return nwritten;
}

/****************************************************************************
 * Name: uart_ioctl
 ****************************************************************************/

static int uart_ioctl(FAR struct file *filep, int cmd, unsigned long arg)
{
  FAR struct inode *inode = filep->f_inode;
  FAR uart_dev_t   *dev   = inode->i_private;

  /* Handle TTY-level IOCTLs here */

  /* Let low-level driver handle the call first */

  int ret = dev->ops->ioctl ? dev->ops->ioctl(filep, cmd, arg) : -ENOTTY;

  /* The device ioctl() handler returns -ENOTTY when it doesn't know
   * how to handle the command. Check if we can handle it here.
   */

  if (ret == -ENOTTY)
    {
      switch (cmd)
        {
          /* Get the number of bytes that may be read from the RX buffer
           * (without waiting)
           */

          case FIONREAD:
            {
              int count;
              irqstate_t flags = enter_critical_section();

              /* Determine the number of bytes available in the RX buffer */

              if (dev->recv.tail <= dev->recv.head)
                {
                  count = dev->recv.head - dev->recv.tail;
                }
              else
                {
                  count = dev->recv.size - (dev->recv.tail - dev->recv.head);
                }

              leave_critical_section(flags);

              *(FAR int *)((uintptr_t)arg) = count;
              ret = 0;
            }
            break;

          /* Get the number of bytes that have been written to the TX
           * buffer.
           */

          case FIONWRITE:
            {
              int count;
              irqstate_t flags = enter_critical_section();

              /* Determine the number of bytes waiting in the TX buffer */

              if (dev->xmit.tail <= dev->xmit.head)
                {
                  count = dev->xmit.head - dev->xmit.tail;
                }
              else
                {
                  count = dev->xmit.size - (dev->xmit.tail - dev->xmit.head);
                }

              leave_critical_section(flags);

              *(FAR int *)((uintptr_t)arg) = count;
              ret = 0;
            }
            break;

          /* Get the number of free bytes in the TX buffer */

          case FIONSPACE:
            {
              int count;
              irqstate_t flags = enter_critical_section();

              /* Determine the number of bytes free in the TX buffer */

              if (dev->xmit.head < dev->xmit.tail)
                {
                  count = dev->xmit.tail - dev->xmit.head - 1;
                }
              else
                {
                  count = dev->xmit.size -
                         (dev->xmit.head - dev->xmit.tail) - 1;
                }

              leave_critical_section(flags);

              *(FAR int *)((uintptr_t)arg) = count;
              ret = 0;
            }
            break;

          case TCFLSH:
            {
              /* Empty the tx/rx buffers */

              irqstate_t flags = enter_critical_section();

              if (arg == TCIFLUSH || arg == TCIOFLUSH)
                {
                  dev->recv.tail = dev->recv.head;

#ifdef CONFIG_SERIAL_IFLOWCONTROL
                  /* De-activate RX flow control. */

                  uart_rxflowcontrol(dev, 0, false);
#endif
                }

              if (arg == TCOFLUSH || arg == TCIOFLUSH)
                {
                  dev->xmit.tail = dev->xmit.head;

                  /* Inform any waiters there there is space available. */

                  uart_datasent(dev);
                }

              leave_critical_section(flags);
              ret = 0;
            }
            break;

          case TCDRN:
            {
              ret = uart_tcdrain(dev, true, 10 * TICK_PER_SEC);
            }
            break;

#if defined(CONFIG_TTY_SIGINT) || defined(CONFIG_TTY_SIGTSTP)
          /* Make the controlling terminal of the calling process */

          case TIOCSCTTY:
            {
              /* Save the PID of the recipient of the SIGINT signal. */

              if ((int)arg < 0 || dev->pid >= 0)
                {
                  ret = -EINVAL;
                }
              else
                {
                  dev->pid = (pid_t)arg;
                  ret = 0;
                }
            }
            break;

          case TIOCNOTTY:
            {
              dev->pid = INVALID_PROCESS_ID;
              ret = 0;
            }
            break;
#endif
        }
    }

#ifdef CONFIG_SERIAL_TERMIOS
  /* Append any higher level TTY flags */

  if (ret == OK || ret == -ENOTTY)
    {
      switch (cmd)
        {
          case TCGETS:
            {
              FAR struct termios *termiosp = (FAR struct termios *)arg;

              if (!termiosp)
                {
                  ret = -EINVAL;
                  break;
                }

              /* And update with flags from this layer */

              termiosp->c_iflag = dev->tc_iflag;
              termiosp->c_oflag = dev->tc_oflag;
              termiosp->c_lflag = dev->tc_lflag;

              ret = 0;
            }
            break;

          case TCSETS:
            {
              FAR struct termios *termiosp = (FAR struct termios *)arg;

              if (!termiosp)
                {
                  ret = -EINVAL;
                  break;
                }

              /* Update the flags we keep at this layer */

              dev->tc_iflag = termiosp->c_iflag;
              dev->tc_oflag = termiosp->c_oflag;
              dev->tc_lflag = termiosp->c_lflag;

              ret = 0;
            }
            break;
        }
    }
#endif

  return ret;
}

/****************************************************************************
 * Name: uart_poll
 ****************************************************************************/

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

  /* Some sanity checking */

#ifdef CONFIG_DEBUG_FEATURES
  if (dev == NULL || fds == NULL)
    {
      return -ENODEV;
    }
#endif

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

  ret = nxmutex_lock(&dev->polllock);
  if (ret < 0)
    {
      /* A signal received while waiting for access to the poll data
       * will abort the operation.
       */

      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 < CONFIG_SERIAL_NPOLLWAITERS; i++)
        {
          /* Find an available slot */

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

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

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

      /* Should we immediately notify on any of the requested events?
       * First, check if the xmit buffer is full.
       *
       * Get exclusive access to the xmit buffer indices.
       * NOTE: that we do not let this wait be interrupted by a signal
       * (we probably should, but that would be a little awkward).
       */

      eventset = 0;
      nxmutex_lock(&dev->xmit.lock);

      ndx = dev->xmit.head + 1;
      if (ndx >= dev->xmit.size)
        {
          ndx = 0;
        }

      if (ndx != dev->xmit.tail)
        {
          eventset |= POLLOUT;
        }

      nxmutex_unlock(&dev->xmit.lock);

      /* Check if the receive buffer is empty.
       *
       * Get exclusive access to the recv buffer indices.
       * NOTE: that we do not let this wait be interrupted by a signal
       * (we probably should, but that would be a little awkward).
       */

      nxmutex_lock(&dev->recv.lock);
      if (dev->recv.head != dev->recv.tail)
        {
          eventset |= POLLIN;
        }

      nxmutex_unlock(&dev->recv.lock);

#ifdef CONFIG_SERIAL_REMOVABLE
      /* Check if a removable device has been disconnected. */

      if (dev->disconnected)
        {
           eventset |= (POLLERR | POLLHUP);
        }
#endif

      poll_notify(dev->fds, CONFIG_SERIAL_NPOLLWAITERS, eventset);
    }
  else if (fds->priv != NULL)
    {
      /* This is a request to tear down the poll. */

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

#ifdef CONFIG_DEBUG_FEATURES
      if (!slot)
        {
          ret = -EIO;
          goto errout;
        }
#endif

      /* Remove all memory of the poll setup */

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

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

/****************************************************************************
 * Name: uart_nxsched_foreach_cb
 ****************************************************************************/

#ifdef CONFIG_TTY_LAUNCH
static void uart_launch_foreach(FAR struct tcb_s *tcb, FAR void *arg)
{
#ifdef CONFIG_TTY_LAUNCH_ENTRY
  if (!strcmp(tcb->name, CONFIG_TTY_LAUNCH_ENTRYNAME))
#else
  if (!strcmp(tcb->name, CONFIG_TTY_LAUNCH_FILEPATH))
#endif
    {
      *(int *)arg = 1;
    }
}

static void uart_launch_worker(void *arg)
{
#ifdef CONFIG_TTY_LAUNCH_ARGS
  FAR char *const argv[] =
  {
    CONFIG_TTY_LAUNCH_ARGS,
    NULL,
  };
#else
  FAR char *const *argv = NULL;
#endif
  int found = 0;

  nxsched_foreach(uart_launch_foreach, &found);
  if (!found)
    {
#ifdef CONFIG_TTY_LAUNCH_ENTRY
      nxtask_create(CONFIG_TTY_LAUNCH_ENTRYNAME,
                    CONFIG_TTY_LAUNCH_PRIORITY,
                    NULL, CONFIG_TTY_LAUNCH_STACKSIZE,
                    CONFIG_TTY_LAUNCH_ENTRYPOINT,
                    argv, NULL);
#else
      posix_spawnattr_t attr;

      posix_spawnattr_init(&attr);

      attr.priority  = CONFIG_TTY_LAUNCH_PRIORITY;
      attr.stacksize = CONFIG_TTY_LAUNCH_STACKSIZE;
      exec_spawn(CONFIG_TTY_LAUNCH_FILEPATH, argv, NULL, 0, &attr);
#endif
    }
}

static void uart_launch(void)
{
  work_queue(HPWORK, &g_serial_work, uart_launch_worker, NULL, 0);
}
#endif

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

/****************************************************************************
 * Name: uart_register
 *
 * Description:
 *   Register serial console and serial ports.
 *
 ****************************************************************************/

int uart_register(FAR const char *path, FAR uart_dev_t *dev)
{
#if defined(CONFIG_TTY_SIGINT) || defined(CONFIG_TTY_SIGTSTP)
  /* Initialize  of the task that will receive SIGINT signals. */

  dev->pid = INVALID_PROCESS_ID;
#endif

#ifdef CONFIG_SERIAL_TERMIOS
  /* If this UART is a serial console */

  if (dev->isconsole)
    {
      /* Enable signals by default */

      dev->tc_lflag |= ISIG;

      /* Enable \n -> \r\n translation for the console */

      dev->tc_oflag = OPOST | ONLCR;
    }
#endif

  /* Initialize mutex & semaphores */

  nxmutex_init(&dev->xmit.lock);
  nxmutex_init(&dev->recv.lock);
  nxmutex_init(&dev->closelock);
  nxsem_init(&dev->xmitsem, 0, 0);
  nxsem_init(&dev->recvsem, 0, 0);
  nxmutex_init(&dev->polllock);

  /* Register the serial driver */

  sinfo("Registering %s\n", path);
  return register_driver(path, &g_serialops, 0666, dev);
}

/****************************************************************************
 * Name: uart_datareceived
 *
 * Description:
 *   This function is called from uart_recvchars when new serial data is
 *   place in the driver's circular buffer.  This function will wake-up any
 *   stalled read() operations that are waiting for incoming data.
 *
 ****************************************************************************/

void uart_datareceived(FAR uart_dev_t *dev)
{
  /* Notify all poll/select waiters that they can read from the recv buffer */

  poll_notify(dev->fds, CONFIG_SERIAL_NPOLLWAITERS, POLLIN);

  /* Is there a thread waiting for read data?  */

  if (dev->recvwaiting)
    {
      /* Yes... wake it up */

      dev->recvwaiting = false;
      nxsem_post(&dev->recvsem);
    }

#if defined(CONFIG_PM) && defined(CONFIG_SERIAL_CONSOLE)
  /* Call pm_activity when characters are received on the console device */

  if (dev->isconsole)
    {
      pm_activity(CONFIG_SERIAL_PM_ACTIVITY_DOMAIN,
                  CONFIG_SERIAL_PM_ACTIVITY_PRIORITY);
    }
#endif
}

/****************************************************************************
 * Name: uart_datasent
 *
 * Description:
 *   This function is called from uart_xmitchars after serial data has been
 *   sent, freeing up some space in the driver's circular buffer. This
 *   function will wake-up any stalled write() operations that was waiting
 *   for space to buffer outgoing data.
 *
 ****************************************************************************/

void uart_datasent(FAR uart_dev_t *dev)
{
  /* Notify all poll/select waiters that they can write to xmit buffer */

  poll_notify(dev->fds, CONFIG_SERIAL_NPOLLWAITERS, POLLOUT);

  /* Is there a thread waiting for space in xmit.buffer?  */

  if (dev->xmitwaiting)
    {
      /* Yes... wake it up */

      dev->xmitwaiting = false;
      nxsem_post(&dev->xmitsem);
    }
}

/****************************************************************************
 * Name: uart_connected
 *
 * Description:
 *   Serial devices (like USB serial) can be removed.
 *   In that case, the "upper half" serial driver must be informed that there
 *   is no longer a valid serial channel associated with the driver.
 *
 *   In this case, the driver will terminate all pending transfers wint
 *   ENOTCONN and will refuse all further transactions while the "lower half"
 *   is disconnected.
 *   The driver will continue to be registered, but will be in an unusable
 *   state.
 *
 *   Conversely, the "upper half" serial driver needs to know when the serial
 *   device is reconnected so that it can resume normal operations.
 *
 * Assumptions/Limitations:
 *   This function may be called from an interrupt handler.
 *
 ****************************************************************************/

#ifdef CONFIG_SERIAL_REMOVABLE
void uart_connected(FAR uart_dev_t *dev, bool connected)
{
  irqstate_t flags;

  /* Is the device disconnected?  Interrupts are disabled because this
   * function may be called from interrupt handling logic.
   */

  flags = enter_critical_section();
  dev->disconnected = !connected;
  if (!connected)
    {
      /* Notify all poll/select waiters that a hangup occurred */

      poll_notify(dev->fds, CONFIG_SERIAL_NPOLLWAITERS, POLLERR | POLLHUP);

      /* Yes.. wake up all waiting threads.  Each thread should detect the
       * disconnection and return the ENOTCONN error.
       */

      /* Is there a thread waiting for space in xmit.buffer?  */

      if (dev->xmitwaiting)
        {
          /* Yes... wake it up */

          dev->xmitwaiting = false;
          nxsem_post(&dev->xmitsem);
        }

      /* Is there a thread waiting for read data?  */

      if (dev->recvwaiting)
        {
          /* Yes... wake it up */

          dev->recvwaiting = false;
          nxsem_post(&dev->recvsem);
        }
    }

  leave_critical_section(flags);
}
#endif

/****************************************************************************
 * Name: uart_reset_sem
 *
 * Description:
 *   This function is called when need reset uart semaphore, this may used in
 *   kill one process, but this process was reading/writing with the
 *   semaphore.
 *
 ****************************************************************************/

void uart_reset_sem(FAR uart_dev_t *dev)
{
  nxsem_reset(&dev->xmitsem,  0);
  nxsem_reset(&dev->recvsem,  0);
  nxmutex_reset(&dev->xmit.lock);
  nxmutex_reset(&dev->recv.lock);
  nxmutex_reset(&dev->polllock);
}

/****************************************************************************
 * Name: uart_check_special
 *
 * Description:
 *   Check if the SIGINT or SIGTSTP character is in the contiguous Rx DMA
 *   buffer region.  The first signal associated with the first such
 *   character is returned.
 *
 *   If there multiple such characters in the buffer, only the signal
 *   associated with the first is returned (this a bug!)
 *
 * Returned Value:
 *   0 if a signal-related character does not appear in the.  Otherwise,
 *   SIGKILL or SIGTSTP may be returned to indicate the appropriate signal
 *   action.
 *
 ****************************************************************************/

#if defined(CONFIG_TTY_SIGINT) || defined(CONFIG_TTY_SIGTSTP) || \
    defined(CONFIG_TTY_FORCE_PANIC) || defined(CONFIG_TTY_LAUNCH)
int uart_check_special(FAR uart_dev_t *dev, const char *buf, size_t size)
{
  size_t i;

#ifdef CONFIG_SERIAL_TERMIOS
  if ((dev->tc_lflag & ISIG) == 0)
#else
  if (!dev->isconsole)
#endif
    {
      return 0;
    }

  for (i = 0; i < size; i++)
    {
#ifdef CONFIG_TTY_FORCE_PANIC
      if (buf[i] == CONFIG_TTY_FORCE_PANIC_CHAR)
        {
          PANIC();
          return 0;
        }
#endif

#ifdef CONFIG_TTY_LAUNCH
      if (buf[i] == CONFIG_TTY_LAUNCH_CHAR)
        {
          uart_launch();
          return 0;
        }
#endif

#ifdef CONFIG_TTY_SIGINT
      if (dev->pid > 0 && buf[i] == CONFIG_TTY_SIGINT_CHAR)
        {
          return SIGINT;
        }
#endif

#ifdef CONFIG_TTY_SIGTSTP
      if (dev->pid > 0 && buf[i] == CONFIG_TTY_SIGTSTP_CHAR)
        {
          return SIGTSTP;
        }
#endif
    }

  return 0;
}
#endif
