2006-07-14 04:42:37 +02:00
|
|
|
/*
|
|
|
|
cache.c
|
|
|
|
The cache is not visible to the user. It should be flushed
|
|
|
|
when any file is closed or changes are made to the filesystem.
|
|
|
|
|
|
|
|
This cache implements a least-used-page replacement policy. This will
|
|
|
|
distribute sectors evenly over the pages, so if less than the maximum
|
|
|
|
pages are used at once, they should all eventually remain in the cache.
|
|
|
|
This also has the benefit of throwing out old sectors, so as not to keep
|
|
|
|
too many stale pages around.
|
|
|
|
|
|
|
|
Copyright (c) 2006 Michael "Chishm" Chisholm
|
|
|
|
|
|
|
|
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. The name of the author may not be used to endorse or promote products derived
|
|
|
|
from this software without specific prior written permission.
|
|
|
|
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
|
2008-05-01 11:31:34 +02:00
|
|
|
|
2008-05-01 12:02:19 +02:00
|
|
|
2007-11-14 - Chishm
|
|
|
|
* Fixed _FAT_cache_constructor to return NULL on error, not false
|
2008-05-01 11:31:34 +02:00
|
|
|
* Fixed _FAT_cache_flush to return false on error. With thanks to xorloser
|
2006-07-14 04:42:37 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
#include "common.h"
|
|
|
|
#include "cache.h"
|
2008-05-10 21:35:18 +02:00
|
|
|
#include "disc.h"
|
2006-07-14 04:42:37 +02:00
|
|
|
|
|
|
|
#include "mem_allocate.h"
|
2008-05-01 12:02:19 +02:00
|
|
|
#include "bit_ops.h"
|
2006-07-14 04:42:37 +02:00
|
|
|
|
|
|
|
#define CACHE_FREE 0xFFFFFFFF
|
|
|
|
|
2006-07-25 12:36:42 +02:00
|
|
|
CACHE* _FAT_cache_constructor (u32 numberOfPages, const IO_INTERFACE* discInterface) {
|
2006-07-14 04:42:37 +02:00
|
|
|
CACHE* cache;
|
|
|
|
u32 i;
|
|
|
|
CACHE_ENTRY* cacheEntries;
|
|
|
|
|
|
|
|
if (numberOfPages < 2) {
|
|
|
|
numberOfPages = 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
cache = (CACHE*) _FAT_mem_allocate (sizeof(CACHE));
|
|
|
|
if (cache == NULL) {
|
2008-05-01 11:31:34 +02:00
|
|
|
return NULL;
|
2006-07-14 04:42:37 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
cache->disc = discInterface;
|
|
|
|
cache->numberOfPages = numberOfPages;
|
|
|
|
|
|
|
|
|
|
|
|
cacheEntries = (CACHE_ENTRY*) _FAT_mem_allocate ( sizeof(CACHE_ENTRY) * numberOfPages);
|
|
|
|
if (cacheEntries == NULL) {
|
|
|
|
_FAT_mem_free (cache);
|
2008-05-01 11:31:34 +02:00
|
|
|
return NULL;
|
2006-07-14 04:42:37 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < numberOfPages; i++) {
|
|
|
|
cacheEntries[i].sector = CACHE_FREE;
|
|
|
|
cacheEntries[i].count = 0;
|
|
|
|
cacheEntries[i].dirty = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
cache->cacheEntries = cacheEntries;
|
|
|
|
|
|
|
|
cache->pages = (u8*) _FAT_mem_allocate ( CACHE_PAGE_SIZE * numberOfPages);
|
|
|
|
if (cache->pages == NULL) {
|
|
|
|
_FAT_mem_free (cache->cacheEntries);
|
|
|
|
_FAT_mem_free (cache);
|
2008-05-01 11:31:34 +02:00
|
|
|
return NULL;
|
2006-07-14 04:42:37 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return cache;
|
|
|
|
}
|
|
|
|
|
|
|
|
void _FAT_cache_destructor (CACHE* cache) {
|
|
|
|
// Clear out cache before destroying it
|
|
|
|
_FAT_cache_flush(cache);
|
|
|
|
|
|
|
|
// Free memory in reverse allocation order
|
|
|
|
_FAT_mem_free (cache->pages);
|
|
|
|
_FAT_mem_free (cache->cacheEntries);
|
|
|
|
_FAT_mem_free (cache);
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Retrieve a sector's page from the cache. If it is not found in the cache,
|
|
|
|
load it into the cache and return the page it was loaded to.
|
|
|
|
Return CACHE_FREE on error.
|
|
|
|
*/
|
|
|
|
static u32 _FAT_cache_getSector (CACHE* cache, u32 sector) {
|
|
|
|
u32 i;
|
|
|
|
CACHE_ENTRY* cacheEntries = cache->cacheEntries;
|
|
|
|
u32 numberOfPages = cache->numberOfPages;
|
|
|
|
|
|
|
|
u32 leastUsed = 0;
|
|
|
|
u32 lowestCount = 0xFFFFFFFF;
|
|
|
|
|
|
|
|
for (i = 0; (i < numberOfPages) && (cacheEntries[i].sector != sector); i++) {
|
|
|
|
// While searching for the desired sector, also search for the leased used page
|
|
|
|
if ( (cacheEntries[i].sector == CACHE_FREE) || (cacheEntries[i].count < lowestCount) ) {
|
|
|
|
leastUsed = i;
|
|
|
|
lowestCount = cacheEntries[i].count;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If it found the sector in the cache, return it
|
|
|
|
if ((i < numberOfPages) && (cacheEntries[i].sector == sector)) {
|
|
|
|
// Increment usage counter
|
|
|
|
cacheEntries[i].count += 1;
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If it didn't, replace the least used cache page with the desired sector
|
|
|
|
if ((cacheEntries[leastUsed].sector != CACHE_FREE) && (cacheEntries[leastUsed].dirty == true)) {
|
|
|
|
// Write the page back to disc if it has been written to
|
|
|
|
if (!_FAT_disc_writeSectors (cache->disc, cacheEntries[leastUsed].sector, 1, cache->pages + CACHE_PAGE_SIZE * leastUsed)) {
|
|
|
|
return CACHE_FREE;
|
|
|
|
}
|
|
|
|
cacheEntries[leastUsed].dirty = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Load the new sector into the cache
|
|
|
|
if (!_FAT_disc_readSectors (cache->disc, sector, 1, cache->pages + CACHE_PAGE_SIZE * leastUsed)) {
|
|
|
|
return CACHE_FREE;
|
|
|
|
}
|
|
|
|
cacheEntries[leastUsed].sector = sector;
|
|
|
|
// Increment the usage count, don't reset it
|
|
|
|
// This creates a paging policy of least used PAGE, not sector
|
|
|
|
cacheEntries[leastUsed].count += 1;
|
|
|
|
return leastUsed;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Reads some data from a cache page, determined by the sector number
|
|
|
|
*/
|
|
|
|
bool _FAT_cache_readPartialSector (CACHE* cache, void* buffer, u32 sector, u32 offset, u32 size) {
|
|
|
|
u32 page;
|
|
|
|
|
|
|
|
if (offset + size > BYTES_PER_READ) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
page = _FAT_cache_getSector (cache, sector);
|
|
|
|
if (page == CACHE_FREE) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
memcpy (buffer, cache->pages + (CACHE_PAGE_SIZE * page) + offset, size);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2008-05-01 12:02:19 +02:00
|
|
|
bool _FAT_cache_readLittleEndianValue (CACHE* cache, u32 *value, u32 sector, u32 offset, u32 num_bytes) {
|
|
|
|
u8 buf[4];
|
|
|
|
if (!_FAT_cache_readPartialSector(cache, buf, sector, offset, num_bytes)) return false;
|
|
|
|
|
|
|
|
switch(num_bytes) {
|
|
|
|
case 1: *value = buf[0]; break;
|
|
|
|
case 2: *value = u8array_to_u16(buf,0); break;
|
|
|
|
case 4: *value = u8array_to_u32(buf,0); break;
|
|
|
|
default: return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2006-07-14 04:42:37 +02:00
|
|
|
/*
|
|
|
|
Writes some data to a cache page, making sure it is loaded into memory first.
|
|
|
|
*/
|
|
|
|
bool _FAT_cache_writePartialSector (CACHE* cache, const void* buffer, u32 sector, u32 offset, u32 size) {
|
|
|
|
u32 page;
|
|
|
|
|
|
|
|
if (offset + size > BYTES_PER_READ) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
page = _FAT_cache_getSector (cache, sector);
|
|
|
|
if (page == CACHE_FREE) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
memcpy (cache->pages + (CACHE_PAGE_SIZE * page) + offset, buffer, size);
|
|
|
|
cache->cacheEntries[page].dirty = true;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2008-05-01 12:02:19 +02:00
|
|
|
bool _FAT_cache_writeLittleEndianValue (CACHE* cache, const u32 value, u32 sector, u32 offset, u32 size) {
|
|
|
|
u8 buf[4] = {0, 0, 0, 0};
|
|
|
|
|
|
|
|
switch(size) {
|
|
|
|
case 1: buf[0] = value; break;
|
|
|
|
case 2: u16_to_u8array(buf, 0, value); break;
|
|
|
|
case 4: u32_to_u8array(buf, 0, value); break;
|
|
|
|
default: return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return _FAT_cache_writePartialSector(cache, buf, sector, offset, size);
|
|
|
|
}
|
|
|
|
|
2006-07-14 04:42:37 +02:00
|
|
|
/*
|
|
|
|
Writes some data to a cache page, zeroing out the page first
|
|
|
|
*/
|
|
|
|
bool _FAT_cache_eraseWritePartialSector (CACHE* cache, const void* buffer, u32 sector, u32 offset, u32 size) {
|
|
|
|
u32 page;
|
|
|
|
|
|
|
|
if (offset + size > BYTES_PER_READ) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
page = _FAT_cache_getSector (cache, sector);
|
|
|
|
if (page == CACHE_FREE) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
memset (cache->pages + (CACHE_PAGE_SIZE * page), 0, CACHE_PAGE_SIZE);
|
|
|
|
memcpy (cache->pages + (CACHE_PAGE_SIZE * page) + offset, buffer, size);
|
|
|
|
cache->cacheEntries[page].dirty = true;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
Flushes all dirty pages to disc, clearing the dirty flag.
|
|
|
|
Also resets all pages' page count to 0.
|
|
|
|
*/
|
|
|
|
bool _FAT_cache_flush (CACHE* cache) {
|
|
|
|
u32 i;
|
|
|
|
|
|
|
|
for (i = 0; i < cache->numberOfPages; i++) {
|
|
|
|
if (cache->cacheEntries[i].dirty) {
|
|
|
|
if (!_FAT_disc_writeSectors (cache->disc, cache->cacheEntries[i].sector, 1, cache->pages + CACHE_PAGE_SIZE * i)) {
|
2008-05-01 11:31:34 +02:00
|
|
|
return false;
|
2006-07-14 04:42:37 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
cache->cacheEntries[i].count = 0;
|
|
|
|
cache->cacheEntries[i].dirty = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void _FAT_cache_invalidate (CACHE* cache) {
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < cache->numberOfPages; i++) {
|
|
|
|
cache->cacheEntries[i].sector = CACHE_FREE;
|
|
|
|
cache->cacheEntries[i].count = 0;
|
|
|
|
cache->cacheEntries[i].dirty = false;
|
|
|
|
}
|
|
|
|
}
|
2008-05-01 11:31:34 +02:00
|
|
|
|