| // 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 <stdarg.h> |
| #include <string.h> |
| |
| #include "ftlnp.h" |
| |
| // 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); |
| } |
| |
| // set_high_wc: Set highest wear count and adjust wear offsets |
| // |
| // Inputs: ftl = pointer to FTL control block |
| // high_b = block with new highest wear count |
| // high_b_wc = new highest wear count |
| // |
| static void set_high_wc(FTLN ftl, ui32 high_b, ui32 high_b_wc) { |
| ui32 b; |
| |
| // Highest wear count should only go up by one and new highest block |
| // should have contained highest wear (0 'high_wc' lag) before. |
| PfAssert(ftl->high_wc + 1 == high_b_wc && ftl->blk_wc_lag[high_b] == 0); |
| |
| // Loop over all other blocks adjusting their 'high_wc' lags. |
| for (b = 0; b < ftl->num_blks; ++b) |
| if (b != high_b) { |
| if (ftl->blk_wc_lag[b] < 0xFF) |
| ++ftl->blk_wc_lag[b]; |
| #if FTLN_DEBUG |
| else |
| ++ftl->max_wc_over; |
| |
| // If new value, record maximum encountered wear lag. |
| if (ftl->max_wc_lag < ftl->blk_wc_lag[b]) |
| ftl->max_wc_lag = ftl->blk_wc_lag[b]; |
| #endif |
| } |
| |
| // Update highest wear count. |
| ftl->high_wc = high_b_wc; |
| } |
| |
| // 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)) |
| 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; |
| } |
| |
| // 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); |
| #endif |
| |
| // 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: { |
| union vstat* buf; |
| |
| // Use the va_arg mechanism to get the vstat buffer. |
| va_start(ap, msg); |
| buf = (union vstat*)va_arg(ap, void*); |
| va_end(ap); |
| |
| // Get the garbage level. |
| buf->xfs.garbage_level = FtlnGarbLvl(ftl); |
| |
| // 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))); |
| #endif |
| |
| // Record high wear count. |
| ftl->stats.wear_count = ftl->high_wc; |
| |
| // Set TargetFTL-NDM driver call counts and reset internal ones. |
| buf->xfs.drvr_stats.ftl.ndm = ftl->stats; |
| memset(&ftl->stats, 0, sizeof(ftl_ndm_stats)); |
| |
| // Return success. |
| 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); |
| #elif FTLN_DEBUG |
| printf("FTL: total blocks: %u, free blocks: %u\n", ftl->num_blks, ftl->num_free_blks); |
| #endif |
| |
| // 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 |
| printf("FtlnMlcSafeFreeVpn: old free = %u, new free = %u\n", ftl->free_vpn, pn); |
| #endif |
| 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 b_wc; |
| |
| #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)) |
| return FtlnFatErr(ftl); |
| |
| // Increment block wear count and possibly adjust highest. |
| b_wc = ftl->high_wc - ftl->blk_wc_lag[b] + 1; |
| if (ftl->high_wc < b_wc) |
| set_high_wc(ftl, b, b_wc); |
| else |
| --ftl->blk_wc_lag[b]; |
| |
| // 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; |
| |
| // 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; |
| |
| // 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; |
| |
| // Continue search. Want free block with lowest wear count. |
| for (b = free_b + 1; b < ftl->num_blks; ++b) |
| if (IS_FREE(ftl->bdata[b]) && (ftl->blk_wc_lag[b] > ftl->blk_wc_lag[free_b])) |
| free_b = 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; |
| |
| // 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; |
| |
| // Continue search. Want free block with highest wear count. |
| for (b = free_b + 1; b < ftl->num_blks; ++b) |
| if (IS_FREE(ftl->bdata[b]) && (ftl->blk_wc_lag[b] < ftl->blk_wc_lag[free_b])) |
| free_b = 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 |
| // Display FTL statistics. |
| FtlnBlkStats(ftl); |
| #endif |
| |
| // Return success. |
| return 0; |
| } |
| |
| // FtlnStateRst: Initialize volume state (except wear count offsets) |
| // |
| // Input: ftl = pointer to FTL control block |
| // |
| void FtlnStateRst(FTLN ftl) { |
| ui32 n; // TIMER: was 'int' |
| |
| 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 FTLN_DEBUG |
| ftl->max_wc_lag = 0; |
| #endif |
| #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 |
| // Read page spare area and assert VPNs match. |
| ++ftl->stats.read_spare; |
| PfAssert(ndmReadSpare(ftl->start_pn + pn, ftl->spare_buf, ftl->ndm) >= 0); |
| PfAssert(GET_SA_VPN(ftl->spare_buf) == vpn); |
| #endif |
| } //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); |
| } |
| |
| #if FTLN_DEBUG |
| // flush_bstat: Flush buffered statistics counts |
| // |
| // Inputs: ftl = pointer to FTL control block |
| // b = block number of current block |
| // type = "FREE", "MAP", or "VOLUME" |
| // In/Outputs: *blk0 = first consecutive block number or -1 |
| // *blke = end consecutive block number |
| // |
| static void flush_bstat(CFTLN ftl, int* blk0, int* blke, int b, const char* type) { |
| if (*blk0 == -1) |
| *blk0 = *blke = b; |
| else if (*blke + 1 == b) |
| *blke = b; |
| else { |
| printf("B = %4u", *blk0); |
| if (*blk0 == *blke) { |
| printf(" - used = %2u, wc lag = %3d, rc = %8u", NUM_USED(ftl->bdata[*blk0]), |
| ftl->blk_wc_lag[*blk0], GET_RC(ftl->bdata[*blk0])); |
| printf(" - %s BLOCK\n", type); |
| } else { |
| printf("-%-4u", *blke); |
| printf("%*s", 37, " "); |
| printf("- %s BLOCKS\n", type); |
| } |
| *blk0 = *blke = b; |
| } |
| } |
| |
| // FtlnBlkStats: Debug function to display blocks statistics |
| // |
| // Input: ftl = pointer to FTL control block |
| // |
| void FtlnBlkStats(CFTLN ftl) { |
| int free0 = -1, freee, vol0 = -1, vole; |
| ui32 b; |
| |
| printf( |
| "\nBLOCK STATS: %u blocks, %u pages per block, curr free " |
| "blocks = %u\n", |
| ftl->num_blks, ftl->pgs_per_blk, ftl->num_free_blks); |
| |
| // Loop over FTL blocks. |
| for (b = 0; b < ftl->num_blks; ++b) { |
| // Check if block is free. |
| if (IS_FREE(ftl->bdata[b])) { |
| flush_bstat(ftl, &vol0, &vole, -1, "VOLUME"); |
| flush_bstat(ftl, &free0, &freee, b, "FREE"); |
| } |
| |
| // Else check if map block. |
| else if (IS_MAP_BLK(ftl->bdata[b])) { |
| flush_bstat(ftl, &free0, &freee, -1, "FREE"); |
| flush_bstat(ftl, &vol0, &vole, -1, "VOLUME"); |
| printf("B = %4u - used = %2u, wc lag = %3d, rc = %8u - ", b, NUM_USED(ftl->bdata[b]), |
| ftl->blk_wc_lag[b], GET_RC(ftl->bdata[b])); |
| printf("MAP BLOCK\n"); |
| } |
| |
| // Else is volume block. |
| else { |
| flush_bstat(ftl, &free0, &freee, -1, "FREE"); |
| #if FTLN_DEBUG <= 1 |
| flush_bstat(ftl, &vol0, &vole, b, "VOLUME"); |
| #else |
| printf("B = %4u - used = %2u, wc lag = %3d, rc = %8u - ", b, NUM_USED(ftl->bdata[b]), |
| ftl->blk_wc_lag[b], GET_RC(ftl->bdata[b])); |
| printf("VOLUME BLOCK\n"); |
| #endif |
| } |
| } |
| flush_bstat(ftl, &free0, &freee, -1, "FREE"); |
| flush_bstat(ftl, &vol0, &vole, -1, "VOLUME"); |
| } |
| #endif // FTLN_DEBUG |
| |
| #if FTLN_DEBUG > 1 |
| // FtlnStats: Display FTL statistics |
| // |
| // Input: ftl = pointer to FTL control block |
| // |
| void FtlnStats(FTLN ftl) { |
| ui32 b, n; |
| |
| printf("\nFTL STATS:\n"); |
| printf(" - # vol pages = %d\n", ftl->num_vpages); |
| printf(" - # map pages = %d\n", ftl->num_map_pgs); |
| printf(" - # free blocks = %d\n", ftl->num_free_blks); |
| for (n = b = 0; b < ftl->num_blks; ++b) |
| if (IS_ERASED(ftl->bdata[b])) |
| ++n; |
| printf(" - # erased blks = %d\n", n); |
| printf(" - flags ="); |
| if (ftl->flags & FTLN_FATAL_ERR) |
| printf(" FTLN_FATAL_ERR"); |
| if (ftl->flags & FTLN_MOUNTED) |
| printf(" FTLN_MOUNTED"); |
| putchar('\n'); |
| } |
| #endif // FTLN_DEBUG |
| |
| #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 |
| |