/****************************************************************************
 * drivers/ipcc/ipcc_write.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 <nuttx/ipcc.h>
#include <nuttx/kmalloc.h>
#include <nuttx/semaphore.h>

#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdio.h>
#include <sys/types.h>

#include "ipcc_priv.h"

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

/****************************************************************************
 * Name: ipcc_txfree_notify
 *
 * Description:
 *   Notifies all blocked threads or those waiting in poll/select that
 *   there is place on buffer to perform writing.
 *
 * Input Parameters:
 *   ipcc - pointer to driver instance
 *
 * Returned Value:
 *   None
 *
 * Assumptions/Limitations:
 *   This function can be called from interrupt handler from lower half.
 *
 ****************************************************************************/

void ipcc_txfree_notify(FAR struct ipcc_driver_s *priv)
{
  int semval;

  if (priv == NULL)
    {
      /* priv can be NULL when ipcc lower half is initialized but
       * upper half has not yet been initialized, and tx interrupt
       * has been received. In such case we don't wake any writers,
       * because since ipcc is not yet initialized there cannot be
       * any writers yet. We can safely return here, first write()
       * to this ipcc channel will immediately write data.
       */

      return;
    }

  if ((nxsem_get_value(&priv->txsem, &semval) == 0) && semval > 0)
    {
      /* Notify all poll/select waiters that they can write to the driver.
       * Do it only when there are no already blocked writers to avoid
       * situation where thread that is polling gets notified only to
       * be blocked in write() because another thread have written to
       * buffer before polling thread could.
       */

      ipcc_pollnotify(priv, POLLOUT);
      return;
    }

  /* Notify all blocked writers that data is available to write */

  do
    {
      nxsem_post(&priv->txsem);
    }
  while (nxsem_get_value(&priv->txsem, &semval) == 0 && semval <= 0);
}

/****************************************************************************
 * Name: ipcc_write
 *
 * Description:
 *   Writes data to IPCC memory so that another CPU can read the contents.
 *   Will block untill whole buffer is copied unless signal is received
 *   or O_NONBLOCK flag is set.
 *
 * Input Parameters:
 *   filep - file on vfs associated with the driver
 *   buffer - buffer to copy to IPCC memory
 *   buflen - size of the buffer... buffer
 *
 * Returned Value:
 *   Number of successfully written bytes into the IPCC memory or netagted
 *   errno when no data could be written.
 *
 * Assumptions/Limitations:
 *
 ****************************************************************************/

ssize_t ipcc_write(FAR struct file *filep, FAR const char *buffer,
                         size_t buflen)
{
  FAR struct ipcc_driver_s *priv;
  ssize_t nwritten;
  int ret;
  int flags;

  /* Get our private data structure */

  DEBUGASSERT(filep != NULL && filep->f_inode != NULL);
  priv = filep->f_inode->i_private;

  /* Get exclusive access to driver */

  if ((ret = nxmutex_lock(&priv->lock)))
    {
      /* nxsem_wait() will return on signal, we did not start
       * any transfer yet, so we can safely return with error
       */

      return ret;
    }

  flags = enter_critical_section();

  for (nwritten = 0; ; )
    {
#ifdef CONFIG_IPCC_BUFFERED
      /* Buffered write, if buffer is empty try to write directly to
       * IPCC memory, else buffer data in circbuf - it will be written
       * in interrupt handler when IPCC tx channel is free.
       */

      if (circbuf_used(&priv->ipcc->txbuf) == 0)
        {
          /* Write buffer is empty, we can try and write data directly
           * to IPCC memory thus omitting copying to buffer.
           */

          nwritten += priv->ipcc->ops.write(priv->ipcc, buffer + nwritten,
                                           buflen - nwritten);
          if (nwritten == (ssize_t)buflen || nwritten < 0)
            {
              /* We've managed to write whole buffer to IPCC memory,
               * there is nothing else for use to do
               *   --or--
               * lower half driver returned error during write,
               *
               * either way we return with nwritten which will either
               * be number of bytes written or negated errno.
               */

              nxmutex_unlock(&priv->lock);
              leave_critical_section(flags);
              return nwritten;
            }
        }

      /* Either, there is already some data on the txbuffer, which
       * means IPCC is occupied, or txbuffer is empty, but we could
       * not write whole buffer to IPCC memory. In either case we
       * copy what is left in data to buffer.
       */

      nwritten += circbuf_write(&priv->ipcc->txbuf, buffer + nwritten,
                                (ssize_t)buflen - nwritten);

      /* Notify lower half that new data on circ buffer is available */

      priv->ipcc->ops.write_notify(priv->ipcc);

      if (nwritten == (ssize_t)buflen)
        {
          /* All outstanding data has been copied to txbuffer, we're done */

          nxmutex_unlock(&priv->lock);
          leave_critical_section(flags);
          return nwritten;
        }

#else /* CONFIG_IPCC_BUFFERED */
      /* Unbuffered write, write data directly to lower driver */

      nwritten += priv->ipcc->ops.write(priv->ipcc, buffer + nwritten,
                                        buflen - nwritten);
      if (nwritten == (ssize_t)buflen)
        {
          /* We've managed to write whole buffer to IPCC memory,
           * there is nothing else for use to do
           *   --or--
           * lower half driver returned error during write,
           *
           * either way we return with nwritten which will either
           * be number of bytes written or negated errno.
           */

          nxmutex_unlock(&priv->lock);
          leave_critical_section(flags);
          return nwritten;
        }

#endif /* CONFIG_IPCC_BUFFERED */

      /* No space left on buffer, should we block? */

      if (filep->f_oflags & O_NONBLOCK)
        {
          /* No, we should not block, return number of bytes written or
           * -EAGAIN when we did not write anything
           */

          nxmutex_unlock(&priv->lock);
          leave_critical_section(flags);
          return nwritten ? nwritten : -EAGAIN;
        }

      /* We are in blocking mode, so we have to wait for space
       * to write data
       */

      nxmutex_unlock(&priv->lock);

      if ((ret = nxsem_wait(&priv->txsem)))
        {
          leave_critical_section(flags);

          /* We were interrupted by signal, return error or number
           * of bytes written
           */

          return nwritten ? nwritten : -EINTR;
        }

      /* Space should now be available, but it's possible that
       * another thread will write data to txbuffer before
       * we can do it, so we will stay in the loop until we
       * manage to write whole buffer - or interrupt signal occurs.
       *
       * We have released exclusive lock to driver when we were
       * waiting for data, so now let's retake it.
       */

      nxmutex_lock(&priv->lock);
    }

  leave_critical_section(flags);
}