/* * Copyright (C) EdgeTX * * Authors: * Dr. Richard Li * * Based on code named * opentx - https://github.com/opentx/opentx * th9x - http://code.google.com/p/th9x * er9x - http://code.google.com/p/er9x * gruvin9x - http://code.google.com/p/gruvin9x * * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 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. */ /* * FrFTL - Flash Resident Flash Translation Layer * * This is a FTL designed for NOR flash, logical to physical mapping uses 2 layers * of translation tables all resident in flash. It comes with mechanisms to ensure * the integrity of the data in previous state when power out occurs in the middle * of flash programming. * * It can be used to back the FatFS library by ChaN and included the support of * CTRL_SYNC and CTRL_TRIM functions for best performance. * * * How it works: * The FTL organize the storage as 4096 byte pages. The translations from a logical page * to a physical page are recorded in the translation table. The translation table is * organized into TT pages in which each TT page can store the mapping of 1024 pages. * the TT pages are organized into 2 layers: master TT (MTT) and secondary TT (STT). * the logical page no. of MTT is 0, and followed by STT from 1 onwards, therefore * all the translation for TT pages are resided in the MTT page. * * For logical to physical lookup, there are 2 cases, depends on what the logical page no. is: * 1. MTT page -> data page * 2. MTT page -> STT page -> data page * * The page buffer is designed to delay the changes and write back to the flash in larger * translation context to minimize the amount of TT page updates, so that the write * amplification effect and wearing is minimized and improving the overall read/write * performance. * */ #include "frftl.h" #include "crc.h" #include "definitions.h" #include #include #define SECTOR_SIZE 512 #define PAGE_SIZE 4096 #define BLOCK_SIZE 32768 #define BLOCK_MASK (BLOCK_SIZE - 1) #define PAGES_PER_BLOCK (BLOCK_SIZE / PAGE_SIZE) #define USE_BLOCK_ERASE_THRESHOLD 4 #define SECTORS_PER_PAGE (PAGE_SIZE / SECTOR_SIZE) #define TT_PAGE_MAGIC 0xEF87364A #define TT_RECORDS_PER_PAGE 1024 // The multiplier for cache buffers, min recommendation is 2 #define BUFFER_SIZE_MULTIPLIER 4 // Will cap the buffer size to this when exceeded #define MAX_BUFFER_SIZE 64 // Reserve pages to minimize the erase cycles when the FS is full, // should be at least 2 times of BUFFER_SIZE_MULTIPLIER #define RESERVED_PAGES_MULTIPLIER 16 #define LOCKED 1 #define UNLOCKED 0 typedef enum { UNKNOWN, USED, ERASE_REQUIRED, ERASED } PhysicalPageState; typedef enum { NONE, PROGRAM, ERASE_PROGRAM, RELOCATE_ERASE_PROGRAM } ProgramMode; // // Structures in FLASH // typedef struct { uint32_t magicStart; uint32_t logicalPageNo; uint32_t serial; uint16_t padding; uint16_t crc16; } TransTableHeader; typedef struct { union { TransTableHeader header; // 16 bytes uint8_t __padding[1024]; // 1KB }; uint8_t sectStatus[TT_RECORDS_PER_PAGE]; // 1KB uint16_t physicalPageNo[TT_RECORDS_PER_PAGE]; // 2KB } TransTable; typedef union { TransTable tt; // 3KB + 16B uint8_t data[PAGE_SIZE]; // 4KB } Page; // // Structures in RAM only // typedef struct { uint16_t physicalPageNo; uint8_t sectStatus; } PageInfo; typedef struct PageBuffer PageBuffer; struct PageBuffer { Page page; PageBuffer* lruPrev; // Double linked-list for LRU implementation PageBuffer* lruNext; // Double linked-list for LRU implementation PageBuffer* hashNext; // Linked-list for hash table implementation uint16_t logicalPageNo; // Required for first program or reprogram uint16_t physicalPageNo; uint8_t lock; // Page locked for delayed update uint8_t sectorProgramRequired; // Record which sector need to be programmed uint8_t pMode; }; static const uint16_t supportedFlashSizes[] = { 4, 8, 16, 32, 64, 128, 256 }; static PhysicalPageState getPhysicalPageState(FrFTL* ftl, uint16_t physicalPageNo) { uint32_t idx = physicalPageNo >> 4; uint32_t result = (ftl->physicalPageState[idx] >> ((physicalPageNo & 0xf) * 2)); return (PhysicalPageState)(result & 0x3); } static void setPhysicalPageState(FrFTL* ftl, uint16_t physicalPageNo, PhysicalPageState state) { uint32_t idx = physicalPageNo >> 4; uint32_t mask = 0x3 << ((physicalPageNo & 0xf) * 2); ftl->physicalPageState[idx] &= ~mask; ftl->physicalPageState[idx] |= ((state & 0x3) << ((physicalPageNo & 0xf) * 2)); } static const uint16_t crc16_ccitt_start = 0xFFFF; static inline uint16_t crc16_x25_ccitt(const void* buf, uint32_t len) { return crc16(CRC_1021, (const uint8_t*)buf, len, crc16_ccitt_start); } static inline uint16_t calcCRC(TransTableHeader* header) { header->padding = 0xffff; return crc16_x25_ccitt(header, sizeof(TransTableHeader) - 2); } static void resolveUnknownState(FrFTL* ftl, uint16_t count) { if (ftl->physicalPageStateResolved) { return; } uint16_t idx = ftl->writeFrontier; bool earlyEnd = false; const FrFTLOps* cb = ftl->callbacks; for (uint16_t i = 0; i < ftl->physicalPageCount; i++) { if (getPhysicalPageState(ftl, idx) == UNKNOWN) { PhysicalPageState state = cb->isFlashErased(idx * PAGE_SIZE) ? ERASED : ERASE_REQUIRED; setPhysicalPageState(ftl, idx, state); count--; if (count == 0) { earlyEnd = true; break; } } idx++; if (idx >= ftl->physicalPageCount) { idx = 0; } } if (!earlyEnd) { ftl->physicalPageStateResolved = true; } } static PageBuffer* findPageFromHashTable(FrFTL* ftl, uint16_t physicalPageNo) { PageBuffer** hashTable = (PageBuffer**) ftl->hashTable; uint16_t hash = physicalPageNo % ftl->pageBufferSize; PageBuffer* lastBuffer = nullptr; PageBuffer* buffer = hashTable[hash]; while (buffer) { if (buffer->physicalPageNo == physicalPageNo) { // Update search order with LRU if (lastBuffer) { lastBuffer->hashNext = buffer->hashNext; buffer->hashNext = hashTable[hash]; hashTable[hash] = buffer; } return buffer; } lastBuffer = buffer; buffer = buffer->hashNext; } return nullptr; } static void removePageFromHashTable(FrFTL* ftl, uint16_t physicalPageNo) { PageBuffer** hashTable = (PageBuffer**) ftl->hashTable; uint16_t hash = physicalPageNo % ftl->pageBufferSize; PageBuffer* lastBuffer = nullptr; PageBuffer* buffer = hashTable[hash]; while (buffer) { if (buffer->physicalPageNo == physicalPageNo) { if (lastBuffer == nullptr) { hashTable[hash] = buffer->hashNext; } else { lastBuffer->hashNext = buffer->hashNext; } buffer->hashNext = nullptr; return; } lastBuffer = buffer; buffer = buffer->hashNext; } } static void addPageToHashTable(FrFTL* ftl, PageBuffer* buffer) { PageBuffer** hashTable = (PageBuffer**) ftl->hashTable; uint16_t hash = buffer->physicalPageNo % ftl->pageBufferSize; buffer->hashNext = hashTable[hash]; hashTable[hash] = buffer; } static void movePageBufferToLRUHead(FrFTL* ftl, PageBuffer* buffer) { // Take buffer out from the linked list if (buffer->lruPrev) { buffer->lruPrev->lruNext = buffer->lruNext; } else { // Already is head, done return; } if (buffer->lruNext) { buffer->lruNext->lruPrev = buffer->lruPrev; } else { // Tail detected, need to update tail pointer ftl->bufferTail = buffer->lruPrev; } // Put buffer to buffer head ((PageBuffer*) ftl->bufferHead)->lruPrev = buffer; buffer->lruNext = (PageBuffer*) ftl->bufferHead; buffer->lruPrev = nullptr; ftl->bufferHead = buffer; } static PageBuffer* rawFindReplacableBuffer(FrFTL* ftl) { PageBuffer* buffer = (PageBuffer*) ftl->bufferTail; while (buffer && buffer->lock == LOCKED) { buffer = buffer->lruPrev; } return buffer; } static void movePageBufferToLRUTail(FrFTL* ftl, PageBuffer* buffer) { // Take buffer out from the linked list if (buffer->lruNext) { buffer->lruNext->lruPrev = buffer->lruPrev; } else { // Already is tail, done return; } if (buffer->lruPrev) { buffer->lruPrev->lruNext = buffer->lruNext; } else { // Head detected, need to update head pointer ftl->bufferHead = buffer->lruNext; } // Put buffer to buffer tail ((PageBuffer*) ftl->bufferTail)->lruNext = buffer; buffer->lruPrev = (PageBuffer*) ftl->bufferTail; buffer->lruNext = nullptr; ftl->bufferTail = buffer; } static PageBuffer* findReplacableBuffer(FrFTL* ftl) { PageBuffer* buffer = rawFindReplacableBuffer(ftl); if (!buffer) { // All buffers are locked, flush lock pages if (!ftlSync(ftl)) { return nullptr; } // Try again buffer = rawFindReplacableBuffer(ftl); } if (buffer) { // Invalidate buffer from hash table if (buffer->physicalPageNo != 0xffff) { removePageFromHashTable(ftl, buffer->physicalPageNo); buffer->physicalPageNo = 0xffff; } // Update LRU movePageBufferToLRUHead(ftl, buffer); } return buffer; } static PageBuffer* findPhysicalPageInBuffer(FrFTL* ftl, uint16_t physicalPageNo) { PageBuffer* buffer = findPageFromHashTable(ftl, physicalPageNo); if (buffer) { // Update LRU movePageBufferToLRUHead(ftl, buffer); } return buffer; } static PageBuffer* loadPhysicalPageInBuffer(FrFTL* ftl, uint16_t logicalPageNo, uint16_t physicalPageNo) { // Find page in buffer PageBuffer* currentBuffer = findPhysicalPageInBuffer(ftl, physicalPageNo); if (currentBuffer == nullptr) { // Page not in buffer, load page in buffer if ((currentBuffer = findReplacableBuffer(ftl)) == nullptr) { return nullptr; // BUG }; const FrFTLOps* cb = ftl->callbacks; if (!cb->flashRead(physicalPageNo * PAGE_SIZE, currentBuffer->page.data, PAGE_SIZE)) { return nullptr; } currentBuffer->logicalPageNo = logicalPageNo; currentBuffer->physicalPageNo = physicalPageNo; currentBuffer->lock = UNLOCKED; currentBuffer->sectorProgramRequired = 0; currentBuffer->pMode = NONE; // Add new page to hash table addPageToHashTable(ftl, currentBuffer); } return currentBuffer; } static PageBuffer* initPhysicalPageInBuffer(FrFTL* ftl, uint16_t logicalPageNo, uint16_t physicalPageNo) { // Find page in buffer PageBuffer* currentBuffer = findPhysicalPageInBuffer(ftl, physicalPageNo); if (currentBuffer == nullptr) { // Page not in buffer, init page in buffer if ((currentBuffer = findReplacableBuffer(ftl)) == nullptr) { return nullptr; // BUG }; currentBuffer->logicalPageNo = logicalPageNo; currentBuffer->physicalPageNo = physicalPageNo; currentBuffer->lock = LOCKED; currentBuffer->sectorProgramRequired = 0; currentBuffer->pMode = ERASE_PROGRAM; memset(currentBuffer->page.data, 0xff, PAGE_SIZE); // Add new page to hash table addPageToHashTable(ftl, currentBuffer); } return currentBuffer; } static bool hasFreeBuffers(FrFTL* ftl, uint16_t bufferCount) { uint16_t freeBufferFound = 0; PageBuffer* pageBuffer = (PageBuffer*) ftl->bufferHead; while (pageBuffer != nullptr) { if (pageBuffer->lock == UNLOCKED) { freeBufferFound++; if (freeBufferFound >= bufferCount) { return true; } } pageBuffer = pageBuffer->lruNext; } return false; } static bool readPhysicalSector(FrFTL* ftl, uint8_t* buffer, uint16_t logicalPageNo, uint16_t physicalPageNo, uint8_t pageSectorNo) { PageBuffer* pageBuffer = loadPhysicalPageInBuffer(ftl, logicalPageNo, physicalPageNo); if (!pageBuffer) return false; memcpy(buffer, pageBuffer->page.data + pageSectorNo * SECTOR_SIZE, SECTOR_SIZE); return true; } static bool readPhysicalPageInfo(FrFTL* ftl, PageInfo* pageInfo, uint16_t logicalPageNo, uint16_t physicalPageNo, uint16_t recordNo) { PageBuffer* pageBuffer = loadPhysicalPageInBuffer(ftl, logicalPageNo, physicalPageNo); if (!pageBuffer) return false; // Page found in buffer const TransTable* tt = &pageBuffer->page.tt; pageInfo->physicalPageNo = tt->physicalPageNo[recordNo]; pageInfo->sectStatus = tt->sectStatus[recordNo]; return true; } static bool readPageInfo(FrFTL* ftl, PageInfo* pageInfo, uint16_t logicalPageNo) { if (logicalPageNo < TT_RECORDS_PER_PAGE) { // Read from master TT return readPhysicalPageInfo(ftl, pageInfo, 0, ftl->mttPhysicalPageNo, logicalPageNo); } // Lookup from secondary TT from master TT PageInfo secondaryTTInfo; uint16_t sttLogicalPageNo = logicalPageNo / TT_RECORDS_PER_PAGE; if (!readPhysicalPageInfo(ftl, &secondaryTTInfo, 0, ftl->mttPhysicalPageNo, sttLogicalPageNo)) { return false; } // Read from secondary TT return readPhysicalPageInfo(ftl, pageInfo, sttLogicalPageNo, secondaryTTInfo.physicalPageNo, logicalPageNo % TT_RECORDS_PER_PAGE); } static bool updatePhysicalPageInfo(FrFTL* ftl, PageInfo* pageInfo, uint16_t logicalPageNo, uint16_t physicalPageNo, uint16_t recordNo) { PageBuffer* pageBuffer = loadPhysicalPageInBuffer(ftl, logicalPageNo, physicalPageNo); if (!pageBuffer) { return false; } // Update info, need to lock and ensure update pageBuffer->lock = LOCKED; if (pageBuffer->pMode == NONE) { pageBuffer->pMode = PROGRAM; } TransTable* tt = &pageBuffer->page.tt; tt->physicalPageNo[recordNo] = pageInfo->physicalPageNo; tt->sectStatus[recordNo] = pageInfo->sectStatus; return true; } static bool updatePageInfo(FrFTL* ftl, PageInfo* pageInfo, uint16_t logicalPageNo) { if (logicalPageNo < TT_RECORDS_PER_PAGE) { // Update to master TT return updatePhysicalPageInfo(ftl, pageInfo, 0, ftl->mttPhysicalPageNo, logicalPageNo); } else { // Lookup from secondary TT from master TT PageInfo secondaryTTInfo; uint16_t sttLogicalPageNo = logicalPageNo / TT_RECORDS_PER_PAGE; if (!readPhysicalPageInfo(ftl, &secondaryTTInfo, 0, ftl->mttPhysicalPageNo, sttLogicalPageNo)) { return false; } // Program to secondary TT return updatePhysicalPageInfo(ftl, pageInfo, sttLogicalPageNo, secondaryTTInfo.physicalPageNo, logicalPageNo % TT_RECORDS_PER_PAGE); } } static uint16_t allocatePhysicalPage(FrFTL* ftl) { uint16_t lookupCount = 0; while (getPhysicalPageState(ftl, ftl->writeFrontier) == USED) { ftl->writeFrontier++; if (ftl->writeFrontier >= ftl->physicalPageCount) { ftl->writeFrontier = 0; } lookupCount++; if (lookupCount > ftl->physicalPageCount) { return 0xffff; // BUG } } uint16_t physicalPageNo = ftl->writeFrontier++; if (ftl->writeFrontier >= ftl->physicalPageCount) { ftl->writeFrontier = 0; } return physicalPageNo; } static bool quickErase(FrFTL* ftl, uint32_t addr) { const FrFTLOps* cb = ftl->callbacks; if ((addr & BLOCK_MASK) == 0) { // Block aligned uint16_t ppn = addr / PAGE_SIZE; uint8_t count = 0; bool hasUsed = false; // Check state of the whole jumbo page for (uint8_t i = 0; i < PAGES_PER_BLOCK; i++) { PhysicalPageState state = getPhysicalPageState(ftl, ppn + i); if (state == UNKNOWN || state == ERASE_REQUIRED) { count++; } if (state == USED) { hasUsed = true; break; // Cannot use block erase } } if (!hasUsed && count >= USE_BLOCK_ERASE_THRESHOLD) { bool ret = cb->flashBlockErase(addr); if (ret) { for (uint8_t i = 0; i < PAGES_PER_BLOCK; i++) { setPhysicalPageState(ftl, ppn + i, ERASED); } } return ret; } } return cb->flashErase(addr); } static bool programPage(FrFTL* ftl, PageBuffer* buffer, bool doErase) { const FrFTLOps* cb = ftl->callbacks; uint32_t pageAddr = buffer->physicalPageNo * PAGE_SIZE; if (doErase && getPhysicalPageState(ftl, buffer->physicalPageNo) != ERASED) { // Do erase on the fly if (!quickErase(ftl, pageAddr)) { return false; } } if (buffer->logicalPageNo < ftl->ttPageCount) { buffer->sectorProgramRequired = 0xff; } // Sector by sector programming: // As flash requires 256 bytes per program command, it will be more efficient to program by sector uint8_t* dataPtr = buffer->page.data; for (uint8_t i = 0; i < SECTORS_PER_PAGE; i++) { uint8_t sectMask = 1 << i; if ((buffer->sectorProgramRequired & sectMask) != 0) { if (!cb->flashProgram(pageAddr, dataPtr, SECTOR_SIZE)) { return false; } } pageAddr += SECTOR_SIZE; dataPtr += SECTOR_SIZE; } return true; } static bool programPageInBuffer(FrFTL* ftl, PageBuffer* buffer) { uint16_t oldPhysicalPageNo; switch (buffer->pMode) { case PROGRAM: // Program only if (!programPage(ftl, buffer, false)) { return false; } setPhysicalPageState(ftl, buffer->physicalPageNo, USED); if (!findPageFromHashTable(ftl, buffer->physicalPageNo)) { addPageToHashTable(ftl, buffer); } break; case ERASE_PROGRAM: if (!programPage(ftl, buffer, true)) { return false; } setPhysicalPageState(ftl, buffer->physicalPageNo, USED); if (!findPageFromHashTable(ftl, buffer->physicalPageNo)) { addPageToHashTable(ftl, buffer); } break; case RELOCATE_ERASE_PROGRAM: // Reprogram oldPhysicalPageNo = buffer->physicalPageNo; setPhysicalPageState(ftl, oldPhysicalPageNo, ERASE_REQUIRED); removePageFromHashTable(ftl, oldPhysicalPageNo); buffer->physicalPageNo = allocatePhysicalPage(ftl); if (buffer->physicalPageNo == 0xffff) { return false; } if (buffer->logicalPageNo < ftl->ttPageCount) { if (buffer->logicalPageNo == 0) { // MTT need update physicalPageNo buffer->page.tt.physicalPageNo[0] = buffer->physicalPageNo; } // TT page, need update serial and CRC buffer->page.tt.header.serial++; buffer->page.tt.header.crc16 = calcCRC(&buffer->page.tt.header); } if (!programPage(ftl, buffer, true)) { return false; } setPhysicalPageState(ftl, buffer->physicalPageNo, USED); addPageToHashTable(ftl, buffer); if (buffer->logicalPageNo == 0) { // MTT page, need update mttPhysicalPageNo ftl->mttPhysicalPageNo = buffer->physicalPageNo; } break; default: break; } return true; } static bool lockTTPages(FrFTL* ftl, uint16_t logicalPageNo) { // Read TT pages and lock it for later update PageBuffer* ttBuffer; PageInfo ttPageInfo; uint16_t ttPageNo = logicalPageNo / TT_RECORDS_PER_PAGE; if (!readPageInfo(ftl, &ttPageInfo, ttPageNo)) { return false; } ttBuffer = loadPhysicalPageInBuffer(ftl, ttPageNo, ttPageInfo.physicalPageNo); ttBuffer->lock = LOCKED; ttBuffer->pMode = RELOCATE_ERASE_PROGRAM; if (ttPageNo > 0) { // TT page not MTT page, need to lock MTT page as well ttBuffer = loadPhysicalPageInBuffer(ftl, 0, ftl->mttPhysicalPageNo); ttBuffer->lock = LOCKED; ttBuffer->pMode = RELOCATE_ERASE_PROGRAM; } return true; } bool ftlSync(FrFTL* ftl) { PageBuffer* pageBuffer = ((PageBuffer*)(ftl->pageBuffer)); // First program data pages for (uint16_t i = 0; i < ftl->pageBufferSize; i++) { PageBuffer* currentBuffer = pageBuffer + i; if (currentBuffer->lock == LOCKED) { if (currentBuffer->logicalPageNo >= ftl->ttPageCount) { if (!programPageInBuffer(ftl, currentBuffer)) { return false; } // Update PageInfo in TT pages PageInfo pageInfo; if (!readPageInfo(ftl, &pageInfo, currentBuffer->logicalPageNo)) { return false; } pageInfo.physicalPageNo = currentBuffer->physicalPageNo; if (!updatePageInfo(ftl, &pageInfo, currentBuffer->logicalPageNo)) { return false; } // Unlock buffer currentBuffer->lock = UNLOCKED; currentBuffer->sectorProgramRequired = 0; currentBuffer->pMode = NONE; } } } // Second program STT pages PageBuffer* mttBuffer = loadPhysicalPageInBuffer(ftl, 0, ftl->mttPhysicalPageNo); if (!mttBuffer) { return false; } for (uint16_t i = 0; i < ftl->pageBufferSize; i++) { PageBuffer* currentBuffer = pageBuffer + i; if (currentBuffer->lock == LOCKED) { int16_t logicalPageNo = currentBuffer->logicalPageNo; if (logicalPageNo > 0 && logicalPageNo < ftl->ttPageCount) { if (!programPageInBuffer(ftl, currentBuffer)) { return false; } // Update PageInfo in MTT page TransTable* tt = &mttBuffer->page.tt; tt->physicalPageNo[logicalPageNo] = currentBuffer->physicalPageNo; // Unlock buffer currentBuffer->lock = UNLOCKED; currentBuffer->pMode = NONE; } } } // Finally program MTT page if (mttBuffer->lock == LOCKED) { if (!programPageInBuffer(ftl, mttBuffer)) { return false; } // Unlock buffer mttBuffer->lock = UNLOCKED; mttBuffer->pMode = NONE; } return true; } bool ftlWrite(FrFTL* ftl, uint32_t startSectorNo, uint32_t noOfSectors, const uint8_t* buf) { resolveUnknownState(ftl, ftl->ttPageCount); if (startSectorNo + noOfSectors > ftl->usableSectorCount) { return false; } uint32_t sectorNo = startSectorNo; while (noOfSectors > 0) { // Max no. of sectors need to be rewritten is 3, // need to ensure has enough free buffers if (!hasFreeBuffers(ftl, 3)) { // Flush the buffers first if free space is not found if (!ftlSync(ftl)) { return false; } } uint16_t logicalPageNo = sectorNo / SECTORS_PER_PAGE + ftl->ttPageCount; uint8_t pageSectorNo = sectorNo % SECTORS_PER_PAGE; // Read page info PageInfo pageInfo; readPageInfo(ftl, &pageInfo, logicalPageNo); PageBuffer* dataBuffer; // Allocate new physical page for uninitialized logical page if (pageInfo.physicalPageNo == 0xffff) { // Need allocate physical page if ((pageInfo.physicalPageNo = allocatePhysicalPage(ftl)) == 0xffff) { return false; } // Init page in buffer, locked for delayed program dataBuffer = initPhysicalPageInBuffer(ftl, logicalPageNo, pageInfo.physicalPageNo); if (!dataBuffer) { return false; } pageInfo.sectStatus = 0xff; if (!updatePageInfo(ftl, &pageInfo, logicalPageNo)) { return false; } } else { dataBuffer = loadPhysicalPageInBuffer(ftl, logicalPageNo, pageInfo.physicalPageNo); if (!dataBuffer) { return false; } } uint8_t sectMask = 1 << pageSectorNo; if ((pageInfo.sectStatus & sectMask) != 0) { // Sector never write, append information pageInfo.sectStatus &= ~sectMask; if (!updatePageInfo(ftl, &pageInfo, logicalPageNo)) { return false; } // Update sector, locked for delayed program dataBuffer->lock = LOCKED; if (dataBuffer->pMode == NONE) { dataBuffer->pMode = PROGRAM; } memcpy(dataBuffer->page.data + pageSectorNo * SECTOR_SIZE, buf, SECTOR_SIZE); dataBuffer->sectorProgramRequired |= sectMask; } else { // Sector already written, use replace write // Lock data page for delayed update with reprogram dataBuffer->lock = LOCKED; dataBuffer->pMode = RELOCATE_ERASE_PROGRAM; memcpy(dataBuffer->page.data + pageSectorNo * SECTOR_SIZE, buf, SECTOR_SIZE); dataBuffer->sectorProgramRequired = ~pageInfo.sectStatus; if (!lockTTPages(ftl, logicalPageNo)) { return false; } } noOfSectors--; sectorNo++; buf += SECTOR_SIZE; } return true; } bool ftlRead(FrFTL* ftl, uint32_t sectorNo, uint8_t* buffer) { // doGC(ftl, ftl->ttPageCount, 1); if (sectorNo >= ftl->usableSectorCount) { return false; } uint16_t logicalPageNo = sectorNo / SECTORS_PER_PAGE + ftl->ttPageCount; uint8_t pageSectorNo = sectorNo % SECTORS_PER_PAGE; // Read page info PageInfo pageInfo; readPageInfo(ftl, &pageInfo, logicalPageNo); // Check if sector written before uint8_t sectMask = 1 << pageSectorNo; if ((pageInfo.sectStatus & sectMask) != 0) { // Sector never write, return init content memset(buffer, 0xff, SECTOR_SIZE); return true; } return readPhysicalSector(ftl, buffer, logicalPageNo, pageInfo.physicalPageNo, pageSectorNo); } bool ftlTrim(FrFTL* ftl, uint32_t startSectorNo, uint32_t noOfSectors) { resolveUnknownState(ftl, ftl->ttPageCount); if (startSectorNo + noOfSectors > ftl->usableSectorCount) { return false; } uint32_t sectorNo = startSectorNo; while (noOfSectors > 0) { // Max no. of sectors need to be rewritten is 3, // need to ensure has enough free buffers if (!hasFreeBuffers(ftl, 3)) { // Flush the buffers first if free space is not found if (!ftlSync(ftl)) { return false; } } uint16_t logicalPageNo = sectorNo / SECTORS_PER_PAGE + ftl->ttPageCount; uint8_t pageSectorNo = sectorNo % SECTORS_PER_PAGE; // Read page info PageInfo pageInfo; readPageInfo(ftl, &pageInfo, logicalPageNo); // Check if physical page in used if (pageInfo.physicalPageNo != 0xffff) { uint8_t sectMask = 1 << pageSectorNo; if ((pageInfo.sectStatus & sectMask) == 0) { // Sector used, free it PageBuffer* dataBuffer = loadPhysicalPageInBuffer( ftl, logicalPageNo, pageInfo.physicalPageNo); if (!dataBuffer) { return false; } pageInfo.sectStatus |= sectMask; if (pageInfo.sectStatus == 0xff) { // Free whole page setPhysicalPageState(ftl, pageInfo.physicalPageNo, ERASE_REQUIRED); removePageFromHashTable(ftl, pageInfo.physicalPageNo); pageInfo.physicalPageNo = 0xffff; // Invalidate page info dataBuffer->physicalPageNo = 0xffff; // Invalidate buffer dataBuffer->lock = UNLOCKED; // Move freed page buffer to LRU tail movePageBufferToLRUTail(ftl, dataBuffer); } else { // Locked for delayed relocate, fill and program dataBuffer->lock = LOCKED; dataBuffer->pMode = RELOCATE_ERASE_PROGRAM; dataBuffer->sectorProgramRequired = ~pageInfo.sectStatus; } // Update page info if (!updatePageInfo(ftl, &pageInfo, logicalPageNo)) { return false; } if (!lockTTPages(ftl, logicalPageNo)) { return false; } } } noOfSectors--; sectorNo++; } return true; } static void initPageBuffer(FrFTL* ftl) { // Init page buffer uint32_t bufferSize = sizeof(PageBuffer) * ftl->pageBufferSize; ftl->memoryUsed += bufferSize; ftl->pageBuffer = malloc(bufferSize); ftl->bufferHead = ftl->pageBuffer; for (uint16_t i = 0; i < ftl->pageBufferSize; i++) { PageBuffer* currentBuffer = ((PageBuffer*)ftl->pageBuffer) + i; if (i == 0) { // First buffer currentBuffer->lruPrev = nullptr; } else { currentBuffer->lruPrev = ((PageBuffer*)ftl->pageBuffer) + i - 1; } if (i == ftl->pageBufferSize - 1) { // Last buffer currentBuffer->lruNext = nullptr; ftl->bufferTail = currentBuffer; } else { currentBuffer->lruNext = ((PageBuffer*)ftl->pageBuffer) + i + 1; } currentBuffer->hashNext = nullptr; currentBuffer->logicalPageNo = 0xffff; currentBuffer->physicalPageNo = 0xffff; currentBuffer->lock = UNLOCKED; currentBuffer->pMode = NONE; } // Init hash table bufferSize = sizeof(PageBuffer*) * ftl->pageBufferSize; ftl->memoryUsed += bufferSize; ftl->hashTable = malloc(bufferSize); memset(ftl->hashTable, 0, bufferSize); } static void initTransTablePage(TransTable* tt, uint32_t logicalPageNo) { memset(tt, 0xff, sizeof(TransTable)); TransTableHeader* header = &tt->header; header->magicStart = TT_PAGE_MAGIC; header->logicalPageNo = logicalPageNo; header->serial = 1; header->crc16 = calcCRC(header); } static void updateTransTablePage(TransTable* tt, uint32_t logicalPageNo) { TransTableHeader* header = &tt->header; header->logicalPageNo = logicalPageNo; header->crc16 = calcCRC(header); } void createFTL(FrFTL* ftl) { // Resolve the first few blocks for proper startup ftl->writeFrontier = 0; resolveUnknownState(ftl, ftl->pageBufferSize); uint16_t i = 1; TransTable tt; initTransTablePage(&tt, i); uint32_t addr = PAGE_SIZE; const FrFTLOps* cb = ftl->callbacks; do { if (getPhysicalPageState(ftl, i) != ERASED) { quickErase(ftl, addr); } cb->flashProgram(addr, (const uint8_t*)&tt, PAGE_SIZE); setPhysicalPageState(ftl, i, USED); i += 1; addr += PAGE_SIZE; updateTransTablePage(&tt, i); } while(i < ftl->ttPageCount); i = 0; initTransTablePage(&tt, i); do { tt.physicalPageNo[i] = i; tt.sectStatus[i] = 0; } while(++i < ftl->ttPageCount); if (getPhysicalPageState(ftl, 0) != ERASED) { quickErase(ftl, 0); } cb->flashProgram(0, (const uint8_t*)&tt, PAGE_SIZE); setPhysicalPageState(ftl, 0, USED); ftl->writeFrontier = ftl->ttPageCount; } static bool loadFTL(FrFTL* ftl) { // Scan for MTT uint32_t currentSerial = 0; uint16_t currentPhysicalMTTPageNo = 0xffff; const FrFTLOps* cb = ftl->callbacks; for (uint16_t i = 0; i < ftl->physicalPageCount; i++) { TransTableHeader header; if (!cb->flashRead(i * PAGE_SIZE, (uint8_t*)&header, sizeof(header))) { return false; } if (header.magicStart == TT_PAGE_MAGIC && header.logicalPageNo == 0 && header.crc16 == calcCRC(&header)) { // MTT detected if (header.serial > currentSerial || currentSerial - header.serial > 0x7fffffff) { // Newer MTT found currentSerial = header.serial; currentPhysicalMTTPageNo = i; } } } if (currentPhysicalMTTPageNo != 0xffff) { // MTT found, load data ftl->mttPhysicalPageNo = currentPhysicalMTTPageNo; setPhysicalPageState(ftl, currentPhysicalMTTPageNo, USED); ftl->writeFrontier = currentPhysicalMTTPageNo + 1; if (ftl->writeFrontier >= ftl->physicalPageCount) { ftl->writeFrontier = 0; } PageBuffer* mtt = loadPhysicalPageInBuffer(ftl, 0, currentPhysicalMTTPageNo); // Check if MTT valid if (!mtt || mtt->page.tt.physicalPageNo[0] != currentPhysicalMTTPageNo) { return false; } PageBuffer* tt = mtt; for (uint16_t i = 0; i < ftl->ttPageCount; i++) { if (i > 0) { // Not MTT, read TT page and check integrity uint16_t ttPhysicalPageNo = mtt->page.tt.physicalPageNo[i]; // Check wrong range of physical page no. if (ttPhysicalPageNo >= ftl->physicalPageCount) { return false; } // Load TT page tt = loadPhysicalPageInBuffer(ftl, i, ttPhysicalPageNo); if (!tt) { return false; } // Check if TT valid if (tt->page.tt.header.magicStart != TT_PAGE_MAGIC || tt->page.tt.header.crc16 != calcCRC(&tt->page.tt.header)) { return false; } } // Mark used page for (uint16_t j = 0; j < TT_RECORDS_PER_PAGE; j++) { uint16_t currentPhysicalPageNo = tt->page.tt.physicalPageNo[j]; if (currentPhysicalPageNo != 0xffff) { // Check wrong range of physical page no. if (currentPhysicalPageNo >= ftl->physicalPageCount) { return false; } // Used page setPhysicalPageState(ftl, currentPhysicalPageNo, USED); } } } // Walk forward to ensure some pages are resolved resolveUnknownState(ftl, ftl->pageBufferSize); return true; } return false; } bool ftlInit(FrFTL* ftl, const FrFTLOps* cb, uint16_t flashSizeInMB) { // Check flash size bool found = false; for (uint8_t i = 0; i < sizeof(supportedFlashSizes); i++) { if (flashSizeInMB == supportedFlashSizes[i]) { found = true; break; } } if (!found) { return false; } memset(ftl, 0, sizeof(FrFTL)); ftl->callbacks = cb; ftl->mttPhysicalPageNo = 0; ftl->physicalPageCount = flashSizeInMB > 128 ? 65535 : flashSizeInMB * 1024 * 1024 / PAGE_SIZE; ftl->ttPageCount = ftl->physicalPageCount / TT_RECORDS_PER_PAGE + (ftl->physicalPageCount % TT_RECORDS_PER_PAGE > 0 ? 1 : 0); ftl->usableSectorCount = (ftl->physicalPageCount - ftl->ttPageCount * RESERVED_PAGES_MULTIPLIER) * SECTORS_PER_PAGE; uint32_t stateSize = ftl->physicalPageCount / 16 + (ftl->physicalPageCount % 16 > 0 ? 1 : 0); ftl->physicalPageState = (uint32_t*)calloc(stateSize, sizeof(uint32_t)); ftl->physicalPageStateResolved = false; ftl->memoryUsed += stateSize * sizeof(uint32_t); ftl->pageBufferSize = ftl->ttPageCount * BUFFER_SIZE_MULTIPLIER; if (ftl->pageBufferSize > MAX_BUFFER_SIZE) { ftl->pageBufferSize = MAX_BUFFER_SIZE; } initPageBuffer(ftl); if (!loadFTL(ftl)) { // Need reset physical page state before create memset(ftl->physicalPageState, 0, stateSize * sizeof(uint32_t)); createFTL(ftl); } return true; } void ftlDeInit(FrFTL* ftl) { free(ftl->pageBuffer); free(ftl->physicalPageState); free(ftl->hashTable); }