diff --git a/src/main/drivers/flash_w25n.c b/src/main/drivers/flash_w25n.c index c23a1bddfbd..fe3ab4ad221 100644 --- a/src/main/drivers/flash_w25n.c +++ b/src/main/drivers/flash_w25n.c @@ -141,6 +141,9 @@ static bool couldBeBusy = false; static timeMs_t timeoutAt = 0; +// The page held in the device's internal data buffer, or UINT32_MAX if its contents are unknown +static uint32_t currentPage = UINT32_MAX; + static bool w25n_waitForReadyInternal(void); static void w25n_setTimeout(timeMs_t timeoutMillis) @@ -216,9 +219,20 @@ static bool w25n_waitForReadyInternal(void) return true; } +/** + * Wait for the device to become ready. + * + * A timeout of zero means "wait for the deadline the pending operation has already armed", + * which is how flashPartitionErase() waits for a block erase to complete (see also + * m25p16_waitForReady). Arming a fresh zero length timeout instead would replace the erase + * timeout with a deadline that has already expired, and the wait would give up immediately. + */ bool w25n_waitForReady(timeMs_t timeoutMillis) { - w25n_setTimeout(timeoutMillis); + if (timeoutMillis > 0) { + w25n_setTimeout(timeoutMillis); + } + return w25n_waitForReadyInternal(); } @@ -300,10 +314,19 @@ bool w25n_detect(uint32_t chipID) */ void w25n_eraseSector(uint32_t address) { - w25n_waitForReadyInternal(); + // A device that is still busy ignores both the write enable and the erase instruction, so + // issuing them anyway would leave the block unerased without any sign of it. Give up on this + // block instead; the caller sees the result when it reads the flash back. + if (!w25n_waitForReadyInternal()) { + return; + } + w25n_writeEnable(); w25n_performCommandWithPageAddress(W25N_INSTRUCTION_BLOCK_ERASE, W25N_LINEAR_TO_PAGE(address)); w25n_setTimeout(W25N_TIMEOUT_BLOCK_ERASE_MS); + + // The data buffer may still hold a page of the block that is being erased + currentPage = UINT32_MAX; } // W25N does not support full chip erase. @@ -313,6 +336,9 @@ void w25n_eraseCompletely(void) for (uint32_t block = 0; block < geometry.sectors; block++) { w25n_eraseSector(W25N_BLOCK_TO_LINEAR(block)); } + + // Let the last block finish, so that the device is readable once the erase returns + w25n_waitForReadyInternal(); } static void w25n_programDataLoad(uint16_t columnAddress, const uint8_t *data, int length) @@ -377,7 +403,6 @@ bool bufferDirty = false; bool isProgramming = false; static uint32_t programStartAddress; static uint32_t programLoadAddress; -static uint32_t currentPage = UINT32_MAX; void w25n_pageProgramBegin(uint32_t address) { diff --git a/src/main/io/flashfs.c b/src/main/io/flashfs.c index 3140e0e1d52..34ba4dd22c3 100644 --- a/src/main/io/flashfs.c +++ b/src/main/io/flashfs.c @@ -71,9 +71,25 @@ static void flashfsSetTailAddress(uint32_t address) void flashfsEraseCompletely(void) { + // Drain the driver's page cache before erasing; the free-space scan reuses the chip buffer. + flashFlush(); flashPartitionErase(flashPartition); flashfsClearBuffer(); - flashfsSetTailAddress(0); + + /* Assuming the erase succeeded would report an empty device from RAM while the flash still + * holds the old log, which only becomes apparent after the next reboot. Locate the start of + * the free space on the chip instead, exactly as flashfsInit() does at boot. + * + * A chip that is still erasing cannot be examined, as reads would time out and the device + * would be taken for full. That is the case for a NOR chip erased with a single bulk erase + * instruction, which completes long after this function returns; there the erase is assumed + * to succeed as before, and the next boot corrects the offset if it did not. + */ + if (flashIsReady()) { + flashfsSetTailAddress(flashfsIdentifyStartOfFreeSpace()); + } else { + flashfsSetTailAddress(0); + } } void flashfsClose(void)