aboutsummaryrefslogtreecommitdiff
path: root/nuttx/drivers/rwbuffer.c
diff options
context:
space:
mode:
Diffstat (limited to 'nuttx/drivers/rwbuffer.c')
-rw-r--r--nuttx/drivers/rwbuffer.c682
1 files changed, 682 insertions, 0 deletions
diff --git a/nuttx/drivers/rwbuffer.c b/nuttx/drivers/rwbuffer.c
new file mode 100644
index 000000000..1d924e2a0
--- /dev/null
+++ b/nuttx/drivers/rwbuffer.c
@@ -0,0 +1,682 @@
+/****************************************************************************
+ * drivers/rwbuffer.c
+ *
+ * Copyright (C) 2009, 2011 Gregory Nutt. All rights reserved.
+ * Author: Gregory Nutt <gnutt@nuttx.org>
+ *
+ * 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 <stdint.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <assert.h>
+#include <semaphore.h>
+#include <errno.h>
+#include <debug.h>
+
+#include <nuttx/kmalloc.h>
+#include <nuttx/wqueue.h>
+#include <nuttx/rwbuffer.h>
+
+#if defined(CONFIG_FS_WRITEBUFFER) || defined(CONFIG_FS_READAHEAD)
+
+/****************************************************************************
+ * Preprocessor Definitions
+ ****************************************************************************/
+
+/* Configuration ************************************************************/
+
+#ifndef CONFIG_SCHED_WORKQUEUE
+# error "Worker thread support is required (CONFIG_SCHED_WORKQUEUE)"
+#endif
+
+#ifndef CONFIG_FS_WRDELAY
+# define CONFIG_FS_WRDELAY 350
+#endif
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Variables
+ ****************************************************************************/
+
+/****************************************************************************
+ * Public Variables
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: rwb_semtake
+ ****************************************************************************/
+
+static void rwb_semtake(sem_t *sem)
+{
+ /* Take the semaphore (perhaps waiting) */
+
+ while (sem_wait(sem) != 0)
+ {
+ /* The only case that an error should occr here is if
+ * the wait was awakened by a signal.
+ */
+
+ ASSERT(errno == EINTR);
+ }
+}
+
+/****************************************************************************
+ * Name: rwb_semgive
+ ****************************************************************************/
+
+#define rwb_semgive(s) sem_post(s)
+
+/****************************************************************************
+ * Name: rwb_overlap
+ ****************************************************************************/
+
+static inline bool rwb_overlap(off_t blockstart1, size_t nblocks1,
+ off_t blockstart2, size_t nblocks2)
+{
+ off_t blockend1 = blockstart1 + nblocks1;
+ off_t blockend2 = blockstart2 + nblocks2;
+
+ /* If the buffer 1 is wholly outside of buffer 2, return false */
+
+ if ((blockend1 < blockstart2) || /* Wholly "below" */
+ (blockstart1 > blockend2)) /* Wholly "above" */
+ {
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+}
+
+/****************************************************************************
+ * Name: rwb_resetwrbuffer
+ ****************************************************************************/
+
+#ifdef CONFIG_FS_WRITEBUFFER
+static inline void rwb_resetwrbuffer(struct rwbuffer_s *rwb)
+{
+ /* We assume that the caller holds the wrsem */
+
+ rwb->wrnblocks = 0;
+ rwb->wrblockstart = (off_t)-1;
+ rwb->wrexpectedblock = (off_t)-1;
+}
+#endif
+
+/****************************************************************************
+ * Name: rwb_wrflush
+ ****************************************************************************/
+
+#ifdef CONFIG_FS_WRITEBUFFER
+static void rwb_wrflush(struct rwbuffer_s *rwb)
+{
+ int ret;
+
+ fvdbg("Timeout!\n");
+
+ rwb_semtake(&rwb->wrsem);
+ if (rwb->wrnblocks)
+ {
+ fvdbg("Flushing: blockstart=0x%08lx nblocks=%d from buffer=%p\n",
+ (long)rwb->wrblockstart, rwb->wrnblocks, rwb->wrbuffer);
+
+ /* Flush cache. On success, the flush method will return the number
+ * of blocks written. Anything other than the number requested is
+ * an error.
+ */
+
+ ret = rwb->wrflush(rwb->dev, rwb->wrbuffer, rwb->wrblockstart, rwb->wrnblocks);
+ if (ret != rwb->wrnblocks)
+ {
+ fdbg("ERROR: Error flushing write buffer: %d\n", ret);
+ }
+
+ rwb_resetwrbuffer(rwb);
+ }
+
+ rwb_semgive(&rwb->wrsem);
+}
+#endif
+
+/****************************************************************************
+ * Name: rwb_wrtimeout
+ ****************************************************************************/
+
+static void rwb_wrtimeout(FAR void *arg)
+{
+ /* The following assumes that the size of a pointer is 4-bytes or less */
+
+ FAR struct rwbuffer_s *rwb = (struct rwbuffer_s *)arg;
+ DEBUGASSERT(rwb != NULL);
+
+ /* If a timeout elpases with with write buffer activity, this watchdog
+ * handler function will be evoked on the thread of execution of the
+ * worker thread.
+ */
+
+ rwb_wrflush(rwb);
+}
+
+/****************************************************************************
+ * Name: rwb_wrstarttimeout
+ ****************************************************************************/
+
+static void rwb_wrstarttimeout(FAR struct rwbuffer_s *rwb)
+{
+ /* CONFIG_FS_WRDELAY provides the delay period in milliseconds. CLK_TCK
+ * provides the clock tick of the system (frequency in Hz).
+ */
+
+ int ticks = (CONFIG_FS_WRDELAY + CLK_TCK/2) / CLK_TCK;
+ (void)work_queue(&rwb->work, rwb_wrtimeout, (FAR void *)rwb, ticks);
+}
+
+/****************************************************************************
+ * Name: rwb_wrcanceltimeout
+ ****************************************************************************/
+
+static inline void rwb_wrcanceltimeout(struct rwbuffer_s *rwb)
+{
+ (void)work_cancel(&rwb->work);
+}
+
+/****************************************************************************
+ * Name: rwb_writebuffer
+ ****************************************************************************/
+
+#ifdef CONFIG_FS_WRITEBUFFER
+static ssize_t rwb_writebuffer(FAR struct rwbuffer_s *rwb,
+ off_t startblock, uint32_t nblocks,
+ FAR const uint8_t *wrbuffer)
+{
+ int ret;
+
+ /* Write writebuffer Logic */
+
+ rwb_wrcanceltimeout(rwb);
+
+ /* First: Should we flush out our cache? We would do that if (1) we already
+ * buffering blocks and the next block writing is not in the same sequence,
+ * or (2) the number of blocks would exceed our allocated buffer capacity
+ */
+
+ if (((startblock != rwb->wrexpectedblock) && (rwb->wrnblocks)) ||
+ ((rwb->wrnblocks + nblocks) > rwb->wrmaxblocks))
+ {
+ fvdbg("writebuffer miss, expected: %08x, given: %08x\n",
+ rwb->wrexpectedblock, startblock);
+
+ /* Flush the write buffer */
+
+ ret = rwb->wrflush(rwb, rwb->wrbuffer, rwb->wrblockstart, rwb->wrnblocks);
+ if (ret < 0)
+ {
+ fdbg("ERROR: Error writing multiple from cache: %d\n", -ret);
+ return ret;
+ }
+
+ rwb_resetwrbuffer(rwb);
+ }
+
+ /* writebuffer is empty? Then initialize it */
+
+ if (!rwb->wrnblocks)
+ {
+ fvdbg("Fresh cache starting at block: 0x%08x\n", startblock);
+ rwb->wrblockstart = startblock;
+ }
+
+ /* Add data to cache */
+
+ fvdbg("writebuffer: copying %d bytes from %p to %p\n",
+ nblocks * wrb->blocksize, wrbuffer,
+ &rwb->wrbuffer[rwb->wrnblocks * rwb->blocksize]);
+ memcpy(&rwb->wrbuffer[rwb->wrnblocks * rwb->blocksize],
+ wrbuffer, nblocks * rwb->blocksize);
+
+ rwb->wrnblocks += nblocks;
+ rwb->wrexpectedblock = rwb->wrblockstart + rwb->wrnblocks;
+ rwb_wrstarttimeout(rwb);
+ return nblocks;
+}
+#endif
+
+/****************************************************************************
+ * Name: rwb_resetrhbuffer
+ ****************************************************************************/
+
+#ifdef CONFIG_FS_READAHEAD
+static inline void rwb_resetrhbuffer(struct rwbuffer_s *rwb)
+{
+ /* We assume that the caller holds the readAheadBufferSemphore */
+
+ rwb->rhnblocks = 0;
+ rwb->rhblockstart = (off_t)-1;
+}
+#endif
+
+/****************************************************************************
+ * Name: rwb_bufferread
+ ****************************************************************************/
+
+#ifdef CONFIG_FS_READAHEAD
+static inline void
+rwb_bufferread(struct rwbuffer_s *rwb, off_t startblock,
+ size_t nblocks, uint8_t **rdbuffer)
+{
+ /* We assume that (1) the caller holds the readAheadBufferSemphore, and (2)
+ * that the caller already knows that all of the blocks are in the
+ * read-ahead buffer.
+ */
+
+ /* Convert the units from blocks to bytes */
+
+ off_t blockoffset = startblock - rwb->rhblockstart;
+ off_t byteoffset = rwb->blocksize * blockoffset;
+ size_t nbytes = rwb->blocksize * nblocks;
+
+ /* Get the byte address in the read-ahead buffer */
+
+ uint8_t *rhbuffer = rwb->rhbuffer + byteoffset;
+
+ /* Copy the data from the read-ahead buffer into the IO buffer */
+
+ memcpy(*rdbuffer, rhbuffer, nbytes);
+
+ /* Update the caller's copy for the next address */
+
+ *rdbuffer += nbytes;
+}
+#endif
+
+/****************************************************************************
+ * Name: rwb_rhreload
+ ****************************************************************************/
+
+#ifdef CONFIG_FS_READAHEAD
+static int rwb_rhreload(struct rwbuffer_s *rwb, off_t startblock)
+{
+ /* Get the block number +1 of the last block that will fit in the
+ * read-ahead buffer
+ */
+
+ off_t endblock = startblock + rwb->rhmaxblocks;
+ size_t nblocks;
+ int ret;
+
+ /* Reset the read buffer */
+
+ rwb_resetrhbuffer(rwb);
+
+ /* Make sure that we don't read past the end of the device */
+
+ if (endblock > rwb->nblocks)
+ {
+ endblock = rwb->nblocks;
+ }
+
+ nblocks = endblock - startblock;
+
+ /* Now perform the read */
+
+ ret = rwb->rhreload(rwb->dev, rwb->rhbuffer, startblock, nblocks);
+ if (ret == nblocks)
+ {
+ /* Update information about what is in the read-ahead buffer */
+
+ rwb->rhnblocks = nblocks;
+ rwb->rhblockstart = startblock;
+
+ /* The return value is not the number of blocks we asked to be loaded. */
+
+ return nblocks;
+ }
+
+ return -EIO;
+}
+#endif
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+/****************************************************************************
+ * Name: rwb_initialize
+ ****************************************************************************/
+
+int rwb_initialize(FAR struct rwbuffer_s *rwb)
+{
+ uint32_t allocsize;
+
+ /* Sanity checking */
+
+ DEBUGASSERT(rwb != NULL);
+ DEBUGASSERT(rwb->blocksize > 0);
+ DEBUGASSERT(rwb->nblocks > 0);
+ DEBUGASSERT(rwb->dev != NULL);
+
+ /* Setup so that rwb_uninitialize can handle a failure */
+
+#ifdef CONFIG_FS_WRITEBUFFER
+ DEBUGASSERT(rwb->wrflush!= NULL);
+ rwb->wrbuffer = NULL;
+#endif
+#ifdef CONFIG_FS_READAHEAD
+ DEBUGASSERT(rwb->rhreload != NULL);
+ rwb->rhbuffer = NULL;
+#endif
+
+#ifdef CONFIG_FS_WRITEBUFFER
+ fvdbg("Initialize the write buffer\n");
+
+ /* Initialize the write buffer access semaphore */
+
+ sem_init(&rwb->wrsem, 0, 1);
+
+ /* Initialize write buffer parameters */
+
+ rwb_resetwrbuffer(rwb);
+
+ /* Allocate the write buffer */
+
+ rwb->wrbuffer = NULL;
+ if (rwb->wrmaxblocks > 0)
+ {
+ allocsize = rwb->wrmaxblocks * rwb->blocksize;
+ rwb->wrbuffer = kmalloc(allocsize);
+ if (!rwb->wrbuffer)
+ {
+ fdbg("Write buffer kmalloc(%d) failed\n", allocsizee);
+ return -ENOMEM;
+ }
+ }
+
+ fvdbg("Write buffer size: %d bytes\n", allocsize);
+#endif /* CONFIG_FS_WRITEBUFFER */
+
+#ifdef CONFIG_FS_READAHEAD
+ fvdbg("Initialize the read-ahead buffer\n");
+
+ /* Initialize the read-ahead buffer access semaphore */
+
+ sem_init(&rwb->rhsem, 0, 1);
+
+ /* Initialize read-ahead buffer parameters */
+
+ rwb_resetrhbuffer(rwb);
+
+ /* Allocate the read-ahead buffer */
+
+ rwb->rhbuffer = NULL;
+ if (rwb->rhmaxblocks > 0)
+ {
+ allocsize = rwb->rhmaxblocks * rwb->blocksize;
+ rwb->rhbuffer = kmalloc(allocsize);
+ if (!rwb->rhbuffer)
+ {
+ fdbg("Read-ahead buffer kmalloc(%d) failed\n", allocsize);
+ return -ENOMEM;
+ }
+ }
+
+ fvdbg("Read-ahead buffer size: %d bytes\n", allocsize);
+#endif /* CONFIG_FS_READAHEAD */
+ return 0;
+}
+
+/****************************************************************************
+ * Name: rwb_uninitialize
+ ****************************************************************************/
+
+void rwb_uninitialize(FAR struct rwbuffer_s *rwb)
+{
+#ifdef CONFIG_FS_WRITEBUFFER
+ rwb_wrcanceltimeout(rwb);
+ sem_destroy(&rwb->wrsem);
+ if (rwb->wrbuffer)
+ {
+ kfree(rwb->wrbuffer);
+ }
+#endif
+
+#ifdef CONFIG_FS_READAHEAD
+ sem_destroy(&rwb->rhsem);
+ if (rwb->rhbuffer)
+ {
+ kfree(rwb->rhbuffer);
+ }
+#endif
+}
+
+/****************************************************************************
+ * Name: rwb_read
+ ****************************************************************************/
+
+int rwb_read(FAR struct rwbuffer_s *rwb, off_t startblock, uint32_t nblocks,
+ FAR uint8_t *rdbuffer)
+{
+ uint32_t remaining;
+
+ fvdbg("startblock=%ld nblocks=%ld rdbuffer=%p\n",
+ (long)startblock, (long)nblocks, rdbuffer);
+
+#ifdef CONFIG_FS_WRITEBUFFER
+ /* If the new read data overlaps any part of the write buffer, then
+ * flush the write data onto the physical media before reading. We
+ * could attempt some more exotic handling -- but this simple logic
+ * is well-suited for simple streaming applications.
+ */
+
+ if (rwb->wrmaxblocks > 0)
+ {
+ /* If the write buffer overlaps the block(s) requested, then flush the
+ * write buffer.
+ */
+
+ rwb_semtake(&rwb->wrsem);
+ if (rwb_overlap(rwb->wrblockstart, rwb->wrnblocks, startblock, nblocks))
+ {
+ rwb_wrflush(rwb);
+ }
+ rwb_semgive(&rwb->wrsem);
+ }
+#endif
+
+#ifdef CONFIG_FS_READAHEAD
+ /* Loop until we have read all of the requested blocks */
+
+ rwb_semtake(&rwb->rhsem);
+ for (remaining = nblocks; remaining > 0;)
+ {
+ /* Is there anything in the read-ahead buffer? */
+
+ if (rwb->rhnblocks > 0)
+ {
+ off_t startblock = startblock;
+ size_t nbufblocks = 0;
+ off_t bufferend;
+
+ /* Loop for each block we find in the read-head buffer. Count the
+ * number of buffers that we can read from read-ahead buffer.
+ */
+
+ bufferend = rwb->rhblockstart + rwb->rhnblocks;
+
+ while ((startblock >= rwb->rhblockstart) &&
+ (startblock < bufferend) &&
+ (remaining > 0))
+ {
+ /* This is one more that we will read from the read ahead buffer */
+
+ nbufblocks++;
+
+ /* And one less that we will read from the media */
+
+ startblock++;
+ remaining--;
+ }
+
+ /* Then read the data from the read-ahead buffer */
+
+ rwb_bufferread(rwb, startblock, nbufblocks, &rdbuffer);
+ }
+
+ /* If we did not get all of the data from the buffer, then we have to refill
+ * the buffer and try again.
+ */
+
+ if (remaining > 0)
+ {
+ int ret = rwb_rhreload(rwb, startblock);
+ if (ret < 0)
+ {
+ fdbg("ERROR: Failed to fill the read-ahead buffer: %d\n", -ret);
+ return ret;
+ }
+ }
+ }
+
+ /* On success, return the number of blocks that we were requested to read.
+ * This is for compatibility with the normal return of a block driver read
+ * method
+ */
+
+ rwb_semgive(&rwb->rhsem);
+ return 0;
+#else
+ return rwb->rhreload(rwb->dev, startblock, nblocks, rdbuffer);
+#endif
+}
+
+/****************************************************************************
+ * Name: rwb_write
+ ****************************************************************************/
+
+int rwb_write(FAR struct rwbuffer_s *rwb, off_t startblock,
+ size_t nblocks, FAR const uint8_t *wrbuffer)
+{
+ int ret;
+
+#ifdef CONFIG_FS_READAHEAD
+ /* If the new write data overlaps any part of the read buffer, then
+ * flush the data from the read buffer. We could attempt some more
+ * exotic handling -- but this simple logic is well-suited for simple
+ * streaming applications.
+ */
+
+ rwb_semtake(&rwb->rhsem);
+ if (rwb_overlap(rwb->rhblockstart, rwb->rhnblocks, startblock, nblocks))
+ {
+ rwb_resetrhbuffer(rwb);
+ }
+ rwb_give(&rwb->rhsem);
+#endif
+
+#ifdef CONFIG_FS_WRITEBUFFER
+ fvdbg("startblock=%d wrbuffer=%p\n", startblock, wrbuffer);
+
+ /* Use the block cache unless the buffer size is bigger than block cache */
+
+ if (nblocks > rwb->wrmaxblocks)
+ {
+ /* First flush the cache */
+
+ rwb_semtake(&rwb->wrsem);
+ rwb_wrflush(rwb);
+ rwb_semgive(&rwb->wrsem);
+
+ /* Then transfer the data directly to the media */
+
+ ret = rwb->wrflush(rwb->dev, startblock, nblocks, wrbuffer);
+ }
+ else
+ {
+ /* Buffer the data in the write buffer */
+
+ ret = rwb_writebuffer(rwb, startblock, nblocks, wrbuffer);
+ }
+
+ /* On success, return the number of blocks that we were requested to write.
+ * This is for compatibility with the normal return of a block driver write
+ * method
+ */
+
+ return ret;
+
+#else
+
+ return rwb->wrflush(rwb->dev, startblock, nblocks, wrbuffer);
+
+#endif
+}
+
+/****************************************************************************
+ * Name: rwb_mediaremoved
+ *
+ * Description:
+ * The following function is called when media is removed
+ *
+ ****************************************************************************/
+
+int rwb_mediaremoved(FAR struct rwbuffer_s *rwb)
+{
+#ifdef CONFIG_FS_WRITEBUFFER
+ rwb_semtake(&rwb->wrsem);
+ rwb_resetwrbuffer(rwb);
+ rwb_semgive(&rwb->wrsem);
+#endif
+
+#ifdef CONFIG_FS_READAHEAD
+ rwb_semtake(&rwb->rhsem);
+ rwb_resetrhbuffer(rwb);
+ rwb_semgive(&rwb->rhsem);
+#endif
+ return 0;
+}
+
+#endif /* CONFIG_FS_WRITEBUFFER || CONFIG_FS_READAHEAD */
+