diff options
Diffstat (limited to 'nuttx/drivers/rwbuffer.c')
-rw-r--r-- | nuttx/drivers/rwbuffer.c | 682 |
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 */ + |