summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorScott Shawcroft <scott@chickadee.tech>2016-10-18 14:56:17 -0700
committerScott Shawcroft <scott@chickadee.tech>2016-10-21 15:44:09 -0700
commit8b1526e95eeb5c324b6009d6d77a622bb131886a (patch)
treefb8cf62a4036f3d1bec9ba3d1a988f1e33b01f2d
parentbb1822faea6038e09c80eb8bec1b9df616a00ac0 (diff)
atmel-samd: Add a heap based cache for writing to flash.
The code will fallback to the flash scratch space when the GC cannot allocate us enough memory.
-rw-r--r--atmel-samd/access_vfs.c10
-rw-r--r--atmel-samd/main.c15
-rw-r--r--atmel-samd/spi_flash.c218
3 files changed, 200 insertions, 43 deletions
diff --git a/atmel-samd/access_vfs.c b/atmel-samd/access_vfs.c
index a3e8aaebc..07d0aa64e 100644
--- a/atmel-samd/access_vfs.c
+++ b/atmel-samd/access_vfs.c
@@ -55,18 +55,20 @@ Ctrl_status vfs_test_unit_ready(void)
}
//! This function returns the address of the last valid sector
-//! @param uint32_t_nb_sector Pointer to number of sectors (sector=512 bytes)
+//! @param uint32_t_nb_sector Pointer to the last valid sector (sector=512 bytes)
//! @return Ctrl_status
//! It is ready -> CTRL_GOOD
//! Memory unplug -> CTRL_NO_PRESENT
//! Not initialized or changed -> CTRL_BUSY
//! An error occurred -> CTRL_FAIL
-Ctrl_status vfs_read_capacity(uint32_t *uint32_t_nb_sector)
+Ctrl_status vfs_read_capacity(uint32_t *last_valid_sector)
{
- if (disk_ioctl(VFS_INDEX, GET_SECTOR_COUNT, uint32_t_nb_sector) != RES_OK) {
+ if (disk_ioctl(VFS_INDEX, GET_SECTOR_COUNT, last_valid_sector) != RES_OK) {
return CTRL_FAIL;
}
- return CTRL_GOOD;
+ // Subtract one from the sector count to get the last valid sector.
+ (*last_valid_sector)--;
+ return CTRL_GOOD;
}
//! This function returns the write-protected mode
diff --git a/atmel-samd/main.c b/atmel-samd/main.c
index cd1eafc23..ff970f15c 100644
--- a/atmel-samd/main.c
+++ b/atmel-samd/main.c
@@ -10,6 +10,7 @@
#include "py/gc.h"
#include "lib/fatfs/ff.h"
+#include "lib/fatfs/diskio.h"
#include "lib/utils/pyexec.h"
#include "extmod/fsusermount.h"
@@ -160,6 +161,12 @@ static char *stack_top;
static char heap[16384];
void reset_mp() {
+ // Sync the file systems in case any used RAM from the GC to cache. As soon
+ // as we re-init the GC all bets are off on the cache.
+ disk_ioctl(0, CTRL_SYNC, NULL);
+ disk_ioctl(1, CTRL_SYNC, NULL);
+ disk_ioctl(2, CTRL_SYNC, NULL);
+
#if MICROPY_ENABLE_GC
gc_init(heap, heap + sizeof(heap));
#endif
@@ -185,9 +192,6 @@ int main(int argc, char **argv) {
samd21_init();
#endif
- // Initialise the local flash filesystem.
- // Create it if needed, mount in on /flash, and set it as current dir.
- init_flash_fs();
int stack_dummy;
// Store the location of stack_dummy as an approximation for the top of the
@@ -196,6 +200,11 @@ int main(int argc, char **argv) {
stack_top = (char*)&stack_dummy;
reset_mp();
+ // Initialise the local flash filesystem after the gc in case we need to
+ // grab memory from it. Create it if needed, mount in on /flash, and set it
+ // as current dir.
+ init_flash_fs();
+
// Start USB after getting everything going.
#ifdef USB_REPL
udc_start();
diff --git a/atmel-samd/spi_flash.c b/atmel-samd/spi_flash.c
index abcc79f8c..5424ee6ea 100644
--- a/atmel-samd/spi_flash.c
+++ b/atmel-samd/spi_flash.c
@@ -29,6 +29,7 @@
#include "asf/sam0/drivers/sercom/spi/spi.h"
+#include "py/gc.h"
#include "py/obj.h"
#include "py/runtime.h"
#include "lib/fatfs/ff.h"
@@ -38,7 +39,7 @@
#include "spi_flash.h"
-#define SPI_FLASH_PART1_START_BLOCK (0x100)
+#define SPI_FLASH_PART1_START_BLOCK (0x1)
#define NO_SECTOR_LOADED 0xFFFFFFFF
@@ -64,28 +65,36 @@ static uint32_t sector_size;
// The page size. Its the maximum number of bytes that can be written at once.
static uint32_t page_size;
-// The currently cached sector in the scratch flash space.
+// The currently cached sector in the cache, ram or flash based.
static uint32_t current_sector;
-// A sector is made up of 8 blocks. This tracks which of those blocks in the
-// current sector current live in the scratch sector.
-static uint8_t dirty_mask;
+// Track which blocks (up to 32) in the current sector currently live in the
+// cache.
+static uint32_t dirty_mask;
+// We use this when we can allocate the whole cache in RAM.
+static uint8_t** ram_cache;
+
+// Address of the scratch flash sector.
#define SCRATCH_SECTOR (flash_size - sector_size)
+// Enable the flash over SPI.
static void flash_enable() {
port_pin_set_output_level(SPI_FLASH_CS, false);
}
+// Disable the flash over SPI.
static void flash_disable() {
port_pin_set_output_level(SPI_FLASH_CS, true);
}
+// Wait until both the write enable and write in progress bits have cleared.
static bool wait_for_flash_ready() {
uint8_t status_request[2] = {CMD_READ_STATUS, 0x00};
uint8_t response[2] = {0x00, 0x01};
enum status_code status = STATUS_OK;
- while (status == STATUS_OK && (response[1] & 0x1) == 1) {
+ // Both the write enable and write in progress bits should be low.
+ while (status == STATUS_OK && ((response[1] & 0x1) == 1 || (response[1] & 0x2) == 2)) {
flash_enable();
status = spi_transceive_buffer_wait(&spi_flash_instance, status_request, response, 2);
flash_disable();
@@ -93,6 +102,7 @@ static bool wait_for_flash_ready() {
return status == STATUS_OK;
}
+// Turn on the write enable bit so we can program and erase the flash.
static bool write_enable() {
flash_enable();
uint8_t command = CMD_ENABLE_WRITE;
@@ -101,12 +111,14 @@ static bool write_enable() {
return status == STATUS_OK;
}
+// Pack the low 24 bits of the address into a uint8_t array.
static void address_to_bytes(uint32_t address, uint8_t* bytes) {
bytes[0] = (address >> 16) & 0xff;
bytes[1] = (address >> 8) & 0xff;
bytes[2] = address & 0xff;
}
+// Read data_length's worth of bytes starting at address into data.
static bool read_flash(uint32_t address, uint8_t* data, uint32_t data_length) {
wait_for_flash_ready();
enum status_code status;
@@ -122,7 +134,9 @@ static bool read_flash(uint32_t address, uint8_t* data, uint32_t data_length) {
return status == STATUS_OK;
}
-// Assumes that the sector that address resides in has already been erased.
+// Writes data_length's worth of bytes starting at address from data. Assumes
+// that the sector that address resides in has already been erased. So make sure
+// to run erase_sector.
static bool write_flash(uint32_t address, const uint8_t* data, uint32_t data_length) {
if (page_size == 0) {
return false;
@@ -149,12 +163,12 @@ static bool write_flash(uint32_t address, const uint8_t* data, uint32_t data_len
return true;
}
-// Sector is really 24 bits.
+// Erases the given sector. Make sure you copied all of the data out of it you
+// need! Also note, sector_address is really 24 bits.
static bool erase_sector(uint32_t sector_address) {
// Before we erase the sector we need to wait for any writes to finish and
- // and then enable the write again. For good measure we check that the flash
- // is ready after enabling the write too.
- if (!wait_for_flash_ready() || !write_enable() || !wait_for_flash_ready()) {
+ // and then enable the write again.
+ if (!wait_for_flash_ready() || !write_enable()) {
return false;
}
@@ -221,25 +235,27 @@ void spi_flash_init(void) {
current_sector = NO_SECTOR_LOADED;
dirty_mask = 0;
+ ram_cache = NULL;
spi_flash_is_initialised = true;
}
}
+// The size of each individual block.
uint32_t spi_flash_get_block_size(void) {
return FLASH_BLOCK_SIZE;
}
+// The total number of available blocks.
uint32_t spi_flash_get_block_count(void) {
- // We subtract on erase sector size because we're going to use it as a
- // staging area for writes.
+ // We subtract one erase sector size because we may use it as a staging area
+ // for writes.
return SPI_FLASH_PART1_START_BLOCK + (flash_size - sector_size) / FLASH_BLOCK_SIZE;
}
-void spi_flash_flush(void) {
- if (current_sector == NO_SECTOR_LOADED) {
- return;
- }
+// Flush the cache that was written to the scratch portion of flash. Only used
+// when ram is tight.
+static bool flush_scratch_flash() {
// First, copy out any blocks that we haven't touched from the sector we've
// cached.
bool copy_to_scratch_ok = true;
@@ -253,7 +269,7 @@ void spi_flash_flush(void) {
if (!copy_to_scratch_ok) {
// TODO(tannewt): Do more here. We opted to not erase and copy bad data
// in. We still risk losing the data written to the scratch sector.
- return;
+ return false;
}
// Second, erase the current sector.
erase_sector(current_sector);
@@ -262,10 +278,123 @@ void spi_flash_flush(void) {
copy_block(SCRATCH_SECTOR + i * FLASH_BLOCK_SIZE,
current_sector + i * FLASH_BLOCK_SIZE);
}
+ return true;
+}
+
+// Attempts to allocate a new set of page buffers for caching a full sector in
+// ram. Each page is allocated separately so that the GC doesn't need to provide
+// one huge block. We can free it as we write if we want to also.
+static bool allocate_ram_cache() {
+ uint8_t blocks_per_sector = sector_size / FLASH_BLOCK_SIZE;
+ uint8_t pages_per_block = FLASH_BLOCK_SIZE / page_size;
+ ram_cache = gc_alloc(blocks_per_sector * pages_per_block * sizeof(uint32_t), false);
+ if (ram_cache == NULL) {
+ return false;
+ }
+ // Declare i and j outside the loops in case we fail to allocate everything
+ // we need. In that case we'll give it back.
+ int i = 0;
+ int j = 0;
+ bool success = true;
+ for (i = 0; i < sector_size / FLASH_BLOCK_SIZE; i++) {
+ for (int j = 0; j < pages_per_block; j++) {
+ uint8_t *page_cache = gc_alloc(page_size, false);
+ if (page_cache == NULL) {
+ success = false;
+ break;
+ }
+ ram_cache[i * pages_per_block + j] = page_cache;
+ }
+ if (!success) {
+ break;
+ }
+ }
+ // We couldn't allocate enough so give back what we got.
+ if (!success) {
+ for (; i >= 0; i--) {
+ for (; j >= 0; j--) {
+ gc_free(ram_cache[i * pages_per_block + j]);
+ }
+ j = pages_per_block - 1;
+ }
+ gc_free(ram_cache);
+ ram_cache = NULL;
+ }
+ return success;
+}
+
+// Flush the cached sector from ram onto the flash. We'll free the cache unless
+// keep_cache is true.
+static bool flush_ram_cache(bool keep_cache) {
+ // First, copy out any blocks that we haven't touched from the sector
+ // we've cached. If we don't do this we'll erase the data during the sector
+ // erase below.
+ bool copy_to_ram_ok = true;
+ uint8_t pages_per_block = FLASH_BLOCK_SIZE / page_size;
+ for (int i = 0; i < sector_size / FLASH_BLOCK_SIZE; i++) {
+ if ((dirty_mask & (1 << i)) == 0) {
+ for (int j = 0; j < pages_per_block; j++) {
+ copy_to_ram_ok = read_flash(
+ current_sector + (i * pages_per_block + j) * page_size,
+ ram_cache[i * pages_per_block + j],
+ page_size);
+ if (!copy_to_ram_ok) {
+ break;
+ }
+ }
+ }
+ if (!copy_to_ram_ok) {
+ break;
+ }
+ }
+
+ if (!copy_to_ram_ok) {
+ return false;
+ }
+ // Second, erase the current sector.
+ erase_sector(current_sector);
+ // Lastly, write all the data in ram that we've cached.
+ for (int i = 0; i < sector_size / FLASH_BLOCK_SIZE; i++) {
+ for (int j = 0; j < pages_per_block; j++) {
+ write_flash(current_sector + (i * pages_per_block + j) * page_size,
+ ram_cache[i * pages_per_block + j],
+ page_size);
+ if (!keep_cache) {
+ gc_free(ram_cache[i * pages_per_block + j]);
+ }
+ }
+ }
+ // We're done with the cache for now so give it back.
+ if (!keep_cache) {
+ gc_free(ram_cache);
+ ram_cache = NULL;
+ }
+ return true;
+}
+
+// Delegates to the correct flash flush method depending on the existing cache.
+static void spi_flash_flush_keep_cache(bool keep_cache) {
+ if (current_sector == NO_SECTOR_LOADED) {
+ return;
+ }
+ // If we've cached to the flash itself flush from there.
+ if (ram_cache == NULL) {
+ flush_scratch_flash();
+ } else {
+ flush_ram_cache(keep_cache);
+ }
current_sector = NO_SECTOR_LOADED;
}
-static void build_partition(uint8_t *buf, int boot, int type, uint32_t start_block, uint32_t num_blocks) {
+// External flash function used. If called externally we assume we won't need
+// the cache after.
+void spi_flash_flush(void) {
+ spi_flash_flush_keep_cache(false);
+}
+
+// Builds a partition entry for the MBR.
+static void build_partition(uint8_t *buf, int boot, int type,
+ uint32_t start_block, uint32_t num_blocks) {
buf[0] = boot;
if (num_blocks == 0) {
@@ -312,15 +441,15 @@ static uint32_t convert_block_to_flash_addr(uint32_t block) {
}
bool spi_flash_read_block(uint8_t *dest, uint32_t block) {
- //printf("RD %u\n", block);
if (block == 0) {
- // fake the MBR so we can decide on our own partition table
-
+ // Fake the MBR so we can decide on our own partition table
for (int i = 0; i < 446; i++) {
dest[i] = 0;
}
- build_partition(dest + 446, 0, 0x01 /* FAT12 */, SPI_FLASH_PART1_START_BLOCK, spi_flash_get_block_count() - SPI_FLASH_PART1_START_BLOCK);
+ build_partition(dest + 446, 0, 0x01 /* FAT12 */,
+ SPI_FLASH_PART1_START_BLOCK,
+ spi_flash_get_block_count() - SPI_FLASH_PART1_START_BLOCK);
build_partition(dest + 462, 0, 0, 0, 0);
build_partition(dest + 478, 0, 0, 0, 0);
build_partition(dest + 494, 0, 0, 0, 0);
@@ -329,9 +458,11 @@ bool spi_flash_read_block(uint8_t *dest, uint32_t block) {
dest[511] = 0xaa;
return true;
-
+ } else if (block < SPI_FLASH_PART1_START_BLOCK) {
+ memset(dest, 0, FLASH_BLOCK_SIZE);
+ return true;
} else {
- // non-MBR block, get data from flash memory
+ // Non-MBR block, get data from flash memory.
uint32_t src = convert_block_to_flash_addr(block);
if (src == -1) {
// bad block number
@@ -342,34 +473,49 @@ bool spi_flash_read_block(uint8_t *dest, uint32_t block) {
}
bool spi_flash_write_block(const uint8_t *data, uint32_t block) {
- if (block == 0) {
- // can't write MBR, but pretend we did
+ if (block < SPI_FLASH_PART1_START_BLOCK) {
+ // Fake writing below the flash partition.
return true;
-
} else {
- // non-MBR block, copy to cache
- volatile uint32_t address = convert_block_to_flash_addr(block);
+ // Non-MBR block, copy to cache
+ uint32_t address = convert_block_to_flash_addr(block);
if (address == -1) {
// bad block number
return false;
}
// Wait for any previous writes to finish.
wait_for_flash_ready();
+ // Mask out the lower bits that designate the address within the sector.
uint32_t this_sector = address & (~(sector_size - 1));
- uint8_t block_index = block % (sector_size / FLASH_BLOCK_SIZE);
+ uint8_t block_index = (address / FLASH_BLOCK_SIZE) % (sector_size / FLASH_BLOCK_SIZE);
uint8_t mask = 1 << (block_index);
+ // Flush the cache if we're moving onto a sector our we're writing the
+ // same block again.
if (current_sector != this_sector || (mask & dirty_mask) > 0) {
if (current_sector != NO_SECTOR_LOADED) {
- spi_flash_flush();
+ spi_flash_flush_keep_cache(true);
+ }
+ if (ram_cache == NULL && !allocate_ram_cache()) {
+ erase_sector(SCRATCH_SECTOR);
+ wait_for_flash_ready();
}
- erase_sector(SCRATCH_SECTOR);
current_sector = this_sector;
dirty_mask = 0;
- wait_for_flash_ready();
}
- uint32_t scratch_address = SCRATCH_SECTOR + block_index * FLASH_BLOCK_SIZE;
dirty_mask |= mask;
- return write_flash(scratch_address, data, FLASH_BLOCK_SIZE);
+ // Copy the block to the appropriate cache.
+ if (ram_cache != NULL) {
+ uint8_t pages_per_block = FLASH_BLOCK_SIZE / page_size;
+ for (int i = 0; i < pages_per_block; i++) {
+ memcpy(ram_cache[block_index * pages_per_block + i],
+ data + i * page_size,
+ page_size);
+ }
+ return true;
+ } else {
+ uint32_t scratch_address = SCRATCH_SECTOR + block_index * FLASH_BLOCK_SIZE;
+ return write_flash(scratch_address, data, FLASH_BLOCK_SIZE);
+ }
}
}