incubator-nuttx/drivers/ipcc/ipcc_write.c

269 lines
8.4 KiB
C

/****************************************************************************
* 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);
}