Skip to content

Commit

Permalink
flashfs: Implement rolling erase
Browse files Browse the repository at this point in the history
A new option `blackbox_rolling_erase` is added to enable
automatic rolling erase when flashfs volume is full.
  • Loading branch information
gongtao0607 committed Jan 6, 2025
1 parent 080dec0 commit e4704b0
Show file tree
Hide file tree
Showing 9 changed files with 175 additions and 13 deletions.
2 changes: 2 additions & 0 deletions src/main/cli/cli.c
Original file line number Diff line number Diff line change
Expand Up @@ -2682,10 +2682,12 @@ static void cliFlashInfo(const char *cmdName, char *cmdline)
FLASH_PARTITION_SECTOR_COUNT(flashPartition) * layout->sectorSize,
flashfsGetOffset()
);
#ifdef USE_FLASHFS_LOOP
cliPrintLinef("FlashFSLoop Head = 0x%08x, Tail = 0x%08x",
flashfsGetHeadAddress(),
flashfsGetTailAddress());
#endif
#endif
}


Expand Down
3 changes: 2 additions & 1 deletion src/main/cli/settings.c
Original file line number Diff line number Diff line change
Expand Up @@ -805,6 +805,7 @@ const clivalue_t valueTable[] = {

#ifdef USE_FLASHFS_LOOP
{ "blackbox_initial_erase", VAR_UINT32 | MASTER_VALUE, .config.u32Max = UINT32_MAX, PG_BLACKBOX_CONFIG, offsetof(blackboxConfig_t, initialEraseFreeSpace) },
{ "blackbox_rolling_erase", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_OFF_ON }, PG_BLACKBOX_CONFIG, offsetof(blackboxConfig_t, rollingErase) },
#endif
#endif

Expand Down Expand Up @@ -1753,7 +1754,7 @@ const clivalue_t valueTable[] = {
{ "sbus_out_source_range_low", VAR_INT16 | MASTER_VALUE | MODE_ARRAY, .config.array.length = SBUS_OUT_CHANNELS, PG_DRIVER_SBUS_OUT_CONFIG, offsetof(sbusOutConfig_t, sourceRangeLow) },
{ "sbus_out_source_range_high", VAR_INT16 | MASTER_VALUE | MODE_ARRAY, .config.array.length = SBUS_OUT_CHANNELS, PG_DRIVER_SBUS_OUT_CONFIG, offsetof(sbusOutConfig_t, sourceRangeHigh) },
{ "sbus_out_frame_rate", VAR_UINT8 | MASTER_VALUE, .config.minmaxUnsigned = {25, 250}, PG_DRIVER_SBUS_OUT_CONFIG, offsetof(sbusOutConfig_t, frameRate) },
{ "sbus_out_pinswap", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_OFF_ON}, PG_DRIVER_SBUS_OUT_CONFIG, offsetof(sbusOutConfig_t, pinSwap) },
{ "sbus_out_pinswap", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_OFF_ON }, PG_DRIVER_SBUS_OUT_CONFIG, offsetof(sbusOutConfig_t, pinSwap) },
#endif
};

Expand Down
88 changes: 87 additions & 1 deletion src/main/io/flashfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,21 @@

#include "pg/blackbox.h"

/*
* How background erase works:
* 1. When start programming (flush) a page, if erase is needed and can be
* started, set FLASHFS_ROLLING_ERASE_PENDING.
* 2. No more flush can be done when FLASHFS_ROLLING_ERASE_PENDING.
* 3. When page program finishes, EraseAsync task picks up the erase task and
* sets FLASHFS_ROLLING_ERASING.
* 4. When erase finishes, EraseAsync task resets state to FLASHFS_IDLE.
*/
typedef enum {
FLASHFS_IDLE,
FLASHFS_ALL_ERASING,
FLASHFS_INITIAL_ERASING,
FLASHFS_ROLLING_ERASE_PENDING,
FLASHFS_ROLLING_ERASING,
} flashfsState_e;

static const flashPartition_t *flashPartition = NULL;
Expand Down Expand Up @@ -268,24 +279,78 @@ static uint32_t flashfsWriteBuffers(uint8_t const **buffers, uint32_t *bufferSiz

// It's OK to overwrite the buffer addresses/lengths being passed in

// If sync is true, block until the FLASH device is ready, otherwise return 0 if the device isn't ready
// If sync is true, block until the FLASH device is ready, otherwise return
// 0 if the device isn't ready
if (sync) {
while (!flashIsReady());
/*
* Wait for any flashfs erase to complete.
* Note: we shouldn't reach inside since there's no such real world
* scenario (sync = 1 and flashfsState != FLASHFS_IDLE).
*/
while (flashfsState != FLASHFS_IDLE) {
flashfsEraseAsync();
}
} else {
if (!flashIsReady()) {
return 0;
}
/*
* Also bail out if we are running any of the erases.
* There are a few cases:
* * FLASHFS_ARMING_ERASING: logging is running ("switch" mode) and
* arming erase has triggered or running. We can't write.
* * FLASHFS_ROLLING_ERASE_PENDING: background erase is pending. We
* can't write. (If we write, those bytes will be discarded on
* erase).
* * FLASHFS_ROLLING_ERASING: technically we can write (under this
* state and flashIsReady()), because background erase erases only 1
* sector. For simplicity, we wait for the task to update the state.
* * FLASHFS_ALL_ERASING: we can't write. We may land between erase
* sectors.
*/
if (flashfsState != FLASHFS_IDLE) {
return 0;
}
}

// Are we at EOF already? Abort.
if (flashfsIsEOF()) {
#ifdef USE_FLASHFS_LOOP
// If EOF, request an background erase unconditionally
if (blackboxConfig()->rollingErase) {
flashfsState = FLASHFS_ROLLING_ERASE_PENDING;
}
#endif
return 0;
}

#ifdef CHECK_FLASH
checkFlashPtr = tailAddress;
#endif

#ifdef USE_FLASHFS_LOOP
// Check if background erase is needed. Why here? Because we can only
// erase when a page (aligned) program is completed.
const uint32_t freeSpace = flashfsSize - flashfsGetOffset();
if (blackboxConfig()->rollingErase &&
freeSpace < flashGeometry->sectorSize) {
// See if we are finishing a page.
uint32_t bytes_to_write = 0;
if (bufferCount > 0)
bytes_to_write += bufferSizes[0];
if (bufferCount > 1)
bytes_to_write += bufferSizes[1];
if (tailAddress / flashGeometry->pageSize !=
(tailAddress + bytes_to_write) / flashGeometry->pageSize) {
// We will write to or write over a page boundary. We can erase when
// this write is done.
flashfsState = FLASHFS_ROLLING_ERASE_PENDING;
}
}

#endif

flashPageProgramBegin(tailAddress, flashfsWriteCallback);

/* Mark that data has yet to be written. There is no race condition as the DMA engine is known
Expand Down Expand Up @@ -448,17 +513,36 @@ void flashfsEraseAsync(void)
if (initialEraseSectors > 0) {
flashEraseSector(headAddress);
initialEraseSectors--;
// We immediately set the head before erase is completed.
// This should be fine since write will be blocked until this
// state is cleared.
headAddress =
flashfsAddressShift(headAddress, flashGeometry->sectorSize);
LED1_TOGGLE;
} else {
flashfsState = FLASHFS_IDLE;
LED1_OFF;
}
} else if (flashfsState == FLASHFS_ROLLING_ERASE_PENDING) {
flashEraseSector(headAddress);
headAddress =
flashfsAddressShift(headAddress, flashGeometry->sectorSize);
flashfsState = FLASHFS_ROLLING_ERASING;
LED1_TOGGLE;
} else if (flashfsState == FLASHFS_ROLLING_ERASING) {
flashfsState = FLASHFS_IDLE;
LED1_OFF;
}
}
}

void flashfsSeekAbs(uint32_t offset)
{
flashfsFlushSync();

flashfsSetTailAddress(flashfsAddressShift(headAddress, offset));
}

void flashfsSeekPhysical(uint32_t offset)
{
flashfsFlushSync();
Expand Down Expand Up @@ -706,6 +790,8 @@ void flashfsInit(void)

// Start the file pointer off at the beginning of free space so caller can start writing immediately
flashfsSeekPhysical(flashfsIdentifyStartOfFreeSpace());

flashfsState = FLASHFS_IDLE;
}

#ifdef USE_FLASH_TOOLS
Expand Down
2 changes: 1 addition & 1 deletion src/main/io/flashfs.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ bool flashfsVerifyEntireFlash(void);

#ifdef USE_FLASHFS_LOOP
void flashfsLoopInitialErase();
#endif

uint32_t flashfsGetHeadAddress();
uint32_t flashfsGetTailAddress();
#endif
5 changes: 5 additions & 0 deletions src/main/msp/msp.c
Original file line number Diff line number Diff line change
Expand Up @@ -1722,13 +1722,15 @@ static bool mspProcessOutCommand(int16_t cmdMSP, sbuf_t *dst)
sbufWriteU16(dst, blackboxConfig()->denom);
sbufWriteU32(dst, blackboxConfig()->fields);
sbufWriteU32(dst, blackboxConfig()->initialEraseFreeSpace);
sbufWriteU8(dst, blackboxConfig()->rollingErase);
#else
sbufWriteU8(dst, 0); // Blackbox not supported
sbufWriteU8(dst, 0);
sbufWriteU8(dst, 0);
sbufWriteU16(dst, 0);
sbufWriteU32(dst, 0);
sbufWriteU32(dst, 0);
sbufWriteU8(dst, 0);
#endif
break;

Expand Down Expand Up @@ -2768,6 +2770,9 @@ static mspResult_e mspProcessInCommand(mspDescriptor_t srcDesc, int16_t cmdMSP,
blackboxConfigMutable()->initialEraseFreeSpace =
sbufReadU32(src);
}
if (sbufBytesRemaining(src) >= 1) {
blackboxConfigMutable()->rollingErase = sbufReadU8(src);
}
}
break;
#endif
Expand Down
1 change: 1 addition & 0 deletions src/main/pg/blackbox.c
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ PG_RESET_TEMPLATE(blackboxConfig_t, blackboxConfig,
BIT(FLIGHT_LOG_FIELD_SELECT_MOTOR) |
BIT(FLIGHT_LOG_FIELD_SELECT_SERVO),
.initialEraseFreeSpace = 0,
.rollingErase = 0,
);

#endif
1 change: 1 addition & 0 deletions src/main/pg/blackbox.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ typedef struct blackboxConfig_s {
uint16_t denom;
uint32_t fields;
uint32_t initialEraseFreeSpace;
uint8_t rollingErase;
} blackboxConfig_t;

PG_DECLARE(blackboxConfig_t, blackboxConfig);
82 changes: 75 additions & 7 deletions src/test/unit/flashfs_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ TEST_F(FlashFSTestBase, flashfsWrite)
TEST_F(FlashFSTestBase, flashfsWriteOverFlashSize)
{
flashfsInit();
blackboxConfigMutable()->rollingErase = false;

// Unexpectedly, the flashfs can't handle EOF if writes are not aligned to
// BLOCK_SIZE (2048) Let's just ignore this bug.
// constexpr uint32_t kBufferSize = FLASHFS_WRITE_BUFFER_USABLE - 10;
Expand Down Expand Up @@ -209,6 +211,9 @@ TEST_F(FlashFSLoopTest, StartFromZero)
flashfsInit();
EXPECT_EQ(headAddress, 0);
EXPECT_EQ(tailAddress, sector_size_ + page_size_);
EXPECT_TRUE(flash_emulator_->IsErased(tailAddress, sector_size_));

EXPECT_FALSE(flashfsIsEOF());
}

TEST_F(FlashFSLoopTest, Flat)
Expand All @@ -222,6 +227,9 @@ TEST_F(FlashFSLoopTest, Flat)
flashfsInit();
EXPECT_EQ(headAddress, sector_size_);
EXPECT_EQ(tailAddress, 2 * sector_size_ + page_size_);
EXPECT_TRUE(flash_emulator_->IsErased(tailAddress, sector_size_));

EXPECT_FALSE(flashfsIsEOF());
}

TEST_F(FlashFSLoopTest, Wrapped1)
Expand All @@ -240,6 +248,7 @@ TEST_F(FlashFSLoopTest, Wrapped1)
flashfsInit();
EXPECT_EQ(headAddress, kStartOfLastSector);
EXPECT_EQ(tailAddress, page_size_);
EXPECT_TRUE(flash_emulator_->IsErased(tailAddress, sector_size_));
}

TEST_F(FlashFSLoopTest, Wrapped2)
Expand All @@ -253,6 +262,7 @@ TEST_F(FlashFSLoopTest, Wrapped2)
flashfsInit();
EXPECT_EQ(headAddress, sector_size_);
EXPECT_EQ(tailAddress, 0);
EXPECT_TRUE(flash_emulator_->IsErased(tailAddress, sector_size_));
}

TEST_F(FlashFSLoopTest, Wrapped3)
Expand All @@ -264,7 +274,7 @@ TEST_F(FlashFSLoopTest, Wrapped3)
kBoundarySector * sector_size_ - page_size_;
const uint32_t kEmptyStop = kBoundarySector * sector_size_;

// Fill all sectors except [kEmptyStart, kEmptyStop). The size = 1 page.
// Fill all sectors except [kEmptyStart, kEmptyStop). The unfilled size = 1 page.
flash_emulator_->Fill(flash_emulator_->kFlashFSStart, 0x55,
kEmptyStart - flash_emulator_->kFlashFSStart);
flash_emulator_->Fill(kEmptyStop, 0x55,
Expand All @@ -273,6 +283,7 @@ TEST_F(FlashFSLoopTest, Wrapped3)
flashfsInit();
EXPECT_EQ(headAddress, kEmptyStop);
EXPECT_EQ(tailAddress, kEmptyStart);
EXPECT_TRUE(flash_emulator_->IsErased(tailAddress, page_size_));
}

TEST_F(FlashFSLoopTest, Full)
Expand Down Expand Up @@ -371,7 +382,18 @@ TEST_F(FlashFSLoopTest, WrappedRead)
}
}

class FlashFSLoopInitialEraseTest : public FlashFSTestBase {};
class FlashFSLoopInitialEraseTest : public FlashFSTestBase {
// In this test, flashfsEraseAsync() has to run in the background.
void SetUp() override {
FlashFSTestBase::SetUp();
}
void TearDown() override {
while(!flashfsIsReady()) {
flashfsEraseAsync();
}
FlashFSTestBase::TearDown();
}
};

TEST_F(FlashFSLoopInitialEraseTest, Normal)
{
Expand All @@ -394,9 +416,9 @@ TEST_F(FlashFSLoopInitialEraseTest, Normal)
// Now let's try to auto erase
blackboxConfigMutable()->initialEraseFreeSpace = sector_size_ * 2;
flashfsLoopInitialErase();
while(!flashfsIsReady()) {

while(!flashfsIsReady())
flashfsEraseAsync();
}

EXPECT_EQ(headAddress, kEmptyStop + sector_size_ * 2);
EXPECT_TRUE(flash_emulator_->IsErased(kEmptyStop, sector_size_ * 2));
Expand All @@ -417,15 +439,15 @@ TEST_F(FlashFSLoopInitialEraseTest, Wrapped)
flash_emulator_->kFlashFSEnd - kEmptyStop);

flashfsInit();
EXPECT_TRUE(flashfsIsReady());
EXPECT_EQ(headAddress, kEmptyStop);
EXPECT_EQ(tailAddress, kEmptyStart);

// Now let's try to auto erase
blackboxConfigMutable()->initialEraseFreeSpace = sector_size_ * 2;
flashfsLoopInitialErase();
while(!flashfsIsReady()) {
while(!flashfsIsReady())
flashfsEraseAsync();
}

// wrapped kEmptyStop + sector_size_ * 2
EXPECT_EQ(headAddress, sector_size_);
Expand Down Expand Up @@ -459,4 +481,50 @@ TEST_F(FlashFSLoopInitialEraseTest, UnalignedSize)

EXPECT_EQ(headAddress, kEmptyStop + sector_size_);
EXPECT_TRUE(flash_emulator_->IsErased(kEmptyStop, sector_size_));
}
}

class FlashFSLoopRollingEraseTest : public FlashFSLoopInitialEraseTest { };

TEST_F(FlashFSLoopRollingEraseTest, flashfsWriteOverFlashSize)
{
flashfsInit();
EXPECT_TRUE(flashfsIsReady());
blackboxConfigMutable()->rollingErase = true;

constexpr uint32_t kBufferSize = 128;
constexpr uint8_t kByte = 0x44;
auto buffer = std::make_unique<uint8_t[]>(kBufferSize);
memset(buffer.get(), kByte, kBufferSize);

EXPECT_EQ(headAddress, 0);
EXPECT_EQ(tailAddress, 0);

uint32_t written = 0;
do {
flashfsWrite(buffer.get(), kBufferSize);
flashfsFlushSync();
written += kBufferSize;
flashfsEraseAsync();
} while (written <= flash_emulator_->kFlashFSSize * 2);

// This test is non-deterministic.
// After writing the full flashfs space once, it will start background erasing,
// where a lot of writes can be silently dropped (we are writing way faster
// than the actual BBlog). In the end, we check if there's less than 2
// erased sector and call it success.
uint32_t erased = 0;
uint32_t current = -1;
for (uint32_t i = 0; i < flash_emulator_->kFlashFSSize; i++) {
ASSERT_TRUE(flash_emulator_->memory_[i] == kByte ||
flash_emulator_->memory_[i] == 0xff)
<< "Mismatch address " << std::hex << i;
if (current!=flash_emulator_->memory_[i]) {
current = flash_emulator_->memory_[i];
std::cout << "offset " << std::hex << i << " = " << current << std::endl;
}
if (flash_emulator_->memory_[i] == 0xff) {
erased++;
}
}
EXPECT_LE(erased, sector_size_ * 2);
}
Loading

0 comments on commit e4704b0

Please sign in to comment.