Newer
Older
patacongo
committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/****************************************************************************
* drivers/rwbuffer.c
*
* Copyright (C) 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>
patacongo
committed
#include <sys/types.h>
#include <stdint.h>
#include <stdbool.h>
patacongo
committed
#include <stdlib.h>
#include <string.h>
#include <time.h>
patacongo
committed
#include <semaphore.h>
#include <errno.h>
#include <debug.h>
patacongo
committed
#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
patacongo
committed
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
#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
****************************************************************************/
patacongo
committed
static inline bool rwb_overlap(off_t blockstart1, size_t nblocks1,
off_t blockstart2, size_t nblocks2)
patacongo
committed
{
off_t blockend1 = blockstart1 + nblocks1;
off_t blockend2 = blockstart2 + nblocks2;
patacongo
committed
/* If the buffer 1 is wholly outside of buffer 2, return false */
if ((blockend1 < blockstart2) || /* Wholly "below" */
(blockstart1 > blockend2)) /* Wholly "above" */
patacongo
committed
{
patacongo
committed
return false;
patacongo
committed
}
else
{
patacongo
committed
return true;
patacongo
committed
}
}
/****************************************************************************
* Name: rwb_resetwrbuffer
****************************************************************************/
#ifdef CONFIG_FS_WRITEBUFFER
static inline void rwb_resetwrbuffer(struct rwbuffer_s *rwb)
{
/* We assume that the caller holds the wrsem */
patacongo
committed
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;
patacongo
committed
rwb_semtake(&rwb->wrsem);
patacongo
committed
{
fvdbg("Flushing: blockstart=0x%08lx nblocks=%d from buffer=%p\n",
(long)rwb->wrblockstart, rwb->wrnblocks, rwb->wrbuffer);
patacongo
committed
patacongo
committed
/* Flush cache. On success, the flush method will return the number
* of blocks written. Anything other than the number requested is
* an error.
*/
patacongo
committed
ret = rwb->wrflush(rwb->dev, rwb->wrbuffer, rwb->wrblockstart, rwb->wrnblocks);
patacongo
committed
if (ret != rwb->wrnblocks)
patacongo
committed
fdbg("ERROR: Error flushing write buffer: %d\n", ret);
patacongo
committed
rwb_resetwrbuffer(rwb);
}
rwb_semgive(&rwb->wrsem);
}
#endif
/****************************************************************************
* Name: rwb_wrtimeout
****************************************************************************/
patacongo
committed
{
/* 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);
patacongo
committed
/* 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.
patacongo
committed
*/
patacongo
committed
}
/****************************************************************************
patacongo
committed
****************************************************************************/
static void rwb_wrstarttimeout(FAR struct rwbuffer_s *rwb)
patacongo
committed
{
/* 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);
patacongo
committed
}
/****************************************************************************
patacongo
committed
****************************************************************************/
static inline void rwb_wrcanceltimeout(struct rwbuffer_s *rwb)
patacongo
committed
{
patacongo
committed
}
/****************************************************************************
* Name: rwb_writebuffer
****************************************************************************/
#ifdef CONFIG_FS_WRITEBUFFER
static ssize_t rwb_writebuffer(FAR struct rwbuffer_s *rwb,
patacongo
committed
off_t startblock, uint32_t nblocks,
FAR const uint8_t *wrbuffer)
patacongo
committed
{
int ret;
/* Write writebuffer Logic */
patacongo
committed
/* 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
*/
patacongo
committed
if (((startblock != rwb->wrexpectedblock) && (rwb->wrnblocks)) ||
((rwb->wrnblocks + nblocks) > rwb->wrmaxblocks))
patacongo
committed
{
fvdbg("writebuffer miss, expected: %08x, given: %08x\n",
rwb->wrexpectedblock, startblock);
patacongo
committed
patacongo
committed
ret = rwb->wrflush(rwb, rwb->wrbuffer, rwb->wrblockstart, rwb->wrnblocks);
patacongo
committed
if (ret < 0)
{
fdbg("ERROR: Error writing multiple from cache: %d\n", -ret);
patacongo
committed
return ret;
patacongo
committed
rwb_resetwrbuffer(rwb);
}
/* writebuffer is empty? Then initialize it */
patacongo
committed
patacongo
committed
{
fvdbg("Fresh cache starting at block: 0x%08x\n", startblock);
patacongo
committed
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);
patacongo
committed
rwb->wrnblocks += nblocks;
rwb->wrexpectedblock = rwb->wrblockstart + rwb->wrnblocks;
patacongo
committed
}
#endif
/****************************************************************************
patacongo
committed
* Name: rwb_resetrhbuffer
patacongo
committed
****************************************************************************/
#ifdef CONFIG_FS_READAHEAD
static inline void rwb_resetrhbuffer(struct rwbuffer_s *rwb)
{
/* We assume that the caller holds the readAheadBufferSemphore */
patacongo
committed
rwb->rhblockstart = (off_t)-1;
}
#endif
/****************************************************************************
patacongo
committed
* Name: rwb_bufferread
patacongo
committed
****************************************************************************/
#ifdef CONFIG_FS_READAHEAD
static inline void
rwb_bufferread(struct rwbuffer_s *rwb, off_t startblock,
patacongo
committed
size_t nblocks, uint8_t **rdbuffer)
patacongo
committed
{
/* 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;
patacongo
committed
/* Get the byte address in the read-ahead buffer */
patacongo
committed
uint8_t *rhbuffer = rwb->rhbuffer + byteoffset;
patacongo
committed
/* Copy the data from the read-ahead buffer into the IO buffer */
memcpy(*rdbuffer, rhbuffer, nbytes);
patacongo
committed
/* Update the caller's copy for the next address */
patacongo
committed
}
#endif
/****************************************************************************
patacongo
committed
* Name: rwb_rhreload
patacongo
committed
****************************************************************************/
#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
*/
patacongo
committed
off_t endblock = startblock + rwb->rhmaxblocks;
patacongo
committed
size_t nblocks;
patacongo
committed
/* 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);
patacongo
committed
if (ret == nblocks)
patacongo
committed
{
/* Update information about what is in the read-ahead buffer */
patacongo
committed
rwb->rhblockstart = startblock;
patacongo
committed
/* The return value is not the number of blocks we asked to be loaded. */
return nblocks;
patacongo
committed
}
patacongo
committed
return -EIO;
patacongo
committed
}
#endif
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: rwb_initialize
****************************************************************************/
int rwb_initialize(FAR struct rwbuffer_s *rwb)
{
patacongo
committed
uint32_t allocsize;
patacongo
committed
/* 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");
patacongo
committed
/* 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)
patacongo
committed
{
allocsize = rwb->wrmaxblocks * rwb->blocksize;
rwb->wrbuffer = malloc(allocsize);
if (!rwb->wrbuffer)
{
fdbg("Write buffer malloc(%d) failed\n", allocsizee);
return -ENOMEM;
}
patacongo
committed
}
fvdbg("Write buffer size: %d bytes\n", allocsize);
patacongo
committed
#endif /* CONFIG_FS_WRITEBUFFER */
#ifdef CONFIG_FS_READAHEAD
fvdbg("Initialize the read-ahead buffer\n");
patacongo
committed
/* 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)
patacongo
committed
{
allocsize = rwb->rhmaxblocks * rwb->blocksize;
rwb->rhbuffer = malloc(allocsize);
if (!rwb->rhbuffer)
{
fdbg("Read-ahead buffer malloc(%d) failed\n", allocsize);
return -ENOMEM;
}
patacongo
committed
}
fvdbg("Read-ahead buffer size: %d bytes\n", allocsize);
patacongo
committed
#endif /* CONFIG_FS_READAHEAD */
return 0;
}
/****************************************************************************
* Name: rwb_uninitialize
****************************************************************************/
void rwb_uninitialize(FAR struct rwbuffer_s *rwb)
{
#ifdef CONFIG_FS_WRITEBUFFER
patacongo
committed
sem_destroy(&rwb->wrsem);
if (rwb->wrbuffer)
{
free(rwb->wrbuffer);
}
#endif
#ifdef CONFIG_FS_READAHEAD
sem_destroy(&rwb->rhsem);
if (rwb->rhbuffer)
{
free(rwb->rhbuffer);
}
#endif
}
/****************************************************************************
patacongo
committed
* Name: rwb_read
patacongo
committed
****************************************************************************/
patacongo
committed
int rwb_read(FAR struct rwbuffer_s *rwb, off_t startblock, uint32_t nblocks,
FAR uint8_t *rdbuffer)
patacongo
committed
{
patacongo
committed
uint32_t remaining;
patacongo
committed
fvdbg("startblock=%ld nblocks=%ld rdbuffer=%p\n",
(long)startblock, (long)nblocks, rdbuffer);
patacongo
committed
#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.
*/
patacongo
committed
{
/* 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);
patacongo
committed
}
#endif
#ifdef CONFIG_FS_READAHEAD
/* Loop until we have read all of the requested blocks */
rwb_semtake(&rwb->rhsem);
patacongo
committed
for (remaining = nblocks; remaining > 0;)
patacongo
committed
{
/* Is there anything in the read-ahead buffer? */
patacongo
committed
size_t nbufblocks = 0;
patacongo
committed
patacongo
committed
/* Loop for each block we find in the read-head buffer. Count the
* number of buffers that we can read from read-ahead buffer.
*/
patacongo
committed
bufferend = rwb->rhblockstart + rwb->rhnblocks;
patacongo
committed
while ((startblock >= rwb->rhblockstart) &&
(startblock < bufferend) &&
patacongo
committed
(remaining > 0))
{
/* This is one more that we will read from the read ahead buffer */
patacongo
committed
patacongo
committed
nbufblocks++;
patacongo
committed
patacongo
committed
patacongo
committed
remaining--;
patacongo
committed
patacongo
committed
patacongo
committed
rwb_bufferread(rwb, startblock, nbufblocks, &rdbuffer);
patacongo
committed
/* If we did not get all of the data from the buffer, then we have to refill
* the buffer and try again.
*/
patacongo
committed
if (remaining > 0)
{
int ret = rwb_rhreload(rwb, startblock);
if (ret < 0)
{
fdbg("ERROR: Failed to fill the read-ahead buffer: %d\n", -ret);
patacongo
committed
}
patacongo
committed
/* 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
*/
patacongo
committed
rwb_semgive(&rwb->rhsem);
return 0;
#else
return rwb->rhreload(rwb->dev, startblock, nblocks, rdbuffer);
patacongo
committed
#endif
}
/****************************************************************************
* Name: rwb_write
****************************************************************************/
int rwb_write(FAR struct rwbuffer_s *rwb, off_t startblock,
patacongo
committed
size_t nblocks, FAR const uint8_t *wrbuffer)
patacongo
committed
{
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))
patacongo
committed
{
rwb_resetrhbuffer(rwb);
}
rwb_give(&rwb->rhsem);
#endif
#ifdef CONFIG_FS_WRITEBUFFER
fvdbg("startblock=%d wrbuffer=%p\n", startblock, wrbuffer);
patacongo
committed
/* Use the block cache unless the buffer size is bigger than block cache */
patacongo
committed
if (nblocks > rwb->wrmaxblocks)
patacongo
committed
{
/* 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);
patacongo
committed
}
else
{
/* Buffer the data in the write buffer */
ret = rwb_writebuffer(rwb, startblock, nblocks, wrbuffer);
patacongo
committed
}
patacongo
committed
/* 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
*/
patacongo
committed
return ret;
#else
return rwb->wrflush(rwb->dev, startblock, nblocks, wrbuffer);
patacongo
committed
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
#endif
}
/****************************************************************************
* Name: rwb_mediaremoved
****************************************************************************/
/* 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 */