From 8b1526e95eeb5c324b6009d6d77a622bb131886a Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Tue, 18 Oct 2016 14:56:17 -0700 Subject: [PATCH] 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. --- atmel-samd/access_vfs.c | 10 +- atmel-samd/main.c | 15 ++- atmel-samd/spi_flash.c | 218 +++++++++++++++++++++++++++++++++------- 3 files changed, 200 insertions(+), 43 deletions(-) diff --git a/atmel-samd/access_vfs.c b/atmel-samd/access_vfs.c index a3e8aaebcd..07d0aa64e0 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 cd1eafc234..ff970f15c9 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 abcc79f8c5..5424ee6ea5 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); + } } }