nuttx/drivers/ipcc/ipcc_write.c
Michał Łyszczek 4e967c67b4 stm32wl5: fix unbuffered mode and other possible bugs
This patch fixes unbuffered mode so it actually works.

Also, patch contains fixes for possible bugs that could result in
deadlock or system hang in certain situations.

Signed-off-by: Michał Łyszczek <michal.lyszczek@bofc.pl>
2022-08-18 11:46:38 +08:00

264 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 = nxsem_wait(&priv->exclsem)))
{
/* nxsem_wait() will return on signal, we did not start
* any transfer yet, so we can safely return with error
*/
return ret;
}
for (nwritten = 0; ; )
{
flags = enter_critical_section();
#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.
*/
nxsem_post(&priv->exclsem);
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 */
nxsem_post(&priv->exclsem);
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.
*/
nxsem_post(&priv->exclsem);
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
*/
nxsem_post(&priv->exclsem);
leave_critical_section(flags);
return nwritten ? nwritten : -EAGAIN;
}
/* We are in blocking mode, so we have to wait for space
* to write data
*/
nxsem_post(&priv->exclsem);
if ((ret = nxsem_wait(&priv->txsem)))
{
/* 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.
*/
nxsem_wait(&priv->exclsem);
continue;
}
}