From: sjlongland Date: Sat, 26 Sep 2015 10:32:45 +0000 (+0000) Subject: stm32f4_vrom: Virtual EEPROM driver for STM32F4. X-Git-Url: http://git.whiteaudio.com/gitweb/?a=commitdiff_plain;h=e6a22b1ace42e3115efefa68133e79c2f988ab62;p=freetel-svn-tracking.git stm32f4_vrom: Virtual EEPROM driver for STM32F4. This is a work-in-progress driver for talking to the flash on the STM32F4. Untested for now. git-svn-id: https://svn.code.sf.net/p/freetel/code@2392 01035d8c-6547-0410-b346-abe4f91aad63 --- diff --git a/codec2-dev/stm32/inc/stm32f4_vrom.h b/codec2-dev/stm32/inc/stm32f4_vrom.h new file mode 100644 index 00000000..e878b9c0 --- /dev/null +++ b/codec2-dev/stm32/inc/stm32f4_vrom.h @@ -0,0 +1,70 @@ +#ifndef _STM32F4_VROM_H_ +#define _STM32F4_VROM_H_ +/*! + * STM32F4 Virtual EEPROM driver + * + * This module implements a crude virtual EEPROM device stored in on-board + * flash. The STM32F405 has 4 16kB flash sectors starting at address + * 0x80000000, followed by a 64kB sector, then 128kB sectors. + * + * The Cortex M4 core maps these all to address 0x00000000 when booting + * from normal flash, so the first sector is reserved for interrupt + * vectors. + * + * Everything else however is free game, and so we use these smaller + * sectors to store our configuration. + * + * Author Stuart Longland + * Copyright (C) 2015 FreeDV project. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. This program is + * distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see + * . + */ + +#include +#include + +/*! + * Read data from a virtual EEPROM. + * @param rom ROM ID to start reading. + * @param offset Address offset into ROM to start reading. + * @param size Number of bytes to read from ROM. + * @param out Buffer to write ROM content to. + * @returns Number of bytes read from ROM. + * @retval -ENXIO No valid data found for address. + * @retval -ESPIPE Offset past end of ROM. + */ +int vrom_read(uint8_t rom, uint16_t offset, uint16_t size, void* out); + +/*! + * Write data to a virtual EEPROM. + * @param rom ROM ID to start writing. + * @param offset Address offset into ROM to start writing. + * @param size Number of bytes to write to ROM. + * @param in Buffer to write ROM content from. + * @returns Number of bytes written to ROM. + * @retval -EIO Programming failed + * @retval -ENOSPC No free blocks available + */ +int vrom_write(uint8_t rom, uint16_t offset, uint16_t size, + const void* in); + +/*! + * Erase a virtual EEPROM. + * @param rom ROM ID to erase. + * @returns Number of bytes written to ROM. + * @retval -EIO Programming failed + * @retval -ENOSPC No free blocks available + */ +int vrom_erase(uint8_t rom); + +#endif diff --git a/codec2-dev/stm32/src/stm32f4_vrom.c b/codec2-dev/stm32/src/stm32f4_vrom.c new file mode 100644 index 00000000..eac060c7 --- /dev/null +++ b/codec2-dev/stm32/src/stm32f4_vrom.c @@ -0,0 +1,688 @@ +/*! + * STM32F4 Virtual EEPROM driver + * + * This module implements a crude virtual EEPROM device stored in on-board + * flash. The STM32F405 has 4 16kB flash sectors starting at address + * 0x80000000, followed by a 64kB sector, then 128kB sectors. + * + * The Cortex M4 core maps these all to address 0x00000000 when booting + * from normal flash, so the first sector is reserved for interrupt + * vectors. + * + * Everything else however is free game, and so we use these smaller + * sectors to store our configuration. + * + * Author Stuart Longland + * Copyright (C) 2015 FreeDV project. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 2.1, + * as published by the Free Software Foundation. This program is + * distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see + * . + */ + +#include +#include +#include "stm32f4_vrom.h" +#include "stm32f4xx_flash.h" +#include "stm32f4xx_crc.h" + +#define VROM_SECT_SZ (65536) /*!< Size of a flash sector */ +#define VROM_SECT_CNT (3) /*!< Number of sectors */ +#define VROM_BLOCK_SZ (256) /*!< Size of a flash block */ + +/*! + * Starting address for the flash area + */ +#define VROM_START_ADDR (0x08004000) + +/*! + * Number of blocks we can fit per sector, including the index block. + */ +#define VROM_BLOCK_CNT (VROM_SECT_SZ / VROM_BLOCK_SZ) + +/*! + * Number of application blocks we can fit per sector. + */ +#define VROM_SECT_APP_BLOCK_CNT (VROM_BLOCK_CNT - 1) + +/*! + * Total number of application blocks we can fit in flash. + */ +#define VROM_APP_BLOCK_CNT (VROM_SECT_CNT * VROM_SECT_APP_BLOCK_CNT) + +/*! + * Maximum number of erase cycles per sector. No idea what this actually + * is, they don't say in the datasheet. + */ +#define VROM_MAX_CYCLES (100000) + +/*! + * EEPROM block header. + */ +struct vrom_block_hdr_t { + /*! + * CRC32 checksum of the data, offset, size and ROM ID. + * A CRC32 of 0x00000000 indicates an obsoleted block. + * A CRC32 of 0xffffffff indicates an erased block. + */ + uint32_t crc32; + /*! + * ROM ID. + */ + uint8_t rom; + /*! + * Block number in the virtual EEPROM. + */ + uint8_t idx; + /*! + * Number of bytes from the virtual EEPROM stored in this block. + */ + uint8_t size; + /*! + * Reserved for future use. + */ + uint8_t reserved; +}; + +/*! + * The size of a block header in bytes. + */ +#define VROM_BLOCK_HDR_SZ (sizeof(struct vrom_block_hdr_t)) + +/*! + * The amount of data available for application use. + */ +#define VROM_DATA_SZ (VROM_BLOCK_SZ - VROM_BLOCK_HDR_SZ) + +/*! + * EEPROM data block. + */ +struct vrom_data_block_t { + /*! Block header */ + struct vrom_block_hdr_t header; + + /*! Block data */ + uint8_t data[VROM_DATA_SZ]; +}; + +/*! + * The first block in a sector is the sector index block. This indicates + * the used/free state of the entire block and counts the number of + * erase cycles for the sector. The index block has no header. + */ +struct vrom_sector_idx_t { + /*! + * Number of erase cycles remaining for the sector. + * 0xffffffff == unprogrammed. + */ + uint32_t cycles_remain; + /*! + * Block metadata flags. One for each data block in the sector. + * Does not include the index block. + */ + uint16_t flags[VROM_SECT_APP_BLOCK_CNT]; +}; + +/*! + * Return the address of a virtual EEPROM sector header. + */ +static const struct vrom_sector_idx_t* vrom_get_sector_hdr(uint8_t sector) +{ + return (const struct vrom_sector_idx_t*)( + VROM_START_ADDR + (VROM_SECT_SZ * sector)); +} + +/*! + * Return the address of a virtual EEPROM block. + */ +static const struct vrom_data_block_t* vrom_get_block( + uint8_t sector, uint8_t block) +{ + const void* sector_hdr = (const void*) + vrom_get_sector_hdr(sector); + return (const struct vrom_data_block_t*)( + sector_hdr + (VROM_BLOCK_SZ + * ((block % VROM_SECT_APP_BLOCK_CNT)+1))); +} + +/*! + * Compute the CRC32 of a block. + */ +static uint32_t vrom_crc32( + const struct vrom_data_block_t* const block) +{ + uint32_t tmp; + uint32_t crc; + uint32_t size = VROM_BLOCK_SZ - sizeof(uint32_t); + const uint8_t* in = (const uint8_t*)(&(block->header.rom)); + + CRC_ResetDR(); + while(size) { + tmp = 0; + if (size) { + tmp |= (uint32_t)(*(in++)) << 24; + size--; + } + if (size) { + tmp |= (uint32_t)(*(in++)) << 16; + size--; + } + if (size) { + tmp |= (uint32_t)(*(in++)) << 8; + size--; + } + if (size) { + tmp |= (uint32_t)(*(in++)); + size--; + } + crc = CRC_CalcCRC(tmp); + } + return crc; +} + +/*! + * Find the block storing the given index. + */ +static const struct vrom_data_block_t* vrom_find(uint8_t rom, uint8_t idx) +{ + int sector, block; + + for (sector = 0; sector < VROM_SECT_CNT; sector++) { + const struct vrom_sector_idx_t* sect_hdr + = vrom_get_sector_hdr(sector); + if (sect_hdr->cycles_remain == UINT32_MAX) + /* unformatted */ + continue; + for (block = 0; block < VROM_SECT_APP_BLOCK_CNT; block++) { + const struct vrom_data_block_t* block_ptr; + if (sect_hdr->flags[block] == UINT16_MAX) + /* unformatted */ + continue; + if (sect_hdr->flags[block] == 0) + /* obsolete */ + continue; + + block_ptr = vrom_get_block(sector, block); + + /* Verify the content */ + if (vrom_crc32(block_ptr) + != block_ptr->header.crc32) + /* corrupt */ + continue; + + if (block_ptr->header.rom != rom) + /* different ROM */ + continue; + + if (block_ptr->header.idx != idx) + /* wrong index */ + continue; + + return block_ptr; + } + } + return NULL; +} + +/*! + * Get the sector number of a given address. + */ +static uint8_t vrom_sector_num(const void* address) +{ + /* Get the offset from the base address */ + uint32_t offset = (uint32_t)address - VROM_START_ADDR; + return offset / VROM_SECT_SZ; +} + +/*! + * Get the block number of a given address. + */ +static uint8_t vrom_block_num(const void* address) +{ + /* Get the sector number */ + uint8_t sector = vrom_sector_num(address); + + /* Get the offset from the sector base */ + uint32_t offset = (uint32_t)(address + - (const void*)vrom_get_sector_hdr(sector)); + offset /= VROM_BLOCK_SZ; + return offset - 1; +} + +/*! + * (Erase and) Format a sector. + * + * @retval -EIO Erase failed + * @retval -EPERM Erase counter depleted. + */ +static int vrom_format_sector(const struct vrom_sector_idx_t* sector) +{ + uint8_t sector_num = vrom_sector_num(sector); + uint32_t cycles_remain = VROM_MAX_CYCLES; + if (sector->cycles_remain != UINT32_MAX) { + if (sector->cycles_remain == 0) + /* This sector is exhausted */ + return -EPERM; + + /* This sector has been formatted before */ + cycles_remain = sector->cycles_remain - 1; + if (FLASH_EraseSector(sector_num, VoltageRange_3)) + /* Erase failed */ + return -EIO; + } + + /* Program the new sector cycle counter */ + if (FLASH_ProgramWord((uint32_t)sector, + cycles_remain) == FLASH_COMPLETE) + return 0; /* All good */ + /* If we get here, then programming failed */ + return -EIO; +} + +/*! + * Find the next available block. + */ +static const struct vrom_data_block_t* vrom_find_free(uint8_t run_gc) +{ + int sector; + if (run_gc) { + for (sector = 0; sector < VROM_SECT_CNT; sector++) { + uint8_t block; + uint8_t used = 0; + const struct vrom_sector_idx_t* sect_hdr + = vrom_get_sector_hdr(sector); + if (sect_hdr->cycles_remain == UINT32_MAX) + /* Already erased */ + continue; + if (sect_hdr->cycles_remain == 0) + /* Depleted */ + continue; + + for (block = 0; block < VROM_SECT_APP_BLOCK_CNT; + block++) { + if (sect_hdr->flags[block]) { + used = 1; + break; + } + } + + if (!used) { + /* We can format this */ + vrom_format_sector(sect_hdr); + } + } + } + + for (sector = 0; sector < VROM_SECT_CNT; sector++) { + uint8_t block; + const struct vrom_sector_idx_t* sect_hdr + = vrom_get_sector_hdr(sector); + if (sect_hdr->cycles_remain == UINT32_MAX) { + /* Unformatted sector. */ + if (vrom_format_sector(sect_hdr)) + /* Couldn't format, keep looking */ + continue; + } + for (block = 0; block < VROM_SECT_APP_BLOCK_CNT; block++) { + if (sect_hdr->flags[block] == UINT16_MAX) + /* Success */ + return vrom_get_block(sector, block); + } + } + + /* No blocks free, but have we done garbage collection? */ + if (!run_gc) + return vrom_find_free(1); + + /* If we get here, then we weren't able to find a free block */ + return NULL; +} + +/*! + * Mark a block as being obsolete + */ +static int vrom_mark_obsolete(const struct vrom_data_block_t* block) +{ + const struct vrom_sector_idx_t* sector = + vrom_get_sector_hdr(vrom_sector_num(block)); + uint8_t block_num = vrom_block_num(block); + + /* Blank out the CRC */ + if (FLASH_ProgramWord((uint32_t)(&(block->header.crc32)), 0) + != FLASH_COMPLETE) + return -EIO; + /* Blank out the ROM ID */ + if (FLASH_ProgramByte((uint32_t)(&(block->header.rom)), 0) + != FLASH_COMPLETE) + return -EIO; + /* Blank out the index */ + if (FLASH_ProgramByte((uint32_t)(&(block->header.idx)), 0) + != FLASH_COMPLETE) + return -EIO; + /* Blank out the size */ + if (FLASH_ProgramByte((uint32_t)&(block->header.size), 0) + != FLASH_COMPLETE) + return -EIO; + /* Blank out the reserved byte */ + if (FLASH_ProgramByte((uint32_t)&(block->header.reserved), 0) + != FLASH_COMPLETE) + return -EIO; + /* Blank out the flags */ + if (FLASH_ProgramHalfWord( + (uint32_t)(&(sector->flags[block_num])), 0) + != FLASH_COMPLETE) + return -EIO; + return 0; +} + +/*! + * Write a new block. + */ +static int vrom_write_block(uint8_t rom, uint8_t idx, uint8_t size, + const uint8_t* in) +{ + /* Find a new home for the block */ + const struct vrom_data_block_t* block = vrom_find_free(0); + struct vrom_data_block_t new_block; + uint8_t* out = (uint8_t*)(&block); + uint32_t rem = sizeof(new_block); + + /* Prepare the new block */ + memset(&new_block, 0xff, sizeof(new_block)); + new_block.header.rom = rom; + new_block.header.idx = idx; + new_block.header.size = size; + memcpy(new_block.data, in, size); + new_block.header.crc32 = vrom_crc32(&new_block); + + /* Start writing out the block */ + in = (uint8_t*)(&new_block); + while(rem) { + if (*in != *out) { + if (FLASH_ProgramByte((uint32_t)out, *in) + != FLASH_COMPLETE) + /* Failed! */ + return -EIO; + } + in++; + out++; + rem--; + } + return size; +} + +/*! + * Re-write the given block if needed. + */ +static int vrom_rewrite_block(const struct vrom_data_block_t* block, + uint8_t size, const uint8_t* in) +{ + uint8_t obsolete = 0; + uint8_t rom = block->header.rom; + uint8_t idx = block->header.idx; + const uint8_t* cmp_block = block->data; + const uint8_t* cmp_in = in; + uint8_t cmp_sz = size; + int res; + while(cmp_sz) { + if (*cmp_block != *cmp_in) { + obsolete = 1; + break; + } + cmp_sz--; + cmp_block++; + cmp_in++; + } + + if (!obsolete) + /* The block is fine, leave it be. */ + return size; + + /* Mark the block as obsolete */ + res = vrom_mark_obsolete(block); + if (res) + return res; + return vrom_write_block(rom, idx, size, in); +} + +/*! + * Overwrite the start of a block. + */ +static int vrom_overwrite_block( + const struct vrom_data_block_t* block, + uint8_t offset, uint8_t size, const uint8_t* in) +{ + uint8_t data[VROM_DATA_SZ]; + uint16_t block_sz = block->header.size; + int res; + + if (!offset && (size >= block->header.size)) + /* Complete overwrite */ + return vrom_rewrite_block(block, size, in); + + if (offset) { + /* Overwrite end of block, possible expansion */ + block_sz = offset + size; + if (block_sz > VROM_DATA_SZ) + block_sz = VROM_DATA_SZ; + memcpy(data, block->data, offset); + memcpy(&data[offset], in, block_sz - offset); + } else { + /* Overwrite start of block, no size change */ + memcpy(data, in, size); + memcpy(&data[size], &(block->data[size]), + block_sz - size); + } + + res = vrom_rewrite_block(block, block_sz, data); + if (res < 0) + return res; + return block_sz; +} + +/*! + * Write data to the virtual EEPROM. + */ +static int vrom_write_internal(uint8_t rom, + uint16_t offset, uint16_t size, const uint8_t* in) +{ + /* Figure out our starting block and offset */ + uint8_t block_idx = offset / VROM_DATA_SZ; + uint8_t block_offset = offset % VROM_DATA_SZ; + int count = 0; + + /* Locate the first block */ + const struct vrom_data_block_t* block = vrom_find(rom, block_idx); + + uint8_t block_sz = VROM_DATA_SZ; + if (block_sz > (size + block_offset)) + block_sz = size + block_offset; + + if (!block) { + /* Create a new block */ + uint8_t data[VROM_DATA_SZ]; + int res; + memset(data, 0xff, sizeof(data)); + memcpy(&data[block_offset], in, + block_sz-block_offset); + res = vrom_write_block(rom, block_idx, block_sz, data); + if (res < 0) + return res; + } else { + /* Overwrite block */ + int res = vrom_overwrite_block(block, block_offset, + block_sz, in); + if (res < 0) + return res; + count += block_sz; + } + + block_idx++; + size -= block_sz - block_offset; + + while(size) { + /* Work out how much data to write */ + if (size < VROM_DATA_SZ) + block_sz = size; + else + block_sz = VROM_DATA_SZ; + + int res; + + /* Is there a block covering this range? */ + block = vrom_find(rom, block_idx); + if (block) + res = vrom_overwrite_block( + block, 0, block_sz, in); + else + res = vrom_write_block(rom, block_idx, + block_sz, in); + + if (res < 0) + return res; + + /* Successful write */ + count += res; + size -= res; + in += res; + offset += res; + } + return count; +} + +/*! + * Read data from a virtual EEPROM. + * @param rom ROM ID to start reading. + * @param offset Address offset into ROM to start reading. + * @param size Number of bytes to read from ROM. + * @param out Buffer to write ROM content to. + * @returns Number of bytes read from ROM. + * @retval -ENXIO ROM not found + * @retval -ESPIPE Offset past end of ROM. + */ +int vrom_read(uint8_t rom, uint16_t offset, uint16_t size, void* out) +{ + /* Figure out our starting block and offset */ + uint8_t block_idx = offset / VROM_DATA_SZ; + uint8_t block_offset = offset % VROM_DATA_SZ; + uint8_t block_sz; + int count = 0; + uint8_t* out_ptr = (uint8_t*)out; + + /* Locate the first block */ + const struct vrom_data_block_t* block = vrom_find(rom, block_idx); + + if (!block) + return -ENXIO; + + if (block_offset >= block->header.size) + return -ESPIPE; + + /* Copy the initial bytes */ + block_sz = block->header.size - block_offset; + if (block_sz > size) + block_sz = size; + memcpy(out_ptr, &(block->data[block_offset]), block_sz); + out_ptr += block_sz; + size -= block_sz; + count += block_sz; + + if (size) { + /* Look for the next block */ + block = vrom_find(rom, ++block_idx); + while(size && block) { + if (block->header.size <= size) + block_sz = block->header.size; + else + block_sz = size; + memcpy(out_ptr, block->data, block_sz); + out_ptr += block_sz; + size -= block_sz; + count += block_sz; + + block = vrom_find(rom, ++block_idx); + } + } + + return count; +} + +/*! + * Write data to a virtual EEPROM. + * @param rom ROM ID to start writing. + * @param offset Address offset into ROM to start writing. + * @param size Number of bytes to write to ROM. + * @param in Buffer to write ROM content from. + * @returns Number of bytes written to ROM. + * @retval -EIO Programming failed + * @retval -ENOSPC No free blocks available + */ +int vrom_write(uint8_t rom, uint16_t offset, uint16_t size, + const void* in) +{ + int res; + FLASH_Unlock(); + res = vrom_write_internal(rom, offset, size, in); + FLASH_Lock(); + return res; +} + +/*! + * Erase a virtual EEPROM. + * @param rom ROM ID to erase. + * @returns Number of bytes written to ROM. + * @retval -EIO Programming failed + * @retval -ENOSPC No free blocks available + */ +int vrom_erase(uint8_t rom) +{ + int sector, block; + FLASH_Unlock(); + + for (sector = 0; sector < VROM_SECT_CNT; sector++) { + const struct vrom_sector_idx_t* sect_hdr + = vrom_get_sector_hdr(sector); + if (sect_hdr->cycles_remain == UINT32_MAX) + /* unformatted */ + continue; + for (block = 0; block < VROM_SECT_APP_BLOCK_CNT; block++) { + int res; + const struct vrom_data_block_t* block_ptr; + if (sect_hdr->flags[block] == UINT16_MAX) + /* unformatted */ + continue; + if (sect_hdr->flags[block] == 0) + /* obsolete */ + continue; + + block_ptr = vrom_get_block(sector, block); + + /* Verify the content */ + if (vrom_crc32(block_ptr) + != block_ptr->header.crc32) + /* corrupt */ + continue; + + if (block_ptr->header.rom != rom) + /* different ROM */ + continue; + + /* + * Block is valid, for the correct ROM. Mark it + * obsolete. + */ + res = vrom_mark_obsolete(block_ptr); + if (res) + return res; + } + } + return 0; +}