1156 lines
33 KiB
C++
1156 lines
33 KiB
C++
/*
|
|
* Copyright (C) EdgeTX
|
|
*
|
|
* Authors:
|
|
* Dr. Richard Li <richard.li@ces.hk>
|
|
*
|
|
* 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 <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#define SECTOR_SIZE 512
|
|
#define PAGE_SIZE 4096
|
|
#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 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 (!cb->flashErase(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) {
|
|
cb->flashErase(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) {
|
|
cb->flashErase(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);
|
|
}
|