blob: f5cc8af992af1ed1b1a45c155765cf5c86bc8272 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <math.h>
#include <stdarg.h>
#include <string.h>
#include <zircon/compiler.h>
#include "ftl.h"
#include "ftlnp.h"
//
// Configuration.
//
// Legacy wear lag parameters. Unused except for measuring some
// statistics below.
//
#define WC_LIM0_LAG 190 // avg wear lag priority trigger
#define WC_LIM1_LAG 205 // periodic wear lag priority trigger
#define WC_LIM2_LAG 252 // constant wear lag priority trigger
// Global Variable Definitions
uint32_t FtlnLim0Lag = WC_LIM0_LAG;
uint32_t FtlnLim1Lag = WC_LIM1_LAG;
uint32_t FtlnLim2Lag = WC_LIM2_LAG;
// Local Function Definitions
// format_ftl: Erase all non-free blocks
//
// Input: ftl = pointer to FTL control block
//
// Returns: 0 on success, -1 on error
//
static int format_ftl(FTLN ftl) {
ui32 meta_block;
// Get number of block that will hold the metapage.
if (ftl->free_mpn == (ui32)-1)
meta_block = FtlnLoWcFreeBlk(ftl);
else
meta_block = ftl->free_mpn / ftl->pgs_per_blk;
// Write meta page, to indicate that format is in progress.
memset(ftl->main_buf, 0xFF, ftl->page_size);
if (FtlnMetaWr(ftl, CONT_FORMAT)) {
return -1;
}
// Erase all map blocks, mark all blocks free, and reset the FTL.
return FtlnFormat(ftl, meta_block);
}
// first_free_blk: Find the first free block, counting from block zero
//
// Input: ftl = pointer to FTL control block
//
// Returns: block number if successful, else (ui32)-1 if none free
//
static ui32 first_free_blk(CFTLN ftl) {
ui32 b;
// Search for first free block.
for (b = 0;; ++b) {
// Return error if no block is free.
if (b == ftl->num_blks) {
return (ui32)FsError2(FTL_NO_FREE_BLK, ENOSPC);
}
// If block is free, return its block number.
if (IS_FREE(ftl->bdata[b]))
return b;
}
}
// Global Function Definitions
// FtlnReport: Callback function used by upper file system layer to
// notify FTL of events
//
// Inputs: vol = FTL handle
// msg = event
// ... = additional arguments
//
// Returns: 0 or 1 (unformat()) for success, -1 on failure
//
int FtlnReport(void* vol, ui32 msg, ...) {
FTLN ftl = vol;
va_list ap;
// Set errno and return -1 if fatal I/O error occurred.
if (ftl->flags & FTLN_FATAL_ERR)
return FsError2(NDM_EIO, EIO);
// Handle event passed down from file system layer.
switch (msg) {
case FS_UNFORMAT: {
ui32 b;
// Return error if volume is mounted.
if (ftl->flags & FTLN_MOUNTED) {
return FsError2(FTL_MOUNTED, EEXIST);
}
// Format volume. Return -1 if error.
if (format_ftl(ftl))
return -1;
// Erase every unerased block. Return -1 if error.
for (b = 0; b < ftl->num_blks; ++b)
if ((ftl->bdata[b] & ERASED_BLK_FLAG) == FALSE)
if (FtlnEraseBlk(ftl, b))
return -1;
// Delete volume (both FTL and FS). Free its memory. Volume is
// unmounted, so nothing to flush. Return value can be ignored.
FtlnDelVol(ftl);
// Return '1' for success.
return 1;
}
case FS_FORMAT:
case FS_FORMAT_RESET_WC: {
// Format volume. Return -1 if error.
if (format_ftl(ftl)) {
ftl->logger.error(__FILE__, __LINE__, "FTL format failed.");
return -1;
}
// Check if we're to equalize the wear counts (for benchmarking).
if (msg == FS_FORMAT_RESET_WC) {
ui32 b, avg_lag = 0;
// Compute average wear count and assign to every block.
for (b = 0; b < ftl->num_blks; ++b)
avg_lag += ftl->blk_wc_lag[b];
avg_lag /= ftl->num_blks;
ftl->high_wc -= avg_lag;
for (b = 0; b < ftl->num_blks; ++b)
ftl->blk_wc_lag[b] = 0;
ftl->wear_data.avg_wc_lag = ftl->wc_lag_sum = 0;
}
// Return success.
return 0;
}
case FS_VCLEAN:
return FtlnVclean(ftl);
case FS_UNMOUNT:
// Return error if not mounted.
if ((ftl->flags & FTLN_MOUNTED) == FALSE)
return FsError2(FTL_UNMOUNTED, ENOENT);
// Clear the 'mounted' flag.
ftl->flags &= ~FTLN_MOUNTED;
__FALLTHROUGH;
case FS_SYNC: {
// Prepare to write all dirty map cache pages. Return -1 if err.
if (FtlnRecCheck(ftl, 0))
return -1;
// Save all dirty map pages to flash. Return -1 if error.
if (ftlmcFlushMap(ftl->map_cache))
return -1;
PfAssert(ftl->num_free_blks >= FTLN_MIN_FREE_BLKS);
#if INC_FTL_NDM_MLC
// For MLC devices, advance free_vpn pointer so next volume page
// write can't corrupt previously written valid page.
FtlnMlcSafeFreeVpn(ftl);
#endif
// If request was for sync, return success now.
if (msg == FS_SYNC)
return 0;
#if INC_ELIST
// Check if there is not a current erased-block list.
if (ftl->elist_blk == (ui32)-1) {
ui32 b, n;
// Count the number of erased free blocks.
for (n = b = 0; b < ftl->num_blks; ++b)
if (IS_ERASED(ftl->bdata[b]))
++n;
// Only write erased list if more than 1 block is erased.
if (n > 1) {
ui32 wc, *lp, prior_free_mpn;
ui32* end = (ui32*)(ftl->main_buf + ftl->page_size);
// Save free map page number and force elist writes to begin
// on first page of a free map block.
prior_free_mpn = ftl->free_mpn;
ftl->free_mpn = (ui32)-1;
// Set pointer to write first entry on page.
lp = (ui32*)(ftl->main_buf + FTLN_META_DATA_BEG);
// Loop to find erased free blocks.
for (b = 0;;) {
if (IS_ERASED(ftl->bdata[b])) {
#if DEBUG_ELIST
// Verify that this block is unwritten.
FtlnCheckBlank(ftl, b);
#endif
// Write block number and wear count of erased block.
WR32_LE(b, lp);
++lp;
wc = ftl->high_wc - ftl->blk_wc_lag[b];
WR32_LE(wc, lp);
++lp;
// If all blocks recorded, fill rest of page with -1.
if (--n == 0)
while (lp != end) {
WR32_LE(-1, lp);
++lp;
}
// Check if page is full.
if (lp == end) {
// Write page of erased list data.
if (FtlnMetaWr(ftl, ERASED_LIST))
return -1;
// Break if all erased blocks have been recorded.
if (n == 0)
break;
// Reset pointer to write next entry on new page.
lp = (ui32*)(ftl->main_buf + FTLN_META_DATA_BEG);
// Assert not at block end. That requires 16B pages.
PfAssert(ftl->free_mpn != (ui32)-1);
}
}
// Check if no blocks left to test.
if (++b == ftl->num_blks) {
// If unwritten data in last page, write it now.
if (lp != (ui32*)(ftl->main_buf + FTLN_META_DATA_BEG))
if (FtlnMetaWr(ftl, ERASED_LIST))
return -1;
// List is finished, break.
break;
}
}
// Save elist block number and restore free map page number.
ftl->elist_blk = ftl->free_mpn / ftl->pgs_per_blk;
ftl->bdata[ftl->elist_blk] = FREE_BLK_FLAG;
++ftl->num_free_blks;
ftl->free_mpn = prior_free_mpn;
}
}
#endif // INC_ELIST
if (ftln_debug() > 1) {
// Display FTL statistics.
FtlnStats(ftl);
FtlnBlkStats(ftl);
}
// Return success.
return 0;
}
case FS_FLUSH_PAGE: {
ui32 vpn, mpn;
// Use the va_arg mechanism to get virtual page to be flushed.
va_start(ap, msg);
vpn = va_arg(ap, ui32);
va_end(ap);
// Check argument for validity.
PfAssert(vpn < ftl->num_vpages);
// Figure out MPN this page belongs to.
mpn = vpn / ftl->mappings_per_mpg;
// Flush MPN from cache. Return -1 if error.
if (ftlmcFlushPage(ftl->map_cache, mpn))
return -1;
#if INC_FTL_NDM_MLC
// For MLC devices, advance free_vpn pointer so next volume page
// write can't corrupt previously written valid page.
FtlnMlcSafeFreeVpn(ftl);
#endif
// Return success.
return 0;
}
case FS_MARK_UNUSED: {
ui32 ppn, count, past_end, vpn;
// Use va_arg mechanism to get the starting page and number of
// pages to be invalidated.
va_start(ap, msg);
vpn = va_arg(ap, ui32);
count = va_arg(ap, ui32);
va_end(ap);
// Check arguments for validity.
if (vpn + count > ftl->num_vpages)
return -1;
// Mark page(s) unused in FTL.
for (past_end = vpn + count; vpn < past_end; ++vpn) {
// Prepare to potentially write 1 map page. Return -1 if error.
if (FtlnRecCheck(ftl, -1))
return -1;
// Retrieve physical page number for VPN. Return -1 if error.
if (FtlnMapGetPpn(ftl, vpn, &ppn) < 0)
return -1;
// If unmapped, skip page.
if (ppn == (ui32)-1)
continue;
#if FS_ASSERT
// Confirm no physical page number changes below.
ftl->assert_no_recycle = TRUE;
#endif
// Assign invalid value to VPN's physical page number and
// decrement block's used page count.
if (FtlnMapSetPpn(ftl, vpn, (ui32)-1))
return -1;
PfAssert(ftl->num_free_blks >= FTLN_MIN_FREE_BLKS);
FtlnDecUsed(ftl, ppn, vpn);
#if FS_ASSERT
// End check for no physical page number changes.
ftl->assert_no_recycle = FALSE;
#endif
}
// Return success.
return 0;
}
case FS_VSTAT: {
vstat* buf;
// Use the va_arg mechanism to get the vstat buffer.
va_start(ap, msg);
buf = (vstat*)va_arg(ap, void*);
va_end(ap);
// Get the garbage level.
buf->garbage_level = FtlnGarbLvl(ftl);
// Record high wear count.
ftl->stats.wear_count = ftl->high_wc;
// Save running count of number of flash pages written.
ftl->fl_pg_writes += ftl->stats.write_page;
// Get TargetFTL-NDM RAM usage.
ftl->stats.ram_used = sizeof(struct ftln) + ftl->num_map_pgs * sizeof(ui32) + ftl->page_size +
ftl->eb_size * ftl->pgs_per_blk + ftlmcRAM(ftl->map_cache) +
ftl->num_blks * (sizeof(ui32) + sizeof(ui8));
if (ftln_debug() > 1) {
printf("TargetFTL-NDM RAM usage:\n");
printf(" - sizeof(Ftln) : %u\n", (int)sizeof(FTLN));
printf(" - tmp buffers : %u\n", ftl->page_size + ftl->eb_size * ftl->pgs_per_blk);
printf(" - map pages : %u\n", ftl->num_map_pgs * 4);
printf(" - map cache : %u\n", ftlmcRAM(ftl->map_cache));
printf(" - bdata[] : %u\n", ftl->num_blks * (int)(sizeof(ui32) + sizeof(ui8)));
}
const int kNumBuckets = sizeof(buf->wear_histogram) / sizeof(ui32);
PfAssert(kNumBuckets == 20);
int rv = FtlnGetWearHistogram(ftl, kNumBuckets, buf->wear_histogram);
PfAssert(rv == 0);
buf->num_blocks = ftl->num_blks;
// Set TargetFTL-NDM driver call counts and reset internal ones.
buf->ndm = ftl->stats;
memset(&ftl->stats, 0, sizeof(ftl_ndm_stats));
// Return success.
return 0;
}
// Similar to vstat but only counters are exported.
case FS_COUNTERS: {
// First argument should be a pointer towards a counter struct.
va_start(ap, msg);
FtlCounters* counters = (FtlCounters*)va_arg(ap, void*);
va_end(ap);
counters->wear_count = ftl->high_wc;
return 0;
}
case FS_MOUNT:
// Return error if already mounted. Else set mounted flag.
if (ftl->flags & FTLN_MOUNTED)
return FsError2(FTL_MOUNTED, EEXIST);
ftl->flags |= FTLN_MOUNTED;
if (ftln_debug() > 1) {
// Display FTL statistics.
FtlnStats(ftl);
FtlnBlkStats(ftl);
} else {
printf("FTL: total blocks: %u, free blocks: %u\n", ftl->num_blks, ftl->num_free_blks);
}
// Return success.
return 0;
}
// Return success.
return 0;
}
#if INC_FTL_NDM_MLC
// FtlnMlcSafeFreeVpn: For MLC devices, ensure free_vpn pointer is
// on a page whose pair is at a higher offset than the
// last non-free page
//
// Input: ftl = pointer to FTL control block
//
void FtlnMlcSafeFreeVpn(FTLN ftl) {
// Only adjust MLC volumes for which volume free pointer is set.
if (ftl->free_vpn != (ui32)-1) {
ui32 pn = ndmPastPrevPair(ftl->ndm, ftl->free_vpn);
if (ftln_debug() > 0) {
printf("FtlnMlcSafeFreeVpn: old free = %u, new free = %u\n", ftl->free_vpn, pn);
}
ftl->free_vpn = pn;
}
}
#endif // INC_FTL_NDM_MLC
// FtlnEraseBlk: Erase a block, increment its wear count, and mark it
// free and erased
//
// Inputs: ftl = pointer to FTL control block
// b = block to erase
//
// Returns: 0 on success, -1 on error
//
int FtlnEraseBlk(FTLN ftl, ui32 b) {
ui32 wc_lag, tb, ge_lim2_cnt = 0;
#if INC_ELIST
// Check if list of erased blocks/wear counts exists.
if (ftl->elist_blk != (ui32)-1) {
ui32 eb = ftl->elist_blk;
// Forget erased list block number.
ftl->elist_blk = (ui32)-1;
// If not this block, erase it - because its info is out-of-date.
if (eb != b)
if (FtlnEraseBlk(ftl, eb))
return -1;
}
#endif
// Call driver to erase block. Return -1 if error.
++ftl->stats.erase_block;
if (ndmEraseBlock(ftl->start_pn + b * ftl->pgs_per_blk, ftl->ndm)) {
ftl->logger.error(__FILE__, __LINE__, "FTL failed to erase block %u.",
ftl->start_pn / ftl->pgs_per_blk + b);
return FtlnFatErr(ftl);
}
// If not free, increment free blocks count. Mark free and erased.
if (IS_FREE(ftl->bdata[b]) == FALSE)
++ftl->num_free_blks;
ftl->bdata[b] = FREE_BLK_FLAG | ERASED_BLK_FLAG;
// Check if block has a positive wear count lag.
if (ftl->blk_wc_lag[b]) {
// Decrement block's wear lag and the total wear lag sum.
--ftl->blk_wc_lag[b];
--ftl->wc_lag_sum;
// Check if this block formerly had the maximum wear lag value.
if (ftl->blk_wc_lag[b] + 1 == ftl->wear_data.cur_max_lag) {
// Calculate new max lag. Count blocks w/lag over FtlnLim2Lag.
ftl->wear_data.cur_max_lag = 0;
for (tb = 0; tb < ftl->num_blks; ++tb) {
wc_lag = ftl->blk_wc_lag[tb];
if (ftl->wear_data.cur_max_lag < wc_lag)
ftl->wear_data.cur_max_lag = (uint8_t)wc_lag;
if (wc_lag > FtlnLim2Lag)
++ge_lim2_cnt;
}
}
// Else block has highest wear. Adjust lag counts of other blocks.
} else {
// To prepare for its update, clear the current maximum lag value.
ftl->wear_data.cur_max_lag = 0;
// Check the wear lag of all other blocks.
for (tb = 0; tb < ftl->num_blks; ++tb) {
// Skip this block, leaving its wear count lag at zero.
if (tb == b)
continue;
// Check if block's wear lag is less than the maximum value.
if (ftl->blk_wc_lag[tb] < 0xFF) {
// Update block's wear lag, the lag sum, and over lim2 count.
wc_lag = ftl->blk_wc_lag[tb];
ftl->blk_wc_lag[tb] = (uint8_t)++wc_lag;
if (wc_lag > FtlnLim2Lag)
++ge_lim2_cnt;
++ftl->wc_lag_sum;
// Else increment count of wear lag overflows.
} else {
++ftl->wear_data.max_wc_over;
++ge_lim2_cnt;
}
// If new values, update current and lifetime high wear lag.
wc_lag = ftl->blk_wc_lag[tb];
if (ftl->wear_data.cur_max_lag < wc_lag)
ftl->wear_data.cur_max_lag = (uint8_t)wc_lag;
}
// Increment the high wear count value.
++ftl->high_wc;
}
// Update lifetime max wear lag, avg lag, and over limit 2 count.
if (ftl->wear_data.lft_max_lag < ftl->wear_data.cur_max_lag)
ftl->wear_data.lft_max_lag = ftl->wear_data.cur_max_lag;
if (ftl->wear_data.max_ge_lim2 < ge_lim2_cnt)
ftl->wear_data.max_ge_lim2 = ge_lim2_cnt;
ftl->wear_data.avg_wc_lag = ftl->wc_lag_sum / ftl->num_blks;
// Return success.
return 0;
}
// FtlnLoWcFreeBlk: Find the free block with the lowest wear count
//
// Input: ftl = pointer to FTL control block
//
// Returns: block number if successful, else (ui32)-1 if none free
//
ui32 FtlnLoWcFreeBlk(CFTLN ftl) {
ui32 b, free_b, hi_lag;
// Search for first free block. Return error if no block is free.
free_b = first_free_blk(ftl);
if (free_b == (ui32)-1)
return free_b;
// Want free block with lowest wear count (highest wear lag).
hi_lag = ftl->blk_wc_lag[free_b];
for (b = free_b + 1; b < ftl->num_blks; ++b) {
if (IS_FREE(ftl->bdata[b]) == FALSE)
continue;
if (ftl->blk_wc_lag[b] > hi_lag) {
free_b = b;
hi_lag = ftl->blk_wc_lag[free_b];
}
}
// Return block number.
return free_b;
}
// FtlnHiWcFreeBlk: Find the free block with the highest wear count
//
// Input: ftl = pointer to FTL control block
//
// Returns: block number if successful, else (ui32)-1 if none free
//
ui32 FtlnHiWcFreeBlk(CFTLN ftl) {
ui32 b, free_b, lo_lag;
// Search for first free block. Return error if no block is free.
free_b = first_free_blk(ftl);
if (free_b == (ui32)-1)
return free_b;
// Want free block with highest wear count (lowest wear lag).
lo_lag = ftl->blk_wc_lag[free_b];
for (b = free_b + 1; b < ftl->num_blks; ++b) {
if (IS_FREE(ftl->bdata[b]) == FALSE)
continue;
if (ftl->blk_wc_lag[b] < lo_lag) {
free_b = b;
lo_lag = ftl->blk_wc_lag[free_b];
}
}
// Return block number.
return free_b;
}
// FtlnFormat: Erase all map blocks, mark all blocks free, and reset
// the FTL (keeping wear offsets)
//
// Inputs: ftl = pointer to FTL control block
// meta_block = number of block holding the metapage
//
// Returns: 0 on success, -1 on error
//
int FtlnFormat(FTLN ftl, ui32 meta_block) {
ui32 b;
PfAssert(meta_block < ftl->num_blks);
// Erase all map blocks, except the one containing the metapage.
for (b = 0; b < ftl->num_blks; ++b) {
// Skip non-map blocks.
if (!IS_MAP_BLK(ftl->bdata[b]))
continue;
// Skip block containing the metapage - this will be erased last.
if (b == meta_block)
continue;
// Erase map block. Return -1 if error.
if (FtlnEraseBlk(ftl, b))
return -1;
}
// Erase the block holding the metapage: format finished!
if (FtlnEraseBlk(ftl, meta_block))
return -1;
// Mark all non-erased blocks as free with zero read wear.
for (b = 0; b < ftl->num_blks; ++b)
if (!IS_FREE(ftl->bdata[b]))
ftl->bdata[b] = FREE_BLK_FLAG;
ftl->num_free_blks = ftl->num_blks;
// Re-initialize volume state.
FtlnStateRst(ftl);
ftl->high_bc = 1; // initial block count of unformatted volumes
if (ftln_debug() > 0) {
// Display FTL statistics.
FtlnBlkStats(ftl);
}
// Return success.
return 0;
}
// FtlnStateRst: Initialize volume state (except wear count offsets)
//
// Input: ftl = pointer to FTL control block
//
void FtlnStateRst(FTLN ftl) {
uint n;
ftl->high_bc = 0;
ftl->high_bc_mblk = ftl->resume_vblk = (ui32)-1;
ftl->high_bc_mblk_po = 0;
ftl->copy_end_found = FALSE;
ftl->max_rc_blk = (ui32)-1;
ftl->free_vpn = ftl->free_mpn = (ui32)-1;
#if INC_ELIST
ftl->elist_blk = (ui32)-1;
#endif
ftl->deferment = 0;
#if FS_ASSERT
ftl->assert_no_recycle = FALSE;
#endif
memset(ftl->spare_buf, 0xFF, ftl->pgs_per_blk * ftl->eb_size);
for (n = 0; n < ftl->num_map_pgs; ++n)
ftl->mpns[n] = (ui32)-1;
ftlmcInit(ftl->map_cache);
}
// FtlnDecUsed: Decrement block used count for page no longer in-use
//
// Inputs: ftl = pointer to FTL control block
// pn = physical page number
// vpn = virtual page number
//
void FtlnDecUsed(FTLN ftl, ui32 pn, ui32 vpn) {
ui32 b = pn / ftl->pgs_per_blk;
// Decrement block used count.
PfAssert(NUM_USED(ftl->bdata[b]));
PfAssert(!IS_FREE(ftl->bdata[b]));
DEC_USED(ftl->bdata[b]);
if (ftln_debug() > 0) {
// Read page spare area and assert VPNs match.
++ftl->stats.read_spare;
// Ignore errors here.
if (ndmReadSpare(ftl->start_pn + pn, ftl->spare_buf, ftl->ndm) >= 0) {
PfAssert(GET_SA_VPN(ftl->spare_buf) == vpn);
}
}
} // lint !e818
// FtlnFatErr: Process FTL-NDM fatal error
//
// Input: ftl = pointer to FTL control block
//
// Returns: -1
//
int FtlnFatErr(FTLN ftl) {
ftl->flags |= FTLN_FATAL_ERR;
return FsError2(NDM_EIO, EIO);
}
// FtlnGetWearData: Return FTL's wear data
//
// Input: ftl = pointer to FTL control block
//
// Returns: Copy of FTL's wear metrics
//
FtlWearData FtlnGetWearData(void* vol) {
uint b, n, lag, num_vblks = 0, use_sum = 0;
uint lag_sum_squared = 0, used_sum_squared = 0;
FTLN ftl = vol;
FtlWearData wear_data = ftl->wear_data;
double d1, d2, d3, d4;
// Loop over the block wear count lag array, calculating metrics.
wear_data.lag_ge_lim1 = wear_data.lag_ge_lim0 = 0;
for (b = 0; b < ftl->num_blks; ++b) {
lag = ftl->blk_wc_lag[b];
if (lag >= FtlnLim1Lag)
++wear_data.lag_ge_lim1;
if (lag >= FtlnLim0Lag)
++wear_data.lag_ge_lim0;
lag_sum_squared += lag * lag;
// Finished if map block or free block.
if (IS_MAP_BLK(ftl->bdata[b]) || IS_FREE(ftl->bdata[b]))
continue;
// Calculate values for used page standard deviation.
n = NUM_USED(ftl->bdata[b]);
use_sum += n;
used_sum_squared += n * n;
++num_vblks;
}
// Calculate the average number of consecutive recycles.
if (ftl->recycle_needed) {
uint n;
n = 100 * wear_data.recycle_cnt;
n = n / ftl->recycle_needed;
n = n + 5;
n = n / 10;
wear_data.avg_consec_rec = n;
} else
wear_data.avg_consec_rec = 0;
// Calculate the current write amplification.
if (ftl->vol_pg_writes) {
uint n;
n = ftl->fl_pg_writes + ftl->stats.write_page;
n = 100 * n;
n = n / ftl->vol_pg_writes;
n = n + 5;
n = n / 10;
wear_data.write_amp_avg = n;
} else
wear_data.write_amp_avg = 0;
// Calculate the standard deviation of the block wear lag.
d1 = lag_sum_squared;
d1 *= ftl->num_blks;
d2 = ftl->wc_lag_sum;
d2 *= ftl->wc_lag_sum;
d3 = ftl->num_blks * (ftl->num_blks - 1);
d4 = (d1 - d2) / d3;
wear_data.lag_sd = sqrt(d4);
// Calculate the standard deviation of used pages per block.
d1 = used_sum_squared;
d1 *= num_vblks;
d2 = use_sum;
d2 *= use_sum;
d3 = num_vblks * (num_vblks - 1);
d4 = (d1 - d2) / d3;
wear_data.used_sd = sqrt(d4);
// Return structure of metrics on wear data.
return wear_data;
}
#if DEBUG_ELIST
// FtlnCheckBlank: Ensure the specified block is blank
//
// Inputs: ftl = pointer to FTL control block
// b = block number of block to check
//
void FtlnCheckBlank(FTLN ftl, ui32 b) {
ui32 pn = b * ftl->pgs_per_blk;
ui32 end = pn + ftl->pgs_per_blk;
int rc;
do {
rc = ftl->page_check(pn, ftl->main_buf, ftl->spare_buf, ftl->ndm);
PfAssert(rc == NDM_PAGE_ERASED);
} while (++pn < end);
}
#endif // DEBUG_ELIST