| /**************************************************************************** |
| * 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 <stdint.h> |
| #include <stdbool.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 = (uint8_t*)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 uint8_t *start = (uint8_t*)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:", (uint8_t*)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, |
| bool 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 */ |