summaryrefslogtreecommitdiff
path: root/nuttx/drivers/pipes/pipe_common.c
diff options
context:
space:
mode:
Diffstat (limited to 'nuttx/drivers/pipes/pipe_common.c')
-rw-r--r--nuttx/drivers/pipes/pipe_common.c654
1 files changed, 654 insertions, 0 deletions
diff --git a/nuttx/drivers/pipes/pipe_common.c b/nuttx/drivers/pipes/pipe_common.c
new file mode 100644
index 000000000..60d028c00
--- /dev/null
+++ b/nuttx/drivers/pipes/pipe_common.c
@@ -0,0 +1,654 @@
+/****************************************************************************
+ * drivers/pipes/pipe_common.c
+ *
+ * Copyright (C) 2008-2009 Gregory Nutt. All rights reserved.
+ * Author: Gregory Nutt <spudmonkey@racsa.co.cr>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * 3. Neither the name NuttX nor the names of its contributors may be
+ * used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+#include <sys/types.h>
+
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sched.h>
+#include <semaphore.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <assert.h>
+#include <debug.h>
+
+#include <nuttx/fs.h>
+#if CONFIG_DEBUG
+# include <nuttx/arch.h>
+#endif
+
+#include "pipe_common.h"
+
+#if CONFIG_DEV_PIPE_SIZE > 0
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* CONFIG_DEV_PIPEDUMP will dump the contents of each transfer into and out
+ * of the pipe.
+ */
+
+#ifdef CONFIG_DEV_PIPEDUMP
+# define pipe_dumpbuffer(m,a,n) lib_dumpbuffer(m,a,n)
+#else
+# define pipe_dumpbuffer(m,a,n)
+#endif
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+static void pipecommon_semtake(sem_t *sem);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: pipecommon_semtake
+ ****************************************************************************/
+
+static void pipecommon_semtake(sem_t *sem)
+{
+ while (sem_wait(sem) != 0)
+ {
+ /* The only case that an error should occur here is if the wait was
+ * awakened by a signal.
+ */
+
+ ASSERT(errno == EINTR);
+ }
+}
+
+/****************************************************************************
+ * Name: pipecommon_pollnotify
+ ****************************************************************************/
+
+#ifndef CONFIG_DISABLE_POLL
+static void pipecommon_pollnotify(FAR struct pipe_dev_s *dev, pollevent_t eventset)
+{
+ int i;
+
+ for (i = 0; i < CONFIG_DEV_PIPE_NPOLLWAITERS; i++)
+ {
+ struct pollfd *fds = dev->d_fds[i];
+ if (fds)
+ {
+ fds->revents |= (fds->events & eventset);
+ if (fds->revents != 0)
+ {
+ fvdbg("Report events: %02x\n", fds->revents);
+ sem_post(fds->sem);
+ }
+ }
+ }
+}
+#else
+# define pipecommon_pollnotify(dev,event)
+#endif
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: pipecommon_allocdev
+ ****************************************************************************/
+
+FAR struct pipe_dev_s *pipecommon_allocdev(void)
+{
+ struct pipe_dev_s *dev;
+
+ /* Allocate a private structure to manage the pipe */
+
+ dev = (struct pipe_dev_s *)malloc(sizeof(struct pipe_dev_s));
+ if (dev)
+ {
+ /* Initialize the private structure */
+
+ memset(dev, 0, sizeof(struct pipe_dev_s));
+ sem_init(&dev->d_bfsem, 0, 1);
+ sem_init(&dev->d_rdsem, 0, 0);
+ sem_init(&dev->d_wrsem, 0, 0);
+ }
+ return dev;
+}
+
+/****************************************************************************
+ * Name: pipecommon_freedev
+ ****************************************************************************/
+
+void pipecommon_freedev(FAR struct pipe_dev_s *dev)
+{
+ sem_destroy(&dev->d_bfsem);
+ sem_destroy(&dev->d_rdsem);
+ sem_destroy(&dev->d_wrsem);
+ free(dev);
+}
+
+/****************************************************************************
+ * Name: pipecommon_open
+ ****************************************************************************/
+
+int pipecommon_open(FAR struct file *filep)
+{
+ struct inode *inode = filep->f_inode;
+ struct pipe_dev_s *dev = inode->i_private;
+ int sval;
+
+ /* Some sanity checking */
+#if CONFIG_DEBUG
+ if (!dev)
+ {
+ return -EBADF;
+ }
+#endif
+ /* Make sure that we have exclusive access to the device structure */
+
+ if (sem_wait(&dev->d_bfsem) == 0)
+ {
+ /* If this the first reference on the device, then allocate the buffer */
+
+ if (dev->d_refs == 0)
+ {
+ dev->d_buffer = (ubyte*)malloc(CONFIG_DEV_PIPE_SIZE);
+ if (!dev->d_buffer)
+ {
+ (void)sem_post(&dev->d_bfsem);
+ return -ENOMEM;
+ }
+ }
+
+ /* Increment the reference count on the pipe instance */
+
+ dev->d_refs++;
+
+ /* If opened for writing, increment the count of writers on on the pipe instance */
+
+ if ((filep->f_oflags & O_WROK) != 0)
+ {
+ dev->d_nwriters++;
+
+ /* If this this is the first writer, then the read semaphore indicates the
+ * number of readers waiting for the first writer. Wake them all up.
+ */
+
+ if (dev->d_nwriters == 1)
+ {
+ while (sem_getvalue(&dev->d_rdsem, &sval) == 0 && sval < 0)
+ {
+ sem_post(&dev->d_rdsem);
+ }
+ }
+ }
+
+ /* If opened for read-only, then wait for at least one writer on the pipe */
+
+ sched_lock();
+ (void)sem_post(&dev->d_bfsem);
+ if ((filep->f_oflags & O_RDWR) == O_RDONLY && dev->d_nwriters < 1)
+ {
+ /* NOTE: d_rdsem is normally used when the read logic waits for more
+ * data to be written. But until the first writer has opened the
+ * pipe, the meaning is different: it is used prevent O_RDONLY open
+ * calls from returning until there is at least one writer on the pipe.
+ * This is required both by spec and also because it prevents
+ * subsequent read() calls from returning end-of-file because there is
+ * no writer on the pipe.
+ */
+
+ pipecommon_semtake(&dev->d_rdsem);
+ }
+ sched_unlock();
+ return OK;
+ }
+ return ERROR;
+}
+
+/****************************************************************************
+ * Name: pipecommon_close
+ ****************************************************************************/
+
+int pipecommon_close(FAR struct file *filep)
+{
+ struct inode *inode = filep->f_inode;
+ struct pipe_dev_s *dev = inode->i_private;
+ int sval;
+
+ /* Some sanity checking */
+#if CONFIG_DEBUG
+ if (!dev)
+ {
+ return -EBADF;
+ }
+#endif
+
+ /* Make sure that we have exclusive access to the device structure.
+ * NOTE: close() is supposed to return EINTR if interrupted, however
+ * I've never seen anyone check that.
+ */
+
+ pipecommon_semtake(&dev->d_bfsem);
+
+ /* Check if the decremented reference count would go to zero */
+
+ if (dev->d_refs > 1)
+ {
+ /* No.. then just decrement the reference count */
+
+ dev->d_refs--;
+
+ /* If opened for writing, decrement the count of writers on on the pipe instance */
+
+ if ((filep->f_oflags & O_WROK) != 0)
+ {
+ /* If there are no longer any writers on the pipe, then notify all of the
+ * waiting readers that they must return end-of-file.
+ */
+
+ if (--dev->d_nwriters <= 0)
+ {
+ while (sem_getvalue(&dev->d_rdsem, &sval) == 0 && sval < 0)
+ {
+ sem_post(&dev->d_rdsem);
+ }
+ }
+ }
+ }
+ else
+ {
+ /* Yes... deallocate the buffer */
+
+ free(dev->d_buffer);
+ dev->d_buffer = NULL;
+
+ /* And reset all counts and indices */
+
+ dev->d_wrndx = 0;
+ dev->d_rdndx = 0;
+ dev->d_refs = 0;
+ dev->d_nwriters = 0;
+ }
+
+ sem_post(&dev->d_bfsem);
+ return OK;
+}
+
+/****************************************************************************
+ * Name: pipecommon_read
+ ****************************************************************************/
+
+ssize_t pipecommon_read(FAR struct file *filep, FAR char *buffer, size_t len)
+{
+ struct inode *inode = filep->f_inode;
+ struct pipe_dev_s *dev = inode->i_private;
+#ifdef CONFIG_DEV_PIPEDUMP
+ FAR ubyte *start = (ubyte*)buffer;
+#endif
+ ssize_t nread = 0;
+ int sval;
+ int ret;
+
+ /* Some sanity checking */
+#if CONFIG_DEBUG
+ if (!dev)
+ {
+ return -ENODEV;
+ }
+#endif
+
+ /* Make sure that we have exclusive access to the device structure */
+
+ if (sem_wait(&dev->d_bfsem) < 0)
+ {
+ return ERROR;
+ }
+
+ /* If the pipe is empty, then wait for something to be written to it */
+
+ while (dev->d_wrndx == dev->d_rdndx)
+ {
+ /* If O_NONBLOCK was set, then return EGAIN */
+
+ if (filep->f_oflags & O_NONBLOCK)
+ {
+ sem_post(&dev->d_bfsem);
+ return -EAGAIN;
+ }
+
+ /* If there are no writers on the pipe, then return end of file */
+
+ if (dev->d_nwriters <= 0)
+ {
+ sem_post(&dev->d_bfsem);
+ return 0;
+ }
+
+ /* Otherwise, wait for something to be written to the pipe */
+
+ sched_lock();
+ sem_post(&dev->d_bfsem);
+ ret = sem_wait(&dev->d_rdsem);
+ sched_unlock();
+
+ if (ret < 0 || sem_wait(&dev->d_bfsem) < 0)
+ {
+ return ERROR;
+ }
+ }
+
+ /* Then return whatever is available in the pipe (which is at least one byte) */
+
+ nread = 0;
+ while (nread < len && dev->d_wrndx != dev->d_rdndx)
+ {
+ *buffer++ = dev->d_buffer[dev->d_rdndx];
+ if (++dev->d_rdndx >= CONFIG_DEV_PIPE_SIZE)
+ {
+ dev->d_rdndx = 0;
+ }
+ nread++;
+ }
+
+ /* Notify all waiting writers that bytes have been removed from the buffer */
+
+ while (sem_getvalue(&dev->d_wrsem, &sval) == 0 && sval < 0)
+ {
+ sem_post(&dev->d_wrsem);
+ }
+
+ /* Notify all poll/select waiters that they can write to the FIFO */
+
+ pipecommon_pollnotify(dev, POLLOUT);
+
+ sem_post(&dev->d_bfsem);
+ pipe_dumpbuffer("From PIPE:", start, nread);
+ return nread;
+}
+
+/****************************************************************************
+ * Name: pipecommon_write
+ ****************************************************************************/
+
+ssize_t pipecommon_write(FAR struct file *filep, FAR const char *buffer, size_t len)
+{
+ struct inode *inode = filep->f_inode;
+ struct pipe_dev_s *dev = inode->i_private;
+ ssize_t nwritten = 0;
+ ssize_t last;
+ int nxtwrndx;
+ int sval;
+
+ /* Some sanity checking */
+
+#if CONFIG_DEBUG
+ if (!dev)
+ {
+ return -ENODEV;
+ }
+#endif
+ pipe_dumpbuffer("To PIPE:", (ubyte*)buffer, len);
+
+ /* At present, this method cannot be called from interrupt handlers. That is
+ * because it calls sem_wait (via pipecommon_semtake below) and sem_wait cannot
+ * be called from interrupt level. This actually happens fairly commonly
+ * IF dbg() is called from interrupt handlers and stdout is being redirected
+ * via a pipe. In that case, the debug output will try to go out the pipe
+ * (interrupt handlers should use the lldbg() APIs).
+ *
+ * On the other hand, it would be very valuable to be able to feed the pipe
+ * from an interrupt handler! TODO: Consider disabling interrupts instead
+ * of taking semaphores so that pipes can be written from interupt handlers
+ */
+
+ DEBUGASSERT(up_interrupt_context() == FALSE)
+
+ /* Make sure that we have exclusive access to the device structure */
+
+ if (sem_wait(&dev->d_bfsem) < 0)
+ {
+ return ERROR;
+ }
+
+ /* Loop until all of the bytes have been written */
+
+ last = 0;
+ for (;;)
+ {
+ /* Calculate the write index AFTER the next byte is written */
+
+ nxtwrndx = dev->d_wrndx + 1;
+ if (nxtwrndx >= CONFIG_DEV_PIPE_SIZE)
+ {
+ nxtwrndx = 0;
+ }
+
+ /* Would the next write overflow the circular buffer? */
+
+ if (nxtwrndx != dev->d_rdndx)
+ {
+ /* No... copy the byte */
+
+ dev->d_buffer[dev->d_wrndx] = *buffer++;
+ dev->d_wrndx = nxtwrndx;
+
+ /* Is the write complete? */
+
+ if (++nwritten >= len)
+ {
+ /* Yes.. Notify all of the waiting readers that more data is available */
+
+ while (sem_getvalue(&dev->d_rdsem, &sval) == 0 && sval < 0)
+ {
+ sem_post(&dev->d_rdsem);
+ }
+
+ /* Notify all poll/select waiters that they can write to the FIFO */
+
+ pipecommon_pollnotify(dev, POLLIN);
+
+ /* Return the number of bytes written */
+
+ sem_post(&dev->d_bfsem);
+ return len;
+ }
+ }
+ else
+ {
+ /* There is not enough room for the next byte. Was anything written in this pass? */
+
+ if (last < nwritten)
+ {
+ /* Yes.. Notify all of the waiting readers that more data is available */
+
+ while (sem_getvalue(&dev->d_rdsem, &sval) == 0 && sval < 0)
+ {
+ sem_post(&dev->d_rdsem);
+ }
+ }
+ last = nwritten;
+
+ /* If O_NONBLOCK was set, then return partial bytes written or EGAIN */
+
+ if (filep->f_oflags & O_NONBLOCK)
+ {
+ if (nwritten == 0)
+ {
+ nwritten = -EAGAIN;
+ }
+ sem_post(&dev->d_bfsem);
+ return nwritten;
+ }
+
+ /* There is more to be written.. wait for data to be removed from the pipe */
+
+ sched_lock();
+ sem_post(&dev->d_bfsem);
+ pipecommon_semtake(&dev->d_wrsem);
+ sched_unlock();
+ pipecommon_semtake(&dev->d_bfsem);
+ }
+ }
+}
+
+/****************************************************************************
+ * Name: pipecommon_poll
+ ****************************************************************************/
+
+#ifndef CONFIG_DISABLE_POLL
+int pipecommon_poll(FAR struct file *filep, FAR struct pollfd *fds,
+ boolean setup)
+{
+ FAR struct inode *inode = filep->f_inode;
+ FAR struct pipe_dev_s *dev = inode->i_private;
+ pollevent_t eventset;
+ pipe_ndx_t nbytes;
+ int ret = OK;
+ int i;
+
+ /* Some sanity checking */
+
+#if CONFIG_DEBUG
+ if (!dev || !fds)
+ {
+ return -ENODEV;
+ }
+#endif
+
+ /* Are we setting up the poll? Or tearing it down? */
+
+ pipecommon_semtake(&dev->d_bfsem);
+ 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_DEV_PIPE_NPOLLWAITERS; i++)
+ {
+ /* Find an available slot */
+
+ if (!dev->d_fds[i])
+ {
+ /* Bind the poll structure and this slot */
+
+ dev->d_fds[i] = fds;
+ fds->priv = &dev->d_fds[i];
+ break;
+ }
+ }
+
+ if (i >= CONFIG_DEV_PIPE_NPOLLWAITERS)
+ {
+ fds->priv = NULL;
+ ret = -EBUSY;
+ goto errout;
+ }
+
+ /* Should immediately notify on any of the requested events?
+ * First, determine how many bytes are in the buffer
+ */
+
+ if (dev->d_wrndx >= dev->d_rdndx)
+ {
+ nbytes = dev->d_wrndx - dev->d_rdndx;
+ }
+ else
+ {
+ nbytes = (CONFIG_DEV_PIPE_SIZE-1) + dev->d_wrndx - dev->d_rdndx;
+ }
+
+ /* Notify the POLLOUT event if the pipe is not full */
+
+ eventset = 0;
+ if (nbytes < (CONFIG_DEV_PIPE_SIZE-1))
+ {
+ eventset |= POLLOUT;
+ }
+
+ /* Notify the POLLIN event if the pipe is not empty */
+
+ if (nbytes > 0)
+ {
+ eventset |= POLLIN;
+ }
+
+ if (eventset)
+ {
+ pipecommon_pollnotify(dev, eventset);
+ }
+ }
+ else
+ {
+ /* This is a request to tear down the poll. */
+
+ struct pollfd **slot = (struct pollfd **)fds->priv;
+
+#ifdef CONFIG_DEBUG
+ if (!slot)
+ {
+ ret = -EIO;
+ goto errout;
+ }
+#endif
+
+ /* Remove all memory of the poll setup */
+
+ *slot = NULL;
+ fds->priv = NULL;
+ }
+
+errout:
+ sem_post(&dev->d_bfsem);
+ return ret;
+}
+#endif
+
+#endif /* CONFIG_DEV_PIPE_SIZE > 0 */