Squashed commit of the following:

Author: Juha Niskanen <juha.niskanen@haltian.com>
Date:   Mon Feb 19 15:03:17 2018 -0600

    drivers/mtd:  mtd_config.c:  Add still more error handling (to detect bad underlying flash implementations)
    drivers/mtd:  mtd_config.c:  Remove MTD_ERASE that was erasing data block instead of erase block.  This is a partial revert of 4f18b4.  Reported-by: Pascal Speck <iktek01@yahoo.com>
    arch/arm/src/stm32l4:  stm32l4_flash: change flash programming to use page buffer for unaligned writes.
This commit is contained in:
Gregory Nutt 2018-02-19 15:03:47 -06:00
parent bd81664b31
commit 8572837d9e
3 changed files with 225 additions and 109 deletions

View File

@ -67,7 +67,7 @@
/* All sectors are 128KB and are uniform in size.
* The only execption is sector 0 which is subdivided into two small sectors
* of 8KB and one larger sector of 112KB.
* The page size is 515 bytes. However, the smallest thing that can be
* The page size is 512 bytes. However, the smallest thing that can be
* erased is four pages. We will refer to this as a "cluster".
*/
@ -577,7 +577,7 @@ ssize_t up_progmem_ispageerased(size_t cluster)
return -EFAULT;
}
/* Flush and invalidate nvalidate D-Cache for this address range */
/* Flush and invalidate D-Cache for this address range */
address = (cluster << SAMV7_CLUSTER_SHIFT) + SAMV7_PROGMEM_START;
arch_flush_dcache(address, address + SAMV7_CLUSTER_SIZE);

View File

@ -55,6 +55,7 @@
#include <semaphore.h>
#include <assert.h>
#include <errno.h>
#include <string.h>
#include "stm32l4_rcc.h"
#include "stm32l4_waste.h"
@ -75,26 +76,37 @@
* Pre-processor Definitions
************************************************************************************/
#define FLASH_KEY1 0x45670123
#define FLASH_KEY2 0xCDEF89AB
#define FLASH_KEY1 0x45670123
#define FLASH_KEY2 0xCDEF89AB
#define OPTBYTES_KEY1 0x08192A3B
#define OPTBYTES_KEY2 0x4C5D6E7F
#define OPTBYTES_KEY1 0x08192A3B
#define OPTBYTES_KEY2 0x4C5D6E7F
#define FLASH_PAGE_SIZE STM32L4_FLASH_PAGESIZE
#define FLASH_PAGE_WORDS (FLASH_PAGE_SIZE / 4)
#define FLASH_PAGE_MASK (FLASH_PAGE_SIZE - 1)
#define FLASH_PAGE_SHIFT (11) /* 2**11 = 2048B */
#define FLASH_BYTE2PAGE(o) ((o) >> FLASH_PAGE_SHIFT)
#define FLASH_CR_PAGE_ERASE FLASH_CR_PER
#define FLASH_SR_WRITE_PROTECTION_ERROR FLASH_SR_WRPERR
/* All errors for Standard Programming, not for other operations. */
#define FLASH_SR_ALLERRS (FLASH_SR_PGSERR | FLASH_SR_SIZERR | \
FLASH_SR_PGAERR | FLASH_SR_WRPERR | \
FLASH_SR_PROGERR)
#define FLASH_SR_ALLERRS (FLASH_SR_PGSERR | FLASH_SR_SIZERR | \
FLASH_SR_PGAERR | FLASH_SR_WRPERR | \
FLASH_SR_PROGERR)
#ifndef MIN
# define MIN(a, b) ((a) < (b) ? (a) : (b))
#endif
/************************************************************************************
* Private Data
************************************************************************************/
static sem_t g_sem = SEM_INITIALIZER(1);
static uint32_t g_page_buffer[FLASH_PAGE_WORDS];
/************************************************************************************
* Private Functions
@ -167,6 +179,22 @@ static inline void flash_optbytes_lock(void)
flash_lock();
}
static inline void flash_erase(size_t page)
{
finfo("erase page %u\n", page);
modifyreg32(STM32L4_FLASH_CR, 0, FLASH_CR_PAGE_ERASE);
modifyreg32(STM32L4_FLASH_CR, FLASH_CR_PNB_MASK, FLASH_CR_PNB(page));
modifyreg32(STM32L4_FLASH_CR, 0, FLASH_CR_START);
while (getreg32(STM32L4_FLASH_SR) & FLASH_SR_BSY)
{
up_waste();
}
modifyreg32(STM32L4_FLASH_CR, FLASH_CR_PAGE_ERASE, 0);
}
/************************************************************************************
* Public Functions
************************************************************************************/
@ -290,22 +318,12 @@ ssize_t up_progmem_erasepage(size_t page)
return -EFAULT;
}
/* Erase single page */
sem_lock();
/* Get flash ready and begin erasing single page. */
flash_unlock();
modifyreg32(STM32L4_FLASH_CR, 0, FLASH_CR_PAGE_ERASE);
modifyreg32(STM32L4_FLASH_CR, FLASH_CR_PNB_MASK, FLASH_CR_PNB(page));
modifyreg32(STM32L4_FLASH_CR, 0, FLASH_CR_START);
while (getreg32(STM32L4_FLASH_SR) & FLASH_SR_BSY)
{
up_waste();
}
modifyreg32(STM32L4_FLASH_CR, FLASH_CR_PAGE_ERASE, 0);
flash_erase(page);
flash_lock();
sem_unlock();
@ -347,90 +365,133 @@ ssize_t up_progmem_ispageerased(size_t page)
return bwritten;
}
ssize_t up_progmem_write(size_t addr, const void *buf, size_t count)
ssize_t up_progmem_write(size_t addr, const void *buf, size_t buflen)
{
uint32_t *word = (uint32_t *)buf;
size_t written = count;
uint32_t *dest;
const uint32_t *src;
size_t written;
size_t xfrsize;
size_t offset;
size_t page;
int i;
int ret = OK;
/* STM32L4 requires double-word access and alignment. */
if (addr & 7)
{
return -EINVAL;
}
/* But we can complete single-word writes by writing the
* erase value 0xffffffff as second word ourselves, so
* allow odd number of words here.
*/
if (count & 3)
{
return -EINVAL;
}
/* Check for valid address range. */
offset = addr;
if (addr >= STM32L4_FLASH_BASE)
{
addr -= STM32L4_FLASH_BASE;
offset -= STM32L4_FLASH_BASE;
}
if ((addr + count) > STM32L4_FLASH_SIZE)
if (offset + buflen > STM32L4_FLASH_SIZE)
{
return -EFAULT;
}
/* Get the page number corresponding to the flash offset and the byte
* offset into the page. Align write destination to page boundary.
*/
page = FLASH_BYTE2PAGE((uint32_t)offset);
offset &= FLASH_PAGE_MASK;
dest = (uint32_t *)((uint8_t *)addr - offset);
written = 0;
sem_lock();
/* Get flash ready and begin flashing. */
flash_unlock();
modifyreg32(STM32L4_FLASH_CR, 0, FLASH_CR_PG);
/* Loop until all of the data has been written */
for (addr += STM32L4_FLASH_BASE; count; count -= 8, word += 2, addr += 8)
while (buflen > 0)
{
uint32_t second_word;
/* How much can we write into this page? */
/* Write first word. */
xfrsize = MIN((size_t)FLASH_PAGE_SIZE - offset, buflen);
putreg32(*word, addr);
/* Do we need to use the intermediate buffer? */
/* Write second word and wait to complete. */
second_word = (count == 4) ? 0xffffffff : *(word + 1);
putreg32(second_word, (addr + 4));
while (getreg32(STM32L4_FLASH_SR) & FLASH_SR_BSY)
if (offset == 0 && xfrsize == FLASH_PAGE_SIZE)
{
up_waste();
/* No, we can take the data directly from the user buffer */
src = (const uint32_t *)buf;
}
else
{
/* Yes, copy data into global page buffer */
if (offset > 0)
{
memcpy(g_page_buffer, dest, offset);
}
memcpy((uint8_t *)g_page_buffer + offset, buf, xfrsize);
if (offset + xfrsize < FLASH_PAGE_SIZE)
{
memcpy((uint8_t *)g_page_buffer + offset + xfrsize,
(const uint8_t *)dest + offset + xfrsize,
FLASH_PAGE_SIZE - offset - xfrsize);
}
src = g_page_buffer;
}
/* Verify */
/* Erase the page. Unlike most flash chips, STM32L4 is unable to
* write back existing data read from page without erase.
*/
if (getreg32(STM32L4_FLASH_SR) & FLASH_SR_WRITE_PROTECTION_ERROR)
flash_erase(page);
/* Write the page. Must be with double-words. */
modifyreg32(STM32L4_FLASH_CR, 0, FLASH_CR_PG);
for (i = 0; i < FLASH_PAGE_WORDS; i += 2)
{
ret = -EROFS;
goto out;
*dest++ = *src++;
*dest++ = *src++;
while (getreg32(STM32L4_FLASH_SR) & FLASH_SR_BSY)
{
up_waste();
}
/* Verify */
if (getreg32(STM32L4_FLASH_SR) & FLASH_SR_WRITE_PROTECTION_ERROR)
{
modifyreg32(STM32L4_FLASH_CR, FLASH_CR_PG, 0);
ret = -EROFS;
goto out;
}
if (getreg32(dest-1) != *(src-1) || getreg32(dest-2) != *(src-2))
{
modifyreg32(STM32L4_FLASH_CR, FLASH_CR_PG, 0);
ret = -EIO;
goto out;
}
}
if (getreg32(addr) != *word || getreg32((addr + 4)) != second_word)
{
ret = -EIO;
goto out;
}
modifyreg32(STM32L4_FLASH_CR, FLASH_CR_PG, 0);
if (count == 4)
{
break;
}
/* Adjust pointers and counts for the next time through the loop */
written += xfrsize;
addr += xfrsize;
dest = (uint32_t *)addr;
buf = (void *)((uintptr_t)buf + xfrsize);
buflen -= xfrsize;
page++;
}
out:
modifyreg32(STM32L4_FLASH_CR, FLASH_CR_PG, 0);
/* If there was an error, clear all error flags in status
* register (rc_w1 register so do this by writing the
* error bits).

View File

@ -1,7 +1,7 @@
/****************************************************************************
* drivers/mtd/mtd_config.c
*
* Copyright (C) 2014, 2017 Gregory Nutt. All rights reserved.
* Copyright (C) 2014, 2017-2018 Gregory Nutt. All rights reserved.
* Copyright (C) 2013 Ken Pettit. All rights reserved.
* Author: Ken Pettit <pettitkd@gmail.com>
* With Updates from Gregory Nutt <gnutt@nuttx.org>
@ -153,8 +153,8 @@ static const struct file_operations mtdconfig_fops =
*
****************************************************************************/
static int mtdconfig_readbytes(FAR struct mtdconfig_struct_s *dev, int offset,
FAR uint8_t *pdata, int readlen)
static int mtdconfig_readbytes(FAR struct mtdconfig_struct_s *dev, int offset,
FAR uint8_t *pdata, int readlen)
{
off_t bytestoread = readlen;
off_t bytesthisblock, firstbyte;
@ -250,8 +250,8 @@ errout:
*
****************************************************************************/
static int mtdconfig_writebytes(FAR struct mtdconfig_struct_s *dev, int offset,
FAR const uint8_t *pdata, int writelen)
static int mtdconfig_writebytes(FAR struct mtdconfig_struct_s *dev, int offset,
FAR const uint8_t *pdata, int writelen)
{
int ret = OK;
@ -286,17 +286,6 @@ static int mtdconfig_writebytes(FAR struct mtdconfig_struct_s *dev, int offset,
goto errout;
}
/* Now erase the block */
ret = MTD_ERASE(dev->mtd, block, 1);
if (ret < 0)
{
/* Error erasing the block */
ret = -EIO;
goto errout;
}
index = offset - block * dev->blocksize;
bytes_this_block = dev->blocksize - index;
if (bytes_this_block > writelen)
@ -563,11 +552,11 @@ read_next:
* block is available in the partition.
*
* Returned Value:
* offset to the next available entry (after consolidation)..
* Offset to the next available entry (after consolidation).
*
****************************************************************************/
static off_t mtdconfig_ramconsolidate(FAR struct mtdconfig_struct_s *dev)
static off_t mtdconfig_ramconsolidate(FAR struct mtdconfig_struct_s *dev)
{
FAR uint8_t *pBuf;
FAR struct mtdconfig_header_s *phdr;
@ -601,7 +590,6 @@ static off_t mtdconfig_ramconsolidate(FAR struct mtdconfig_struct_s *dev)
{
/* Error doing block read */
dst_offset = 0;
goto errout;
}
@ -612,7 +600,6 @@ static off_t mtdconfig_ramconsolidate(FAR struct mtdconfig_struct_s *dev)
{
/* Error erasing the block */
dst_offset = 0;
goto errout;
}
@ -623,17 +610,25 @@ static off_t mtdconfig_ramconsolidate(FAR struct mtdconfig_struct_s *dev)
sig[0] = 'C';
sig[1] = 'D';
sig[2] = CONFIGDATA_FORMAT_VERSION;
mtdconfig_writebytes(dev, 0, sig, sizeof(sig));
ret = mtdconfig_writebytes(dev, 0, sig, sizeof(sig));
if (ret != sizeof(sig))
{
/* Cannot write even the signature. */
ret = -EIO;
goto errout;
}
}
/* Copy active items back to the MTD device */
/* Copy active items back to the MTD device. */
while (src_offset < dev->erasesize)
{
phdr = (FAR struct mtdconfig_header_s *) &pBuf[src_offset];
if (phdr->id == MTD_ERASED_ID)
{
/* No more data in this erase block */
/* No more data in this erase block. */
src_offset = dev->erasesize;
continue;
@ -650,7 +645,7 @@ static off_t mtdconfig_ramconsolidate(FAR struct mtdconfig_struct_s *dev)
if (bytes_left_in_block < sizeof(*phdr) + phdr->len)
{
/* Item won't fit in the destination block. Move to
* the next block
* the next block.
*/
dst_block++;
@ -663,19 +658,29 @@ static off_t mtdconfig_ramconsolidate(FAR struct mtdconfig_struct_s *dev)
DEBUGASSERT(dst_block != dev->neraseblocks);
}
/* Now Write the item to the current dst_offset location */
/* Now write the item to the current dst_offset location. */
ret = mtdconfig_writebytes(dev, dst_offset, (uint8_t *) phdr,
sizeof(hdr));
if (ret < 0)
if (ret != sizeof(hdr))
{
dst_offset = 0;
/* I/O Error! */
ret = -EIO;
goto errout;
}
dst_offset += sizeof(hdr);
ret = mtdconfig_writebytes(dev, dst_offset, &pBuf[src_offset
+ sizeof(hdr)], phdr->len);
ret = mtdconfig_writebytes(dev, dst_offset,
&pBuf[src_offset + sizeof(hdr)], phdr->len);
if (ret != phdr->len)
{
/* I/O Error! */
ret = -EIO;
goto errout;
}
dst_offset += phdr->len;
/* Test if enough space in dst block for another header */
@ -705,9 +710,13 @@ static off_t mtdconfig_ramconsolidate(FAR struct mtdconfig_struct_s *dev)
src_offset = CONFIGDATA_BLOCK_HDR_SIZE;
}
errout:
kmm_free(pBuf);
return dst_offset;
errout:
kmm_free(pBuf);
ferr("ERROR: fail ram consolidate: %d\n", ret);
return 0;
}
/****************************************************************************
@ -720,7 +729,7 @@ errout:
* partition as it goes.
*
* Returned Value:
* offset to the next available entry (after consolidation)..
* Offset to the next available entry (after consolidation).
*
****************************************************************************/
@ -781,9 +790,17 @@ static off_t mtdconfig_consolidate(FAR struct mtdconfig_struct_s *dev)
sig[0] = 'C';
sig[1] = 'D';
sig[2] = CONFIGDATA_FORMAT_VERSION;
mtdconfig_writebytes(dev, 0, sig, sizeof(sig));
/* Now consolidate entries */
ret = mtdconfig_writebytes(dev, 0, sig, sizeof(sig));
if (ret != sizeof(sig))
{
/* Cannot write even the signature. */
ret = -EIO;
goto errout;
}
/* Now consolidate entries. */
src_block = 1;
dst_block = 0;
@ -800,6 +817,7 @@ retry_relocate:
{
/* I/O Error! */
ret = -EIO;
goto errout;
}
@ -840,7 +858,15 @@ retry_relocate:
/* Copy this entry to the destination */
mtdconfig_writebytes(dev, dst_offset, (uint8_t *) &hdr, sizeof(hdr));
ret = mtdconfig_writebytes(dev, dst_offset, (uint8_t *) &hdr, sizeof(hdr));
if (ret != sizeof(hdr))
{
/* I/O Error! */
ret = -EIO;
goto errout;
}
src_offset += sizeof(hdr);
dst_offset += sizeof(hdr);
@ -856,8 +882,23 @@ retry_relocate:
/* Move the data. */
mtdconfig_readbytes(dev, src_offset, pBuf, bytes);
mtdconfig_writebytes(dev, dst_offset, pBuf, bytes);
ret = mtdconfig_readbytes(dev, src_offset, pBuf, bytes);
if (ret != OK)
{
/* I/O Error! */
ret = -EIO;
goto errout;
}
ret = mtdconfig_writebytes(dev, dst_offset, pBuf, bytes);
if (ret != bytes)
{
/* I/O Error! */
ret = -EIO;
goto errout;
}
/* Update control variables */
@ -916,10 +957,15 @@ retry_relocate:
}
}
kmm_free(pBuf);
return dst_offset;
errout:
kmm_free(pBuf);
ferr("ERROR: fail consolidate: %d\n", ret);
return 0;
}
#endif /* CONFIG_MTD_CONFIG_RAM_CONSOLIDATE */
/****************************************************************************
@ -1108,7 +1154,15 @@ retry:
sig[0] = 'C';
sig[1] = 'D';
sig[2] = CONFIGDATA_FORMAT_VERSION;
mtdconfig_writebytes(dev, 0, sig, sizeof(sig));
ret = mtdconfig_writebytes(dev, 0, sig, sizeof(sig));
if (ret != sizeof(sig))
{
/* Cannot write even the signature. */
ret = -EIO;
goto errout;
}
/* Now go try to read the signature again (as verification) */
@ -1147,6 +1201,7 @@ retry:
/* Now find a new entry for this config data */
retrycount = 0;
retry_find:
offset = mtdconfig_findfirstentry(dev, &hdr);
if (offset > 0 && hdr.id == MTD_ERASED_ID)
@ -1226,7 +1281,7 @@ retry_find:
hdr.flags = MTD_ERASED_FLAGS;
ret = mtdconfig_writebytes(dev, offset, (uint8_t *)&hdr, sizeof(hdr));
if (ret < 0)
if (ret != sizeof(hdr))
{
/* Cannot write even header! */