stm32f4_vrom: Virtual EEPROM driver for STM32F4.
authorsjlongland <sjlongland@01035d8c-6547-0410-b346-abe4f91aad63>
Sat, 26 Sep 2015 10:32:45 +0000 (10:32 +0000)
committersjlongland <sjlongland@01035d8c-6547-0410-b346-abe4f91aad63>
Sat, 26 Sep 2015 10:32:45 +0000 (10:32 +0000)
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

codec2-dev/stm32/inc/stm32f4_vrom.h [new file with mode: 0644]
codec2-dev/stm32/src/stm32f4_vrom.c [new file with mode: 0644]

diff --git a/codec2-dev/stm32/inc/stm32f4_vrom.h b/codec2-dev/stm32/inc/stm32f4_vrom.h
new file mode 100644 (file)
index 0000000..e878b9c
--- /dev/null
@@ -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 <me@vk4msl.id.au>
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdint.h>
+#include <errno.h>
+
+/*!
+ * 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 (file)
index 0000000..eac060c
--- /dev/null
@@ -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 <me@vk4msl.id.au>
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#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;
+}