| // 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 |
| |