blob: 7aaa52e368419a6bd3576b168e992521070230c6 [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 "ndmp.h"
#include <kprivate/fsprivate.h>
// Symbol Definitions
#define NDM_META_BLKS 2 // blocks reserved for internal use
// Global Variable Declarations
CircLink NdmDevs;
SEM NdmSem;
static int NdmSemCount;
#if NV_NDM_CTRL_STORE
static ui8 NdmDevCnt;
#endif
// Local Function Definitions
// get_page_status: Examine page to see if it is an NDM control page
//
// Inputs: ndm = pointer to NDM control block
// pn = page to examine
//
// Returns: -1 if I/O error, NDM_CTRL_BLOCK (2) if control page,
// or NDM_REG_BLOCK (3) if ECC, CRC, sig, or flag fails
//
static int get_page_status(CNDM ndm, ui32 pn) {
int i, j, status;
ui32 crc;
// Read spare area to check page type. Use read_decode_spare() if
// decode is 'free', to avoid second read. Return -1 if I/O error,
// regular block if ECC error.
if (FLAG_IS_CLR(ndm->flags, FSF_FREE_SPARE_ECC)) {
status = ndm->read_spare(pn, ndm->spare_buf, ndm->dev);
} else {
status = ndm->read_decode_spare(pn, ndm->spare_buf, ndm->dev);
if (status == -1)
return NDM_REG_BLOCK;
}
if (status < 0)
return FsError2(NDM_EIO, EIO);
// Block is regular block if regular page mark is not cleared.
if (ONES_UI8(ndm->spare_buf[EB_REG_MARK]) >= 7)
return NDM_REG_BLOCK;
// If not done already, read decode spare area. Return -1 if fatal
// error, regular block if ECC error.
if (FLAG_IS_CLR(ndm->flags, FSF_FREE_SPARE_ECC)) {
status = ndm->read_decode_spare(pn, ndm->spare_buf, ndm->dev);
if (status == -2)
return FsError2(NDM_EIO, EIO);
else if (status == -1)
return NDM_REG_BLOCK;
}
// Check signature in spare area to ensure this is a control page.
for (i = j = 0; i < CTRL_SIG_SZ; ++i) {
// Skip the bad block mark in extra bytes.
if (i == EB_BBLOCK_MARK)
++j;
// Block is regular block if signature is invalid.
if (ndm->spare_buf[i + j] != CTRL_SIG[i])
return NDM_REG_BLOCK;
}
// Read main data. Return -1 if fatal err, regular block if ECC err.
status = ndm->read_page(pn, ndm->main_buf, ndm->spare_buf, ndm->dev);
if (status == -2)
return FsError2(NDM_EIO, EIO);
else if (status == -1)
return NDM_REG_BLOCK;
// Perform CRC on page. The 4 CRC bytes are in the page header.
// First perform CRC on all but the 4 CRC bytes.
for (crc = CRC32_START, i = 0; (ui32)i < ndm->page_size; ++i) {
if (i == HDR_CRC_LOC)
i = CTRL_DATA_START;
crc = CRC32_UPDATE(crc, ndm->main_buf[i]);
}
// Now run the CRC bytes through the CRC encoding.
for (i = HDR_CRC_LOC; i < CTRL_DATA_START; ++i) //lint !e850
crc = CRC32_UPDATE(crc, ndm->main_buf[i]);
// If the CRC does not match, page is not control page.
if (crc != CRC32_FINAL)
return NDM_REG_BLOCK;
// Valid signature found. This is a control block.
return NDM_CTRL_BLOCK;
}
// format_status: Check if NDM device is formatted
//
// Inputs: ndm = pointer to NDM control block
//
// Returns: 0 if formatted, else -1 with error code in FsErrCode
//
// Note: If found, metadata block # is saved in ctrl_blk0
//
static int format_status(NDM ndm) {
ui32 b;
int status;
// Search reserved good blocks for control info, starting from end.
for (b = ndm->num_dev_blks - 1;; --b) {
ui32 pn = b * ndm->pgs_per_blk;
// Get block's initial good/bad status. Return -1 if error.
status = ndm->is_block_bad(pn, ndm->dev);
if (status < 0)
return FsError2(NDM_EIO, EIO);
// If good, check block's first page for control information.
if (status == FALSE) {
// Get page status. Return -1 if error.
status = get_page_status(ndm, pn);
if (status == -1)
return -1;
// If control info found, device is formatted. Return TRUE.
if (status == NDM_CTRL_BLOCK) {
#if NDM_DEBUG
printf("NDM formatted - block #%u has control info!\n", b);
#endif
PfAssert(ndm->ctrl_blk0 == (ui32)-1);
ndm->ctrl_blk0 = b;
return 0;
}
}
// Return FALSE if no metadata in range used for control blocks.
if (b == ndm->num_dev_blks - NDM_META_BLKS - ndm->max_bad_blks)
return FsError2(NDM_NO_META_BLK, ENXIO);
}
}
// get_free_ctrl_blk: Get next free block reserved for replacing bad
// control blocks (starts at highest and goes down)
//
// Input: ndm = pointer to NDM control block
//
// Returns: Block number of free block or (ui32)-1 if none left
//
static ui32 get_free_ctrl_blk(NDM ndm) {
ui32 free_b = ndm->free_ctrl_blk;
// Move free control block pointer one down, if any free blocks left.
if (free_b != (ui32)-1) {
ui32 b = free_b - 1;
// Skip past initial bad blocks.
while (b >= ndm->free_virt_blk && ndmInitBadBlock(ndm, b)) --b;
// If above the blocks reserved for swapping bad virtual blocks,
// update the free control block pointer. Else fail.
if (b >= ndm->free_virt_blk)
ndm->free_ctrl_blk = b;
else
ndm->free_virt_blk = ndm->free_ctrl_blk = (ui32)-1;
}
// Return free control block number or -1.
return free_b;
}
// set_frst_ndm_block: Compute the cutoff point between virtual area
// and the NDM reserved area
//
// Input: ndm = pointer to NDM control block
//
static void set_frst_ndm_block(NDM ndm) {
ui32 i;
// There must be enough good (non-initial bad) blocks before the cut
// off point to hold all the virtual blocks. Find the lowest offset
// past the virtual block count that satisfies this.
for (i = 0;; ++i) {
// If the offset reaches the number of initially bad blocks, then
// there are definitely num_vblks good blocks below this cutoff.
if (i == ndm->num_bad_blks)
break;
// 'i' is the number of initial bad blocks preceding the indexed
// initial bad block. Break when the number of volume blocks and
// skipped bad blocks is less than the indexed initial bad block.
if (ndm->num_vblks + i < ndm->init_bad_blk[i])
break;
}
// The cutoff point is num_vblks plus the determined offset.
ndm->frst_reserved = ndm->num_vblks + i;
}
// init_ibad_list: Initialize list of initially bad blocks
//
// Input: ndm = pointer to NDM control block
//
// Returns: 0 on success, -1 on error
//
static int init_ibad_list(NDM ndm) {
int status;
ui32 b;
// Build the initial bad block map by scanning all blocks in order
// from lowest to highest. Supports "skip bad block" programming.
ndm->num_bad_blks = 0;
for (b = 0; b < ndm->num_dev_blks; ++b) {
// Get block's initial good/bad status. Return -1 if error.
status = ndm->is_block_bad(b * ndm->pgs_per_blk, ndm->dev);
if (status < 0)
return FsError2(NDM_EIO, EIO);
// Check if block is an initial bad block.
if (status == TRUE) {
// If too many bad blocks encountered, error. Return -1.
if (ndm->num_bad_blks >= ndm->max_bad_blks)
return FsError2(NDM_TOO_MANY_IBAD, EINVAL);
// Add block to initial bad blocks array and increment bad count.
ndm->init_bad_blk[ndm->num_bad_blks] = b;
#if NDM_DEBUG
printf("init_ibad_lis: adding block #%u to init_bad_blk[%u]\n", b, ndm->num_bad_blks);
#endif
++ndm->num_bad_blks;
}
}
// Set the last initial bad block entry to the device block count.
ndm->init_bad_blk[ndm->num_bad_blks] = ndm->num_dev_blks;
#if NDM_DEBUG
printf("init_ibad_lis: LAST init_bad_blk[%u] = %u\n", ndm->num_bad_blks, ndm->num_dev_blks);
#endif
// Return success.
return 0;
}
// ndm_format: Format previously unformatted device
//
// Input: ndm = pointer to NDM control block
//
// Returns: 0 on success, -1 on error
//
static int ndm_format(NDM ndm) {
ui32 b;
#if NV_NDM_CTRL_STORE
// Invalidate the saved first page of NDM control information.
NvNdmCtrlPgWr(0);
#endif
// Build the initial bad block map by scanning all blocks in order.
if (init_ibad_list(ndm))
return -1;
// Compute the cutoff between virtual blocks and reserved blocks.
set_frst_ndm_block(ndm);
// Set the free control block (last good block) and free volume
// block (first good block after cutoff) pointers.
for (b = ndm->frst_reserved; b < ndm->num_dev_blks; ++b) {
// If initial bad block, skip it.
if (ndmInitBadBlock(ndm, b))
continue;
// If first free block pointer is not set, set it.
if (ndm->free_virt_blk == (ui32)-1)
ndm->free_virt_blk = b;
// Set the last good block as the start of the metadata blocks.
ndm->free_ctrl_blk = b;
}
// The last two good free blocks are used for control information.
ndm->ctrl_blk0 = get_free_ctrl_blk(ndm);
ndm->ctrl_blk1 = get_free_ctrl_blk(ndm);
if (ndm->ctrl_blk1 == (ui32)-1)
return FsError2(NDM_NO_FREE_BLK, ENOSPC);
#if NDM_DEBUG
printf("NDM ctrl_blk0=%u, ctrl_blk1=%u\n", ndm->ctrl_blk0, ndm->ctrl_blk1);
#endif
// Begin the first control write on ctrl_blk0.
ndm->next_ctrl_start = ndm->ctrl_blk0 * ndm->pgs_per_blk;
// Write initial control information and return status.
ndm->xfr_tblk = (ui32)-1;
return ndmWrCtrl(ndm);
}
// find_last_ctrl_info: Find last valid control information
//
// Input: ndm = pointer to NDM control block
//
// Returns: 0 if found, -1 otherwise
//
static int find_last_ctrl_info(NDM ndm) {
ui32 b, p, high_seq = (ui32)-1, curr_seq;
ui32 p_beg, p_end, last_ctrl_p = 0, ctrl_pages = 0;
ui16 curr_p, last_p;
int page_status;
#if NV_NDM_CTRL_STORE
// Check if number of first control information page has been saved.
p = NvNdmCtrlPgRd();
if (p) {
// Get the page status. If I/O error, return -1.
page_status = get_page_status(ndm, p);
if (page_status == -1)
return -1;
// Check if it is a control page.
if (page_status == NDM_CTRL_BLOCK) {
// Check if it is the last page of a control information write.
curr_p = RD16_LE(&ndm->main_buf[HDR_CURR_LOC]);
ctrl_pages = RD16_LE(&ndm->main_buf[HDR_LAST_LOC]);
if (curr_p == ctrl_pages) {
// Read its (the highest) sequence number and use this page.
high_seq = RD32_LE(&ndm->main_buf[HDR_SEQ_LOC]);
last_ctrl_p = p;
}
}
}
// If last control page not known from NVRAM, search all reserved
// blocks for it: from ctrl_blk0 (highest block w/control info) to
// num_vblks.
if (last_ctrl_p == 0)
#endif
{
for (b = ndm->ctrl_blk0; b >= ndm->num_vblks; --b) {
// Get first and last page numbers to search.
p_beg = b * ndm->pgs_per_blk;
p_end = p_beg + ndm->pgs_per_blk - 1;
// Check if not block that format_status() found metadata on.
if (b != ndm->ctrl_blk0) {
// Get status of block's first page. If error, return -1.
page_status = get_page_status(ndm, p_beg);
if (page_status == -1)
return -1;
// Skip block if its first page is not a control page.
if (page_status != NDM_CTRL_BLOCK)
continue;
}
// Search block from end to beginning for last control page.
for (p = p_end; p >= p_beg; --p) {
// Get the page status. If error, return -1.
page_status = get_page_status(ndm, p);
if (page_status == -1)
return -1;
// If page is not control page, skip it.
if (page_status != NDM_CTRL_BLOCK)
continue;
// If this is not the last page of a control info, skip it.
curr_p = RD16_LE(&ndm->main_buf[HDR_CURR_LOC]);
last_p = RD16_LE(&ndm->main_buf[HDR_LAST_LOC]);
if (curr_p != last_p)
continue;
// Read current sequence number.
curr_seq = RD32_LE(&ndm->main_buf[HDR_SEQ_LOC]);
// If first 'last page' found or most recent, remember it.
if (high_seq == (ui32)-1 || curr_seq > high_seq) {
// Remember its sequence number and first/last page numbers.
high_seq = curr_seq;
last_ctrl_p = p;
ctrl_pages = last_p;
#if NDM_DEBUG
printf(
"find_ctrl: seq #%u, last = %u (block = %u), "
"# pages = %u\n",
high_seq, last_ctrl_p, last_ctrl_p / ndm->pgs_per_blk, ctrl_pages);
#endif
}
// Break to search next block.
break;
}
}
}
// If no last control page found, no control information on device.
if (high_seq == (ui32)-1)
return FsError2(NDM_NO_META_DATA, ENXIO);
// Save information found so far.
ndm->last_ctrl_page = last_ctrl_p;
ndm->ctrl_pages = ctrl_pages;
ndm->ctrl_seq = high_seq;
// If control information is only one page, finish (success!) here.
if (ctrl_pages == 1) {
ndm->frst_ctrl_page = last_ctrl_p;
return 0;
}
// Search for first page of latest control info in block with last.
p_end = (last_ctrl_p / ndm->pgs_per_blk) * ndm->pgs_per_blk;
for (p = last_ctrl_p - 1; p >= p_end; --p) {
// Get the page status. If error, return -1.
page_status = get_page_status(ndm, p);
if (page_status == -1)
return -1;
// If page is not control page, skip it.
if (page_status != NDM_CTRL_BLOCK)
continue;
// If matching first control page found, return success.
curr_seq = RD32_LE(&ndm->main_buf[HDR_SEQ_LOC]);
curr_p = RD16_LE(&ndm->main_buf[HDR_CURR_LOC]);
last_p = RD16_LE(&ndm->main_buf[HDR_LAST_LOC]);
if (curr_p == 1 && curr_seq == high_seq && last_p == ctrl_pages) {
#if NDM_DEBUG
printf("find_ctrl: first = %u (block = %u)\n", p, p / ndm->pgs_per_blk);
#endif
ndm->frst_ctrl_page = p;
return 0;
}
}
// First page wasn't found, scan all other NDM reserved blocks.
for (b = ndm->ctrl_blk0; b >= ndm->num_vblks; --b) {
// Skip the scanned block.
if (b == last_ctrl_p / ndm->pgs_per_blk)
continue;
// Get first and last page numbers to search.
p_beg = b * ndm->pgs_per_blk;
p_end = p_beg + ndm->pgs_per_blk - 1;
// Start scanning the pages in the block.
for (p = p_beg; p <= p_end; ++p) {
// Get the page status. If error, return -1.
page_status = get_page_status(ndm, p);
if (page_status == -1)
return -1;
// If page is not control page, skip it.
if (page_status != NDM_CTRL_BLOCK)
continue;
// If first control page found, return success.
curr_seq = RD32_LE(&ndm->main_buf[HDR_SEQ_LOC]);
curr_p = RD16_LE(&ndm->main_buf[HDR_CURR_LOC]);
last_p = RD16_LE(&ndm->main_buf[HDR_LAST_LOC]);
if (curr_p == 1 && curr_seq == high_seq && last_p == ctrl_pages) {
#if NDM_DEBUG
printf("find_ctrl: first = %u (block = %u)\n", p, p / ndm->pgs_per_blk);
#endif
ndm->frst_ctrl_page = p;
return 0;
}
}
}
// First control page not found, return -1.
return FsError2(NDM_NO_META_DATA, ENXIO);
}
// is_next_ctrl_page: Determine if page is next in control sequence
//
// Inputs: ndm = pointer to NDM control block
// pn = page to analyze
// curr_num = current number in control sequence
//
// Returns: NDM_CTRL_BLOCK iff page next one, NDM_REG_BLOCK if
// not, -1 on error
//
static int is_next_ctrl_page(CNDM ndm, ui32 pn, ui16 curr_num) {
int page_status;
// Get the page status.
page_status = get_page_status(ndm, pn);
if (page_status != NDM_CTRL_BLOCK)
return page_status;
// Read page in. Return -1 if error (ECC or fatal).
if (ndm->read_page(pn, ndm->main_buf, ndm->spare_buf, ndm->dev) < 0)
return FsError2(NDM_EIO, EIO);
// Determine if this is the next control page in sequence.
if (RD16_LE(&ndm->main_buf[HDR_CURR_LOC]) == curr_num + 1 &&
RD16_LE(&ndm->main_buf[HDR_LAST_LOC]) == ndm->ctrl_pages &&
RD32_LE(&ndm->main_buf[HDR_SEQ_LOC]) == ndm->ctrl_seq)
return NDM_CTRL_BLOCK;
// Else this is a regular block.
return NDM_REG_BLOCK;
}
// get_next_ctrl_page: Retrieve next page in control info
//
// Inputs: ndm = pointer to NDM control block
// curr_p = current control page
//
// Returns: Next control page, -1 on error
//
static ui32 get_next_ctrl_page(CNDM ndm, ui32 curr_p) {
ui16 curr_num;
ui32 p, p_end;
int page_status;
// Retrieve current number in control info sequence.
curr_num = RD16_LE(&ndm->main_buf[HDR_CURR_LOC]);
// If there's no next control page according to header, return -1.
if (curr_num >= ndm->ctrl_pages)
return (ui32)FsError2(NDM_BAD_META_DATA, EINVAL);
// Look for page in same block first.
for (p = curr_p + 1; p % ndm->pgs_per_blk; ++p) {
page_status = is_next_ctrl_page(ndm, p, curr_num);
if (page_status == NDM_CTRL_BLOCK)
return p;
else if (page_status == -1)
return (ui32)-1;
}
// Get first and last page numbers in opposing control block.
if (curr_p / ndm->pgs_per_blk == ndm->ctrl_blk0)
p = ndm->ctrl_blk1 * ndm->pgs_per_blk;
else
p = ndm->ctrl_blk0 * ndm->pgs_per_blk;
p_end = p + ndm->pgs_per_blk - 1;
// Search second control block for next control page.
do {
page_status = is_next_ctrl_page(ndm, p, curr_num);
if (page_status == NDM_CTRL_BLOCK)
return p;
else if (page_status == -1)
return (ui32)-1;
} while (++p <= p_end);
// At this point, no next page can be found.
return (ui32)FsError2(NDM_BAD_META_DATA, EINVAL);
}
// check_next_read: If next read spans control pages, adjust the
// current control information pointers
//
// Input: ndm = pointer to NDM control block
// In/Output:curr_loc = current location in control page
// In/Output:pn = current control page
// In/Output:ctrl_pages = control pages read so far
// Input: size = size of next read in bytes
//
// Returns: 0 on success, -1 on failure
//
static int check_next_read(CNDM ndm, ui32* curr_loc, ui32* pn, ui32* ctrl_pages, ui32 size) {
// If next read goes past end of current control page, read next.
if (*curr_loc + size > ndm->page_size) {
// Figure out where the next page is.
*pn = get_next_ctrl_page(ndm, *pn);
if (*pn == (ui32)-1)
return -1;
// Reset current control location to beginning of the next page.
*curr_loc = CTRL_DATA_START;
*ctrl_pages += 1;
// Read the next page. Return -1 if error (ECC or fatal).
if (ndm->read_page(*pn, ndm->main_buf, ndm->spare_buf, ndm->dev) < 0)
return FsError2(NDM_EIO, EIO);
#if NDM_DEBUG
printf("read_ctrl: READ page #%u\n", *pn);
#endif
}
// Return success.
return 0;
}
// read_ctrl_info: Read the NDM control information
//
// Input: ndm = pointer to NDM control block
//
// Returns: 0 on success, -1 on failure
//
static int read_ctrl_info(NDM ndm) {
ui32 bn, vbn, i, curr_loc = CTRL_DATA_START, ctrl_pages = 1;
ui32 p = ndm->frst_ctrl_page;
// Read the first control page. Return -1 if error (ECC or fatal).
if (ndm->read_page(p, ndm->main_buf, ndm->spare_buf, ndm->dev) < 0)
return FsError2(NDM_EIO, EIO);
#if NDM_DEBUG
printf("read_ctrl: READ page #%u\n", p);
#endif
// Ensure the number of blocks and block size are correct.
if (ndm->num_dev_blks != RD32_LE(&ndm->main_buf[curr_loc]))
return FsError2(NDM_BAD_META_DATA, EINVAL);
curr_loc += sizeof(ui32);
if (ndm->block_size != RD32_LE(&ndm->main_buf[curr_loc]))
return FsError2(NDM_BAD_META_DATA, EINVAL);
curr_loc += sizeof(ui32);
// Retrieve the control block pointers.
ndm->ctrl_blk0 = RD32_LE(&ndm->main_buf[curr_loc]);
curr_loc += sizeof(ui32);
ndm->ctrl_blk1 = RD32_LE(&ndm->main_buf[curr_loc]);
curr_loc += sizeof(ui32);
// Retrieve free_virt_blk pointer.
ndm->free_virt_blk = RD32_LE(&ndm->main_buf[curr_loc]);
curr_loc += sizeof(ui32);
// Retrieve free_ctrl_blk pointer.
ndm->free_ctrl_blk = RD32_LE(&ndm->main_buf[curr_loc]);
curr_loc += sizeof(ui32);
#if NDM_DEBUG
printf("read_ctrl info:\n");
printf(" -> ctrl_seq = %u\n", ndm->ctrl_seq);
printf(" -> ctrl_blk0 = %u\n", ndm->ctrl_blk0);
printf(" -> ctrl_blk1 = %u\n", ndm->ctrl_blk1);
printf(" -> free_virt_blk = %u\n", ndm->free_virt_blk);
printf(" -> free_ctrl_blk = %u\n", ndm->free_ctrl_blk);
#endif
// Retrieve the transfer to block (if any).
ndm->xfr_tblk = RD32_LE(&ndm->main_buf[curr_loc]);
curr_loc += sizeof(ui32);
// If a bad block was being transferred, retrieve all other relevant
// information about it so that the transfer can be redone.
if (ndm->xfr_tblk != (ui32)-1) {
// Retrieve the physical bad block being transferred.
ndm->xfr_fblk = RD32_LE(&ndm->main_buf[curr_loc]);
curr_loc += sizeof(ui32);
// Retrieve the page offset of bad page in bad block.
ndm->xfr_bad_po = RD32_LE(&ndm->main_buf[curr_loc]);
curr_loc += sizeof(ui32);
// Skip obsolete full/partial transfer flag.
++curr_loc;
#if NDM_DEBUG
printf(" -> xfr_tblk = %u\n", ndm->xfr_tblk);
printf(" -> xfr_fblk = %u\n", ndm->xfr_fblk);
printf(" -> xfr_bad_po = %u\n", ndm->xfr_bad_po);
#endif
}
#if NDM_DEBUG
else
puts(" -> xfr_tblk = -1");
#endif
// Retrieve the number of partitions.
ndm->num_partitions = RD32_LE(&ndm->main_buf[curr_loc]);
curr_loc += sizeof(ui32);
#if NDM_DEBUG
printf(" -> num_partitions = %u\n", ndm->num_partitions);
printf("read_ctrl: init_bad_blk[]\n");
#endif
// Retrieve the initial bad blocks map.
for (ndm->num_bad_blks = i = 0;; ++i) {
// If too many initial bad blocks, error.
if (ndm->num_bad_blks > ndm->max_bad_blks)
return FsError2(NDM_TOO_MANY_IBAD, EINVAL);
// If next read spans control pages, adjust. Return -1 if error.
if (check_next_read(ndm, &curr_loc, &p, &ctrl_pages, sizeof(ui32)))
return -1;
// Retrieve the next initial bad block.
bn = RD32_LE(&ndm->main_buf[curr_loc]);
curr_loc += sizeof(ui32);
#if NDM_DEBUG
printf(" [%u] = %u\n", i, bn);
#endif
// Store block in initial bad block map and check for end of map.
ndm->init_bad_blk[i] = bn;
if (bn == ndm->num_dev_blks)
break;
// Adjust running count of bad blocks to account for this one.
++ndm->num_bad_blks;
}
#if NDM_DEBUG
puts("read_ctrl: run_bad_blk[]");
#endif
// Retrieve running bad blocks map.
for (ndm->num_rbb = 0;; ++ndm->num_rbb) {
// If too many bad blocks, error.
if (ndm->num_bad_blks > ndm->max_bad_blks)
return FsError2(NDM_TOO_MANY_RBAD, EINVAL);
// If next read spans control pages, adjust. Return -1 if error.
if (check_next_read(ndm, &curr_loc, &p, &ctrl_pages, 2 * sizeof(ui32)))
return -1;
// Retrieve the next running bad block pair.
vbn = RD32_LE(&ndm->main_buf[curr_loc]);
curr_loc += sizeof(ui32);
bn = RD32_LE(&ndm->main_buf[curr_loc]);
curr_loc += sizeof(ui32);
// If end of running blocks reached, stop.
if (vbn == (ui32)-1 && bn == (ui32)-1)
break;
// Store bad block pair in running block map.
ndm->run_bad_blk[ndm->num_rbb].key = vbn;
ndm->run_bad_blk[ndm->num_rbb].val = bn;
#if NDM_DEBUG
printf(" [%u] key = %u, val = %u\n", ndm->num_rbb, vbn, bn);
#endif
// Adjust running count of bad blocks to account for this one.
++ndm->num_bad_blks;
}
// Retrieve the NDM partitions if any.
if (ndm->num_partitions) {
// If not already done, allocate memory for partitions table.
if (ndm->partitions == NULL) {
ndm->partitions = FsCalloc(ndm->num_partitions, sizeof(NDMPartition));
if (ndm->partitions == NULL)
return -1;
}
#if NDM_DEBUG
puts("read_ctrl: partitions[]");
#endif
// Read partitions from the control information one at a time.
for (i = 0; i < ndm->num_partitions; ++i) {
#if NDM_PART_USER
ui32 j;
#endif
// If next read spans control pages, adjust. Return -1 if error.
if (check_next_read(ndm, &curr_loc, &p, &ctrl_pages, sizeof(NDMPartition)))
return -1;
// Retrieve the next partition. Read partition first block.
ndm->partitions[i].first_block = RD32_LE(&ndm->main_buf[curr_loc]);
curr_loc += sizeof(ui32);
// Read partition number of blocks.
ndm->partitions[i].num_blocks = RD32_LE(&ndm->main_buf[curr_loc]);
curr_loc += sizeof(ui32);
#if NDM_PART_USER
// Read the user defined ui32s.
for (j = 0; j < NDM_PART_USER; ++j) {
ndm->partitions[i].user[j] = RD32_LE(&ndm->main_buf[curr_loc]);
curr_loc += sizeof(ui32);
}
#endif
// Read the partition name.
strncpy(ndm->partitions[i].name, (char*)&ndm->main_buf[curr_loc],
NDM_PART_NAME_LEN - 1);
curr_loc += NDM_PART_NAME_LEN;
// Read the partition type.
ndm->partitions[i].type = ndm->main_buf[curr_loc++];
#if NDM_DEBUG
printf(" partition[%2u]:\n", i);
printf(" - name = %s\n", ndm->partitions[i].name);
printf(" - first block = %u\n", ndm->partitions[i].first_block);
printf(" - num blocks = %u\n", ndm->partitions[i].num_blocks);
#if NDM_PART_USER
for (j = 0; j < NDM_PART_USER; ++j)
printf(" - user[%u] = %u\n", j, ndm->partitions[i].user[j]);
#endif
#endif // NDM_DEBUG
}
}
// Check that number of read pages agrees with recorded one.
if (ctrl_pages != ndm->ctrl_pages || p != ndm->last_ctrl_page)
return FsError2(NDM_BAD_META_DATA, EINVAL);
// Return success.
return 0;
}
// recover_bad_blk: Recover from an unexpected power down while a bad
// block was being transferred
//
// Input: ndm = pointer to NDM control block
//
// Returns: 0 on success, -1 on failure
//
static int recover_bad_blk(NDM ndm) {
ui32 i, bpn;
int rc;
// Ensure the 'transfer from' block value is valid.
if (ndm->xfr_fblk >= ndm->num_dev_blks)
return FsError2(NDM_BAD_META_DATA, EINVAL);
// Ensure the 'transfer to' block value is valid.
if (ndm->xfr_tblk < ndm->frst_reserved || ndm->xfr_tblk >= ndm->free_virt_blk)
return FsError2(NDM_BAD_META_DATA, EINVAL);
// Return error if doing a read-only initialization.
if (ndm->flags & FSF_READ_ONLY_INIT)
return FsError2(NDM_BAD_BLK_RECOV, EINVAL);
// Erase the 'transfer to' block. Return if fatal error.
rc = ndm->erase_block(ndm->xfr_tblk * ndm->pgs_per_blk, ndm->dev);
if (rc == -2)
return FsError2(NDM_EIO, EIO);
// Check if block erase command failed.
if (rc < 0) {
// Adjust bad block count. If too many, error.
if (++ndm->num_bad_blks > ndm->max_bad_blks)
return FsError2(NDM_TOO_MANY_RBAD, ENOSPC);
// Find running list entry with this 'transfer from/to' block pair.
for (i = 0;; ++i) {
if (i == ndm->num_rbb)
return FsError2(NDM_ASSERT, EINVAL);
if (ndm->run_bad_blk[i].key == ndm->xfr_fblk &&
ndm->run_bad_blk[i].val == ndm->xfr_tblk)
break;
}
// Invalidate the 'transfer to' block since it's bad now.
ndm->run_bad_blk[i].val = (ui32)-1;
// Add new bad block list entry with this 'transfer to' block.
ndm->run_bad_blk[ndm->num_rbb].key = ndm->xfr_tblk;
ndm->run_bad_blk[ndm->num_rbb].val = (ui32)-1;
++ndm->num_rbb;
}
// Else erase was successful. Adjust free block pointer.
else {
PfAssert(ndm->free_virt_blk == (ui32)-1 || ndm->xfr_tblk + 1 == ndm->free_virt_blk);
ndm->free_virt_blk = ndm->xfr_tblk;
}
// Reset 'transfer to' block pointer.
ndm->xfr_tblk = (ui32)-1;
// Figure out bad page number on bad block.
bpn = ndm->xfr_fblk * ndm->pgs_per_blk + ndm->xfr_bad_po;
// Mark block bad and do bad block recovery for write failure.
return ndmMarkBadBlock(ndm, bpn, WRITE_PAGE);
}
// init_ndm: Initialize an NDM by reading the flash
//
// Input: ndm = pointer to NDM control block
//
// Returns: 0 on success, -1 on failure
//
static int init_ndm(NDM ndm) {
int wr_metadata;
ui32 ctrl_blk;
// See if device is formatted with NDM metadata. Check for error.
if (format_status(ndm) != 0) {
// If no metadata was found and initialization is not being done
// in read-only mode, format the device. Else return -1.
if ((GetFsErrCode() == NDM_NO_META_BLK) && FLAG_IS_CLR(ndm->flags, FSF_READ_ONLY_INIT))
return ndm_format(ndm);
else
return -1;
}
// Else device is NDM formatted. Find latest control information.
if (find_last_ctrl_info(ndm))
return -1;
// Read the control information. Return -1 if error.
PfAssert(ndm->ctrl_blk1 == (ui32)-1);
if (read_ctrl_info(ndm))
return -1;
// Set flag if configured to write metadata at every startup to
// ensure control block reads don't create read-disturb errors.
wr_metadata = FLAG_IS_SET(ndm->flags, FSF_NDM_INIT_WRITE);
// Match the block the control information was found on with either
// ctrl_blk0 or ctrl_blk1, pick other block for next control write.
ctrl_blk = ndm->last_ctrl_page / ndm->pgs_per_blk;
if (ctrl_blk == ndm->ctrl_blk0)
ctrl_blk = ndm->ctrl_blk1;
else if (ctrl_blk == ndm->ctrl_blk1)
ctrl_blk = ndm->ctrl_blk0;
// Else this must be the first start from a preprogrammed image.
else {
// Fail start-up if image's running bad block count is non-zero.
if (ndm->num_rbb)
return FsError2(NDM_IMAGE_RBB_CNT, ENXIO);
// Redo the initial bad block list for our device.
if (init_ibad_list(ndm))
return -1;
// Request first metadata write and have it on ctrl_blk0.
ctrl_blk = ndm->ctrl_blk0;
wr_metadata = TRUE;
}
// Assign starting control write page number.
ndm->next_ctrl_start = ctrl_blk * ndm->pgs_per_blk;
// Compute the cutoff between virtual blocks and reserved blocks.
set_frst_ndm_block(ndm);
// Ensure even lowest running bad block lies in reserved area.
if (ndm->run_bad_blk[0].val < ndm->frst_reserved)
return FsError2(NDM_RBAD_LOCATION, EINVAL);
// If in the middle of transferring a bad block, continue transfer.
if (ndm->xfr_tblk != (ui32)-1)
return recover_bad_blk(ndm);
// Check if NDM metadata write is requested or needed.
if (wr_metadata) {
// Return error if doing a read-only initialization.
if (ndm->flags & FSF_READ_ONLY_INIT)
return FsError2(NDM_META_WR_REQ, EINVAL);
// Write initial control information and return status.
ndm->xfr_tblk = (ui32)-1;
return ndmWrCtrl(ndm);
}
// Return success.
return 0;
}
// ndm_xfr_page: Substitute if driver does not supply transfer_page()
//
// Inputs: old_pn = old page number
// new_pn = new page number
// buf = pointer to temperary buffer for page data
// old_spare = buffer to hold old page spare contents
// new_spare = buffer to hold new page spare contents
// encode_spare = flag to encode/not encode spare bytes
// 1 through 14 (FTL encodes/FFS does not)
// ndm_ptr = pointer to TargetNDM control block
//
// Returns: 0 on success, -2 on fatal error, -1 on chip error,
// 1 on ECC decode error
//
static int ndm_xfr_page(ui32 old_pn, ui32 new_pn, ui8* buf, ui8* old_spare, ui8* new_spare,
int encode_spare, void* ndm_ptr) {
int status;
NDM ndm = ndm_ptr;
// Read page data. Return is 1, 0, -2, or -1.
status = ndm->read_page(old_pn, buf, old_spare, ndm->dev);
// Error check: return 1 for ECC decode error, else -2 fatal error.
if (status < 0) {
if (status == -1) {
FsError2(NDM_RD_ECC_FAIL, EIO);
return 1;
}
FsError2(NDM_EIO, EIO);
return -2;
}
// Write data page to flash and return status.
return ndm->write_page(new_pn, buf, new_spare, encode_spare, ndm->dev);
}
// Global Function Definitions
// NdmInit: Initialize NDM
//
// Returns: 0 on success, -1 on failure
//
int NdmInit(void) {
// Initialize the devices list.
CIRC_LIST_INIT(&NdmDevs);
// Create the NDM global synchronization semaphore.
NdmSem = semCreate("NDM_SEM", 1, OS_FIFO);
if (NdmSem == NULL) {
FsError2(NDM_SEM_CRE_ERR, errno);
return -1;
}
// Return success.
return 0;
}
// ndmAddDev: Create a new NDM
//
// Input: dvr = pointer to NDM driver control block
//
// Returns: New NDM control block on success, NULL on error
//
NDM ndmAddDev(const NDMDrvr* dvr) {
char sem_name[9];
uint eb_alloc_sz;
NDM ndm;
#if NV_NDM_CTRL_STORE
// Can only use one NDM device with NVRAM control page storage.
if (NdmDevCnt > 0) {
FsError2(NDM_CFG_ERR, EINVAL);
return NULL;
}
#endif
// Error if unsupported flash type.
#if INC_FTL_NDM_SLC
if (dvr->type != NDM_SLC) {
#else
if (dvr->type != NDM_MLC) {
#endif
FsError2(NDM_CFG_ERR, EINVAL);
return NULL;
}
// Ensure NDM driver flags are valid.
if (dvr->flags &
~(FSF_MULTI_ACCESS | FSF_TRANSFER_PAGE | FSF_FREE_SPARE_ECC | FSF_NDM_INIT_WRITE |
FSF_READ_ONLY_INIT)) {
FsError2(NDM_CFG_ERR, EINVAL);
return NULL;
}
// Check for valid number of blocks.
if (dvr->num_blocks <= dvr->max_bad_blocks + NDM_META_BLKS) {
FsError2(NDM_CFG_ERR, EINVAL);
return NULL;
}
// Check for valid page size (multiple of 512).
if (dvr->page_size == 0 || dvr->page_size % 512) {
FsError2(NDM_CFG_ERR, EINVAL);
return NULL;
}
// Check for valid spare bytes size.
if (dvr->eb_size > dvr->page_size || dvr->eb_size < 16) {
FsError2(NDM_CFG_ERR, EINVAL);
return NULL;
}
// Allocate space for TargetNDM control block.
ndm = FsCalloc(1, sizeof(struct ndm));
if (ndm == NULL) {
FsError2(NDM_ENOMEM, ENOMEM);
return NULL;
}
// Set the number of virtual blocks.
ndm->num_vblks = dvr->num_blocks - dvr->max_bad_blocks - NDM_META_BLKS;
// Set the other driver dependent variables.
ndm->num_dev_blks = dvr->num_blocks;
ndm->max_bad_blks = dvr->max_bad_blocks;
ndm->block_size = dvr->block_size;
ndm->page_size = dvr->page_size;
ndm->eb_size = dvr->eb_size;
ndm->pgs_per_blk = ndm->block_size / ndm->page_size;
ndm->flags = dvr->flags;
// Allocate memory for initial and running bad blocks arrays.
ndm->init_bad_blk = FsMalloc((ndm->max_bad_blks + 1) * sizeof(ui32));
if (ndm->init_bad_blk == NULL) {
FsError2(NDM_ENOMEM, ENOMEM);
goto ndmAddDe_err;
}
ndm->run_bad_blk = FsMalloc((ndm->max_bad_blks + 1) * sizeof(Pair));
if (ndm->run_bad_blk == NULL) {
FsError2(NDM_ENOMEM, ENOMEM);
goto ndmAddDe_err;
}
// Initialize the initial and running bad block arrays.
memset(ndm->init_bad_blk, 0xFF, (ndm->max_bad_blks + 1) * sizeof(ui32));
memset(ndm->run_bad_blk, 0xFF, (ndm->max_bad_blks + 1) * sizeof(Pair));
// Create the access semaphore.
sprintf(sem_name, "NDM_S%03d", NdmSemCount++);
ndm->sem = semCreate(sem_name, 1, OS_FIFO);
if (ndm->sem == NULL) {
FsError2(NDM_SEM_CRE_ERR, errno);
goto ndmAddDe_err;
}
// Ensure spare area buffers allocated by NDM are cache aligned.
eb_alloc_sz = ndm->eb_size;
#if CACHE_LINE_SIZE
eb_alloc_sz = ((eb_alloc_sz + CACHE_LINE_SIZE - 1) / CACHE_LINE_SIZE) * CACHE_LINE_SIZE;
#endif
// Allocate memory for page main and spare data buffers.
ndm->main_buf = FsAalloc(ndm->page_size + 2 * eb_alloc_sz);
if (ndm->main_buf == NULL) {
FsError2(NDM_ENOMEM, ENOMEM);
goto ndmAddDe_err;
}
ndm->spare_buf = ndm->main_buf + ndm->page_size;
ndm->tmp_spare = ndm->spare_buf + eb_alloc_sz;
// Initialize all other NDM variables.
ndm->ctrl_blk0 = ndm->ctrl_blk1 = (ui32)-1;
ndm->frst_ctrl_page = ndm->last_ctrl_page = (ui32)-1;
ndm->free_virt_blk = ndm->free_ctrl_blk = (ui32)-1;
ndm->ctrl_seq = (ui32)-1;
ndm->xfr_tblk = ndm->xfr_fblk = ndm->xfr_bad_po = (ui32)-1;
ndm->last_wr_vbn = ndm->last_wr_pbn = (ui32)-1;
ndm->last_rd_vbn = ndm->last_rd_pbn = (ui32)-1;
// Install driver callback routine function pointers.
ndm->write_page = dvr->write_data_and_spare;
ndm->read_page = dvr->read_decode_data;
ndm->read_decode_spare = dvr->read_decode_spare;
ndm->read_spare = dvr->read_spare;
ndm->page_blank = dvr->data_and_spare_erased;
ndm->check_page = dvr->data_and_spare_check;
ndm->erase_block = dvr->erase_block;
ndm->is_block_bad = dvr->is_block_bad;
ndm->dev = dvr->dev;
#if INC_FTL_NDM_MLC
// The 'pair_offset' driver function does not take a page/address.
ndm->pair_offset = dvr->pair_offset;
#endif
// If driver supplies transfer page function, use it.
if (FLAG_IS_SET(dvr->flags, FSF_TRANSFER_PAGE)) {
ndm->dev_ndm = ndm->dev;
ndm->xfr_page = dvr->transfer_page;
// Else use internal read-page/write-page substitute.
} else {
ndm->dev_ndm = ndm;
ndm->xfr_page = ndm_xfr_page;
}
// If driver read/write pages supplied, use them directly.
if (FLAG_IS_SET(dvr->flags, FSF_MULTI_ACCESS)) {
ndm->read_pages = dvr->read_pages;
ndm->write_pages = dvr->write_pages;
}
// Initialize the NDM.
if (init_ndm(ndm))
goto ndmAddDe_err;
#if NV_NDM_CTRL_STORE
// Account for NVRAM routine use.
++NdmDevCnt;
#endif
// Add NDM to global NDM list while holding access semaphore.
semPend(NdmSem, WAIT_FOREVER);
CIRC_LIST_APPEND(&ndm->link, &NdmDevs);
semPostBin(NdmSem);
// Success! Returns handle to new NDM control block.
return ndm;
// Error exit.
ndmAddDe_err:
if (ndm->init_bad_blk)
FsFree(ndm->init_bad_blk);
if (ndm->run_bad_blk)
FsFree(ndm->run_bad_blk);
if (ndm->sem)
semDelete(&ndm->sem);
if (ndm->main_buf)
FsAfreeClear(&ndm->main_buf);
if (ndm->partitions)
FsFree(ndm->partitions);
FsFree(ndm);
return NULL;
}
// ndmDelDev: Delete (uninitialize) the NDM
//
// Input: ndm = pointer to NDM control block
//
// Returns: 0 on success, -1 on failure
//
int ndmDelDev(NDM ndm) {
CircLink* circ;
int saved_errno;
// Acquire exclusive access to global NDM semaphore.
semPend(NdmSem, WAIT_FOREVER);
// Ensure the device is on the device list.
for (circ = CIRC_LIST_HEAD(&NdmDevs);; circ = circ->next_bck) {
// If the device was not found, return error.
if (CIRC_LIST_AT_END(circ, &NdmDevs)) {
semPostBin(NdmSem);
return FsError2(NDM_NOT_FOUND, ENOENT);
}
// If device found, stop looking.
if (ndm == (void*)circ)
break;
}
// Remove device from list of devices.
CIRC_NODE_REMOVE(&ndm->link);
CIRC_NODE_INIT(&ndm->link);
// Release exclusive access to global NDM semaphore.
semPostBin(NdmSem);
// Remove all volumes from device.
saved_errno = errno;
(void)ndmDelVols(ndm);
errno = saved_errno;
// Free the initial and running bad block maps.
FsFree(ndm->init_bad_blk);
FsFree(ndm->run_bad_blk);
// Free the partitions table.
if (ndm->partitions)
FsFree(ndm->partitions);
// Free the region semaphore, temporary space, and control block.
semDelete(&ndm->sem);
FsAfreeClear(&ndm->main_buf);
FsFree(ndm);
#if NV_NDM_CTRL_STORE
// Decrement NDM device count.
--NdmDevCnt;
#endif
// Return success.
return 0;
}
// ndmInitBadBlock: Check if a block is in initial bad block map
//
// Inputs: ndm = pointer to NDM control block
// b = block to check
//
// Returns: TRUE iff initial bad block, FALSE otherwise
//
int ndmInitBadBlock(CNDM ndm, ui32 b) {
ui32 i;
// Loop over list of initially bad blocks.
for (i = 0; i <= ndm->max_bad_blks; ++i) {
// If end of map, stop.
if (ndm->init_bad_blk[i] == ndm->num_dev_blks)
break;
// If block found in map, it's initial bad block.
if (ndm->init_bad_blk[i] == b)
return TRUE;
}
// Return FALSE. Block is not an initial bad block.
return FALSE;
}
#if RDBACK_CHECK
// ndmCkMeta: Read-back verify the TargetNDM metadata
//
// Input: ndm0 = pointer to NDM control block
//
void ndmCkMeta(NDM ndm0) {
int rc;
NDM ndm1;
// Allocate TargetNDM control block for metadata comparison.
ndm1 = FsCalloc(1, sizeof(struct ndm));
PfAssert(ndm1 != NULL);
// Copy initialized (not read) structure values.
memcpy(ndm1, ndm0, sizeof(struct ndm));
// Read the latest control info into temporary control block, using
// - if any - previously allocated partition memory.
rc = read_ctrl_info(ndm1);
PfAssert(rc == 0);
// Compare control block used for writing with one used for reading.
rc = memcmp(ndm1, ndm0, sizeof(struct ndm));
PfAssert(rc == 0);
// Free allocated TargetNDM test control block.
free(ndm1);
}
#endif // RDBACK_CHECK