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.
This commit is contained in:
Scott Shawcroft 2016-10-18 14:56:17 -07:00
parent bb1822faea
commit 8b1526e95e
3 changed files with 200 additions and 43 deletions

View File

@ -55,18 +55,20 @@ Ctrl_status vfs_test_unit_ready(void)
} }
//! This function returns the address of the last valid sector //! 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 //! @return Ctrl_status
//! It is ready -> CTRL_GOOD //! It is ready -> CTRL_GOOD
//! Memory unplug -> CTRL_NO_PRESENT //! Memory unplug -> CTRL_NO_PRESENT
//! Not initialized or changed -> CTRL_BUSY //! Not initialized or changed -> CTRL_BUSY
//! An error occurred -> CTRL_FAIL //! 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_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 //! This function returns the write-protected mode

View File

@ -10,6 +10,7 @@
#include "py/gc.h" #include "py/gc.h"
#include "lib/fatfs/ff.h" #include "lib/fatfs/ff.h"
#include "lib/fatfs/diskio.h"
#include "lib/utils/pyexec.h" #include "lib/utils/pyexec.h"
#include "extmod/fsusermount.h" #include "extmod/fsusermount.h"
@ -160,6 +161,12 @@ static char *stack_top;
static char heap[16384]; static char heap[16384];
void reset_mp() { 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 #if MICROPY_ENABLE_GC
gc_init(heap, heap + sizeof(heap)); gc_init(heap, heap + sizeof(heap));
#endif #endif
@ -185,9 +192,6 @@ int main(int argc, char **argv) {
samd21_init(); samd21_init();
#endif #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; int stack_dummy;
// Store the location of stack_dummy as an approximation for the top of the // 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; stack_top = (char*)&stack_dummy;
reset_mp(); 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. // Start USB after getting everything going.
#ifdef USB_REPL #ifdef USB_REPL
udc_start(); udc_start();

View File

@ -29,6 +29,7 @@
#include "asf/sam0/drivers/sercom/spi/spi.h" #include "asf/sam0/drivers/sercom/spi/spi.h"
#include "py/gc.h"
#include "py/obj.h" #include "py/obj.h"
#include "py/runtime.h" #include "py/runtime.h"
#include "lib/fatfs/ff.h" #include "lib/fatfs/ff.h"
@ -38,7 +39,7 @@
#include "spi_flash.h" #include "spi_flash.h"
#define SPI_FLASH_PART1_START_BLOCK (0x100) #define SPI_FLASH_PART1_START_BLOCK (0x1)
#define NO_SECTOR_LOADED 0xFFFFFFFF #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. // The page size. Its the maximum number of bytes that can be written at once.
static uint32_t page_size; 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; static uint32_t current_sector;
// A sector is made up of 8 blocks. This tracks which of those blocks in the // Track which blocks (up to 32) in the current sector currently live in the
// current sector current live in the scratch sector. // cache.
static uint8_t dirty_mask; 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) #define SCRATCH_SECTOR (flash_size - sector_size)
// Enable the flash over SPI.
static void flash_enable() { static void flash_enable() {
port_pin_set_output_level(SPI_FLASH_CS, false); port_pin_set_output_level(SPI_FLASH_CS, false);
} }
// Disable the flash over SPI.
static void flash_disable() { static void flash_disable() {
port_pin_set_output_level(SPI_FLASH_CS, true); 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() { static bool wait_for_flash_ready() {
uint8_t status_request[2] = {CMD_READ_STATUS, 0x00}; uint8_t status_request[2] = {CMD_READ_STATUS, 0x00};
uint8_t response[2] = {0x00, 0x01}; uint8_t response[2] = {0x00, 0x01};
enum status_code status = STATUS_OK; 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(); flash_enable();
status = spi_transceive_buffer_wait(&spi_flash_instance, status_request, response, 2); status = spi_transceive_buffer_wait(&spi_flash_instance, status_request, response, 2);
flash_disable(); flash_disable();
@ -93,6 +102,7 @@ static bool wait_for_flash_ready() {
return status == STATUS_OK; return status == STATUS_OK;
} }
// Turn on the write enable bit so we can program and erase the flash.
static bool write_enable() { static bool write_enable() {
flash_enable(); flash_enable();
uint8_t command = CMD_ENABLE_WRITE; uint8_t command = CMD_ENABLE_WRITE;
@ -101,12 +111,14 @@ static bool write_enable() {
return status == STATUS_OK; 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) { static void address_to_bytes(uint32_t address, uint8_t* bytes) {
bytes[0] = (address >> 16) & 0xff; bytes[0] = (address >> 16) & 0xff;
bytes[1] = (address >> 8) & 0xff; bytes[1] = (address >> 8) & 0xff;
bytes[2] = address & 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) { static bool read_flash(uint32_t address, uint8_t* data, uint32_t data_length) {
wait_for_flash_ready(); wait_for_flash_ready();
enum status_code status; 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; 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) { static bool write_flash(uint32_t address, const uint8_t* data, uint32_t data_length) {
if (page_size == 0) { if (page_size == 0) {
return false; return false;
@ -149,12 +163,12 @@ static bool write_flash(uint32_t address, const uint8_t* data, uint32_t data_len
return true; 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) { static bool erase_sector(uint32_t sector_address) {
// Before we erase the sector we need to wait for any writes to finish and // 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 // and then enable the write again.
// is ready after enabling the write too. if (!wait_for_flash_ready() || !write_enable()) {
if (!wait_for_flash_ready() || !write_enable() || !wait_for_flash_ready()) {
return false; return false;
} }
@ -221,25 +235,27 @@ void spi_flash_init(void) {
current_sector = NO_SECTOR_LOADED; current_sector = NO_SECTOR_LOADED;
dirty_mask = 0; dirty_mask = 0;
ram_cache = NULL;
spi_flash_is_initialised = true; spi_flash_is_initialised = true;
} }
} }
// The size of each individual block.
uint32_t spi_flash_get_block_size(void) { uint32_t spi_flash_get_block_size(void) {
return FLASH_BLOCK_SIZE; return FLASH_BLOCK_SIZE;
} }
// The total number of available blocks.
uint32_t spi_flash_get_block_count(void) { uint32_t spi_flash_get_block_count(void) {
// We subtract on erase sector size because we're going to use it as a // We subtract one erase sector size because we may use it as a staging area
// staging area for writes. // for writes.
return SPI_FLASH_PART1_START_BLOCK + (flash_size - sector_size) / FLASH_BLOCK_SIZE; return SPI_FLASH_PART1_START_BLOCK + (flash_size - sector_size) / FLASH_BLOCK_SIZE;
} }
void spi_flash_flush(void) { // Flush the cache that was written to the scratch portion of flash. Only used
if (current_sector == NO_SECTOR_LOADED) { // when ram is tight.
return; static bool flush_scratch_flash() {
}
// First, copy out any blocks that we haven't touched from the sector we've // First, copy out any blocks that we haven't touched from the sector we've
// cached. // cached.
bool copy_to_scratch_ok = true; bool copy_to_scratch_ok = true;
@ -253,7 +269,7 @@ void spi_flash_flush(void) {
if (!copy_to_scratch_ok) { if (!copy_to_scratch_ok) {
// TODO(tannewt): Do more here. We opted to not erase and copy bad data // 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. // in. We still risk losing the data written to the scratch sector.
return; return false;
} }
// Second, erase the current sector. // Second, erase the current sector.
erase_sector(current_sector); erase_sector(current_sector);
@ -262,10 +278,123 @@ void spi_flash_flush(void) {
copy_block(SCRATCH_SECTOR + i * FLASH_BLOCK_SIZE, copy_block(SCRATCH_SECTOR + i * FLASH_BLOCK_SIZE,
current_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; 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; buf[0] = boot;
if (num_blocks == 0) { 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) { bool spi_flash_read_block(uint8_t *dest, uint32_t block) {
//printf("RD %u\n", block);
if (block == 0) { 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++) { for (int i = 0; i < 446; i++) {
dest[i] = 0; 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 + 462, 0, 0, 0, 0);
build_partition(dest + 478, 0, 0, 0, 0); build_partition(dest + 478, 0, 0, 0, 0);
build_partition(dest + 494, 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; dest[511] = 0xaa;
return true; return true;
} else if (block < SPI_FLASH_PART1_START_BLOCK) {
memset(dest, 0, FLASH_BLOCK_SIZE);
return true;
} else { } 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); uint32_t src = convert_block_to_flash_addr(block);
if (src == -1) { if (src == -1) {
// bad block number // 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) { bool spi_flash_write_block(const uint8_t *data, uint32_t block) {
if (block == 0) { if (block < SPI_FLASH_PART1_START_BLOCK) {
// can't write MBR, but pretend we did // Fake writing below the flash partition.
return true; return true;
} else { } else {
// non-MBR block, copy to cache // Non-MBR block, copy to cache
volatile uint32_t address = convert_block_to_flash_addr(block); uint32_t address = convert_block_to_flash_addr(block);
if (address == -1) { if (address == -1) {
// bad block number // bad block number
return false; return false;
} }
// Wait for any previous writes to finish. // Wait for any previous writes to finish.
wait_for_flash_ready(); wait_for_flash_ready();
// Mask out the lower bits that designate the address within the sector.
uint32_t this_sector = address & (~(sector_size - 1)); 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); 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 != this_sector || (mask & dirty_mask) > 0) {
if (current_sector != NO_SECTOR_LOADED) { 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; current_sector = this_sector;
dirty_mask = 0; dirty_mask = 0;
wait_for_flash_ready();
} }
uint32_t scratch_address = SCRATCH_SECTOR + block_index * FLASH_BLOCK_SIZE;
dirty_mask |= mask; 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);
}
} }
} }