| // 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" |
| |
| #if INC_NDM |
| #include <kprivate/fsprivate.h> |
| |
| // Configuration |
| #define BBL_INSERT_INC TRUE |
| #define BBL_INSERT_DEBUG FALSE |
| |
| // Symbol Definitions |
| |
| // |
| // Reason for get_pbn() virtual to physical block mapping |
| // |
| #define WR_MAPPING 0 |
| #define RD_MAPPING 1 |
| |
| // Global Data Definitions |
| #if BBL_INSERT_INC |
| static ui32 ExtractedCnt; |
| static Pair* ExtractedList; |
| #endif |
| |
| // Local Function Definitions |
| |
| #if BBL_INSERT_DEBUG |
| // show_rbbl: Show NDM's running bad block list |
| // |
| // Inputs: list = pointer to NDM control block running BB list |
| // cnt = number of bad blocks in list |
| // |
| static void show_rbbl(Pair* list, ui32 cnt) { |
| Pair *past_end, *pair = list; |
| |
| putchar('\n'); |
| for (past_end = pair + cnt; pair < past_end; ++pair) |
| printf("pair %u: vblk/key=%u, pblk/val=%d\n", pair - list, pair->key, pair->val); |
| } |
| #endif |
| |
| // get_ctrl_size: Count number of pages needed to write current |
| // control information |
| // |
| // Input: ndm = pointer to NDM control block |
| // |
| // Returns: control info size in pages on success, 0 on failure |
| // |
| static int get_ctrl_size(CNDM ndm) { |
| ui32 i, curr_loc = CTRL_DATA_START, num_pages = 0; |
| |
| // Figure out how many control pages are needed. Each control page |
| // has a header (HDR_SIZE bytes). Control information has the |
| // following preamble: |
| // - device number of blocks + block size (8 bytes = 2 ui32s) |
| // - control block pointers (2) (8 bytes = 2 ui32s) |
| // - free block/ctrl block pointers (8 bytes = 2 ui32s) |
| // - number of partitions (4 bytes = 1 ui32) |
| // - on normal write |
| // - invalid transfer to block (4 bytes = 1 ui32) |
| // - on bad block transfer |
| // - transfer to block (4 bytes = 1 ui32) |
| // - transferred block (4 bytes = 1 ui32) |
| // - bad page in transferred block (4 bytes = 1 ui32) |
| // - partial/full scan flag (1 byte) |
| curr_loc += 8 * sizeof(ui32); |
| if (ndm->xfr_tblk != (ui32)-1) |
| curr_loc += 2 * sizeof(ui32) + 1; |
| |
| // After the preamble, the initial bad block map is written. Figure |
| // out how much space it takes. |
| for (i = 0;; ++i) { |
| // Ensure the initial bad block map is valid. |
| if (i > ndm->max_bad_blks) { |
| FsError(EINVAL); |
| return 0; |
| } |
| |
| // Add current entry. |
| if (curr_loc + sizeof(ui32) > ndm->page_size) { |
| ++num_pages; |
| curr_loc = CTRL_DATA_START; |
| } |
| curr_loc += sizeof(ui32); |
| |
| // If we've reached end of initial bad block map, stop. |
| if (ndm->init_bad_blk[i] == ndm->num_dev_blks) |
| break; |
| } |
| |
| // The running bad block map is written next. Determine its size. |
| for (i = 0;; ++i) { |
| // Ensure running bad block map is valid. |
| if (i > ndm->max_bad_blks) { |
| FsError(EINVAL); |
| return 0; |
| } |
| |
| // Add current entry. |
| if (curr_loc + 2 * sizeof(ui32) > ndm->page_size) { |
| ++num_pages; |
| curr_loc = CTRL_DATA_START; |
| } |
| curr_loc += 2 * sizeof(ui32); |
| |
| // If we've reached the end of running bad block map, stop. |
| if (i == ndm->num_rbb) { |
| if (curr_loc + 2 * sizeof(ui32) > ndm->page_size) { |
| ++num_pages; |
| curr_loc = CTRL_DATA_START; |
| } |
| curr_loc += 2 * sizeof(ui32); |
| break; |
| } |
| } |
| |
| // The partitions are written. Figure out how much space they take. |
| for (i = 0; i < ndm->num_partitions; ++i) { |
| if (curr_loc + sizeof(NDMPartition) > ndm->page_size) { |
| ++num_pages; |
| curr_loc = CTRL_DATA_START; |
| } |
| curr_loc += sizeof(NDMPartition); |
| } |
| |
| // If last control page will be partially written, account for it. |
| if (curr_loc > CTRL_DATA_START) |
| ++num_pages; |
| |
| // Output number of control pages and return success. |
| return num_pages; |
| } |
| |
| // wr_ctrl_page: Write a page of control information to flash |
| // |
| // Inputs: ndm = pointer to NDM control block |
| // cpc = current control page count |
| // In/Output: *curr_pnp = page number where next page is written |
| // Output: *badblkp = bad block number (if page write fails) |
| // |
| // Returns: 0 if successful, -1 if block failed while writing, -2 |
| // if fatal error |
| // |
| static int wr_ctrl_page(NDM ndm, ui32 cpc, ui32* curr_pnp, ui32* badblkp) { |
| ui32 cpn = *curr_pnp, crc = CRC32_START, i; |
| int rc; |
| |
| // Fill current page count in header. |
| WR16_LE(cpc, &ndm->main_buf[HDR_CURR_LOC]); |
| |
| // Perform CRC over page contents and write it on the page. |
| for (i = 0; i < ndm->page_size; ++i) { |
| if (i == HDR_CRC_LOC) |
| i = CTRL_DATA_START; |
| crc = CRC32_UPDATE(crc, ndm->main_buf[i]); |
| } |
| crc = ~crc; |
| WR32_LE(crc, &ndm->main_buf[HDR_CRC_LOC]); //lint !e850 |
| |
| // Write page to flash. Check for error indication. |
| rc = ndm->write_page(cpn, ndm->main_buf, ndm->spare_buf, NDM_ECC_VAL, ndm->dev); |
| if (rc) { |
| // If fatal error, return fatal error indication. |
| if (rc == -2) { |
| FsError(EIO); |
| return rc; |
| } |
| |
| // Else bad block caused write failure, output block number and |
| // return bad block indication. |
| else { |
| PfAssert(rc == -1); |
| #if NDM_DEBUG |
| printf("wr_ctrl_page: bad block for #%u at page #%u\n", cpc, cpn); |
| #endif |
| *badblkp = cpn / ndm->pgs_per_blk; |
| return -1; |
| } |
| } |
| |
| // Update first and/or last control page. |
| if (cpc == 1) { |
| ndm->frst_ctrl_page = cpn; |
| #if NV_NDM_CTRL_STORE |
| NvNdmCtrlPgWr(cpn); |
| #endif |
| } |
| if (cpc == ndm->ctrl_pages) |
| ndm->last_ctrl_page = cpn; |
| #if NDM_DEBUG |
| printf("wr_ctrl_page: wrote %u at page %u (block %u)\n", cpc, cpn, cpn / ndm->pgs_per_blk); |
| #endif |
| |
| // Advance to next page to write control information into. |
| // Just increment page number if not on last page of block. |
| if ((cpn + 1) % ndm->pgs_per_blk) |
| ++cpn; |
| |
| // Else prepare to switch to new control page. |
| else { |
| // Switch to first page on opposing control block. |
| if (cpn / ndm->pgs_per_blk == ndm->ctrl_blk0) |
| cpn = ndm->ctrl_blk1 * ndm->pgs_per_blk; |
| else |
| cpn = ndm->ctrl_blk0 * ndm->pgs_per_blk; |
| |
| // Erase new block now, before its first write. Check for error. |
| rc = ndm->erase_block(cpn, ndm->dev); |
| if (rc) { |
| // If fatal error, return fatal error indication. |
| if (rc == -2) { |
| FsError(EIO); |
| return rc; |
| } |
| |
| // Else bad block caused erase failure, output block number and |
| // return bad block indication. |
| else { |
| PfAssert(rc == -1); |
| #if NDM_DEBUG |
| printf("wr_ctrl_page: bad block for #%u at page #%u\n", cpc, cpn); |
| #endif |
| *badblkp = cpn / ndm->pgs_per_blk; |
| return -1; |
| } |
| } |
| } |
| |
| // Output page number for next control write and return success. |
| *curr_pnp = cpn; |
| return 0; |
| } |
| |
| // wr_ctrl_info: Write NDM control information |
| // |
| // Inputs: ndm = pointer to NDM control block |
| // frst_page = first page to write to |
| // Output: *badblkp = number of bad control block (if any) |
| // |
| // Returns: 0 if successful, -1 if block failed, -2 if fatal error |
| // |
| static int wr_ctrl_info(NDM ndm, ui32 frst_page, ui32* badblkp) { |
| ui32 i, curr_loc, write_count = 1, cpn = frst_page; |
| int status; |
| |
| // Determine control information size as number of pages. |
| ndm->ctrl_pages = get_ctrl_size(ndm); |
| if (ndm->ctrl_pages == 0) |
| return -2; |
| #if NDM_DEBUG |
| printf("wr_ctrl_inf: preparing to write %u NDM ctrl pages\n", ndm->ctrl_pages); |
| #endif |
| |
| // Initialize the spare area: 0xFF except for the signature bytes |
| // and NDM control page mark. |
| memset(ndm->spare_buf, 0xFF, ndm->eb_size); |
| memcpy(&ndm->spare_buf[EB_FRST_RESERVED], CTRL_SIG, CTRL_SIG_SZ); |
| ndm->spare_buf[EB_REG_MARK] = 0; |
| |
| // Initialize main page with 0xFF. |
| memset(ndm->main_buf, 0xFF, ndm->page_size); |
| |
| // Set the constant part of the control page header: last location |
| // and sequence number. The current location varies with page number. |
| WR16_LE(ndm->ctrl_pages, &ndm->main_buf[HDR_LAST_LOC]); |
| ++ndm->ctrl_seq; |
| WR32_LE(ndm->ctrl_seq, &ndm->main_buf[HDR_SEQ_LOC]); |
| |
| // Set the first control page data, starting with the device size. |
| curr_loc = CTRL_DATA_START; |
| WR32_LE(ndm->num_dev_blks, &ndm->main_buf[curr_loc]); |
| curr_loc += sizeof(ui32); |
| WR32_LE(ndm->block_size, &ndm->main_buf[curr_loc]); |
| curr_loc += sizeof(ui32); |
| |
| // Write the control block numbers. |
| WR32_LE(ndm->ctrl_blk0, &ndm->main_buf[curr_loc]); |
| curr_loc += sizeof(ui32); |
| WR32_LE(ndm->ctrl_blk1, &ndm->main_buf[curr_loc]); |
| curr_loc += sizeof(ui32); |
| |
| // Add free_virt_blk pointer. |
| WR32_LE(ndm->free_virt_blk, &ndm->main_buf[curr_loc]); |
| curr_loc += sizeof(ui32); |
| |
| // Add free_ctrl_blk number. |
| WR32_LE(ndm->free_ctrl_blk, &ndm->main_buf[curr_loc]); |
| curr_loc += sizeof(ui32); |
| |
| #if NDM_DEBUG |
| puts("wr_ctrl_inf:"); |
| 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 |
| |
| // Add 'transfer to' physical block number value/flag. |
| WR32_LE(ndm->xfr_tblk, &ndm->main_buf[curr_loc]); |
| curr_loc += sizeof(ui32); |
| |
| // If bad block transfer, add remaining bad block transfer info. |
| if (ndm->xfr_tblk != (ui32)-1) { |
| // Add physical block number of the bad block being transferred. |
| WR32_LE(ndm->xfr_fblk, &ndm->main_buf[curr_loc]); |
| curr_loc += sizeof(ui32); |
| |
| // Add page offset of bad page in bad block. |
| WR32_LE(ndm->xfr_bad_po, &ndm->main_buf[curr_loc]); |
| curr_loc += sizeof(ui32); |
| |
| // Add (legacy) partial scan flag. |
| ndm->main_buf[curr_loc++] = PARTIAL_SCAN; |
| |
| #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 |
| |
| // Write the number of partitions. |
| WR32_LE(ndm->num_partitions, &ndm->main_buf[curr_loc]); |
| curr_loc += sizeof(ui32); |
| #if NDM_DEBUG |
| printf(" -> num_partitions = %u\n", ndm->num_partitions); |
| #endif |
| |
| // Now write the initial bad block map. |
| for (i = 0;; ++i) { |
| // If next write would go past end of control page, write page. |
| if (curr_loc + sizeof(ui32) > ndm->page_size) { |
| status = wr_ctrl_page(ndm, write_count++, &cpn, badblkp); |
| if (status) |
| return status; |
| curr_loc = CTRL_DATA_START; |
| } |
| |
| // Add next bad block. |
| WR32_LE(ndm->init_bad_blk[i], &ndm->main_buf[curr_loc]); |
| curr_loc += sizeof(ui32); |
| |
| // If end of map is reached, stop. |
| if (ndm->init_bad_blk[i] == ndm->num_dev_blks) |
| break; |
| |
| #if NDM_DEBUG |
| printf(" -> init_bad_blk[%2u] = %u\n", i, ndm->init_bad_blk[i]); |
| #endif |
| } |
| |
| // Now write the running bad block map. |
| for (i = 0;; ++i) { |
| // If next write would go past end of control page, write page. |
| if (curr_loc + 2 * sizeof(ui32) > ndm->page_size) { |
| status = wr_ctrl_page(ndm, write_count++, &cpn, badblkp); |
| if (status) |
| return status; |
| curr_loc = CTRL_DATA_START; |
| } |
| |
| // If end of running bad blocks map reached, mark end. |
| if (i == ndm->num_rbb) { |
| // Mark end of map before writing whole map to flash. |
| WR32_LE((ui32)-1, &ndm->main_buf[curr_loc]); |
| curr_loc += sizeof(ui32); |
| WR32_LE((ui32)-1, &ndm->main_buf[curr_loc]); |
| curr_loc += sizeof(ui32); |
| break; |
| } |
| |
| // Add next running bad block pair. |
| WR32_LE(ndm->run_bad_blk[i].key, &ndm->main_buf[curr_loc]); |
| curr_loc += sizeof(ui32); |
| WR32_LE(ndm->run_bad_blk[i].val, &ndm->main_buf[curr_loc]); |
| curr_loc += sizeof(ui32); |
| |
| #if NDM_DEBUG |
| printf(" -> run_bad_blk[%2u]: key = %u, val = %u\n", i, ndm->run_bad_blk[i].key, |
| ndm->run_bad_blk[i].val); |
| #endif |
| } |
| |
| // Write the NDM partitions if any, one at a time. |
| for (i = 0; i < ndm->num_partitions; ++i) { |
| #if NDM_PART_USER |
| ui32 j; |
| #endif |
| |
| // If next write would go past end of control page, write page. |
| if (curr_loc + sizeof(NDMPartition) > ndm->page_size) { |
| status = wr_ctrl_page(ndm, write_count++, &cpn, badblkp); |
| if (status) |
| return status; |
| curr_loc = CTRL_DATA_START; |
| } |
| |
| // Write partition first block. |
| WR32_LE(ndm->partitions[i].first_block, &ndm->main_buf[curr_loc]); |
| curr_loc += sizeof(ui32); |
| |
| // Write partition number of blocks. |
| WR32_LE(ndm->partitions[i].num_blocks, &ndm->main_buf[curr_loc]); |
| curr_loc += sizeof(ui32); |
| |
| #if NDM_PART_USER |
| // Write the user defined ui32s. |
| for (j = 0; j < NDM_PART_USER; ++j) { |
| WR32_LE(ndm->partitions[i].user[j], &ndm->main_buf[curr_loc]); |
| curr_loc += sizeof(ui32); |
| } |
| #endif |
| |
| // Write the partition name. |
| strncpy((char*)&ndm->main_buf[curr_loc], ndm->partitions[i].name, NDM_PART_NAME_LEN); |
| curr_loc += NDM_PART_NAME_LEN; |
| |
| // Write the partition type. |
| ndm->main_buf[curr_loc++] = ndm->partitions[i].type; |
| |
| #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 |
| printf(" - type = "); |
| switch (ndm->partitions[i].type) { |
| case 0: |
| puts("NONE"); |
| break; |
| case FFS_VOL: |
| puts("FFS"); |
| break; |
| case FAT_VOL: |
| puts("FAT"); |
| break; |
| default: |
| printf("%u\n", ndm->partitions[i].type); |
| break; |
| } |
| #endif // NDM_DEBUG |
| } |
| |
| // Write last control page and return status. |
| return wr_ctrl_page(ndm, write_count, &cpn, badblkp); |
| } |
| |
| // mark_ctrl_bblock: Record a control block failure and get a new one |
| // |
| // Inputs: ndm = pointer to NDM control block |
| // *cblkp = control block that failed |
| // Output: *cblkp = new control block |
| // |
| // Returns: 0 on success, -1 on failure |
| // |
| static int mark_ctrl_bblock(NDM ndm, ui32* cblkp) { |
| ui32 bad_blk = *cblkp; |
| |
| // Clear virtual to physical translation caches. |
| ndm->last_wr_vbn = ndm->last_rd_vbn = (ui32)-1; |
| |
| // Adjust bad block count. If too many, error. |
| if (++ndm->num_bad_blks > ndm->max_bad_blks) |
| return FsError(ENOSPC); |
| |
| // Update the running bad block map with this block. |
| ndm->run_bad_blk[ndm->num_rbb].key = bad_blk; |
| ndm->run_bad_blk[ndm->num_rbb].val = (ui32)-1; |
| ++ndm->num_rbb; |
| |
| // Get a new free block for the other control block. |
| for (; ndm->free_ctrl_blk != (ui32)-1; --ndm->free_ctrl_blk) { |
| // If we've ran out of free blocks, error. |
| if (ndm->free_ctrl_blk < ndm->free_virt_blk) { |
| ndm->free_ctrl_blk = ndm->free_virt_blk = (ui32)-1; |
| break; |
| } |
| |
| // If initial bad block, skip it. |
| if (ndmInitBadBlock(ndm, ndm->free_ctrl_blk)) |
| continue; |
| |
| // We found a free block. Remember it. |
| *cblkp = ndm->free_ctrl_blk; |
| |
| // Update the right control block pointer with this free block. |
| if (bad_blk == ndm->ctrl_blk0) |
| ndm->ctrl_blk0 = ndm->free_ctrl_blk; |
| else if (bad_blk == ndm->ctrl_blk1) |
| ndm->ctrl_blk1 = ndm->free_ctrl_blk; |
| else |
| return FsError(EINVAL); |
| |
| // Update pointer to next free control block and return success. |
| --ndm->free_ctrl_blk; |
| return 0; |
| } |
| |
| // If no free block found for control block, error. |
| return FsError(ENOSPC); |
| } |
| |
| // get_free_virt_blk: Get next free block reserved for replacing bad |
| // virtual blocks (starts at lowest and goes up) |
| // |
| // Input: ndm = pointer to NDM control block |
| // |
| // Returns: Free block if successful, (ui32)-1 if none are free |
| // |
| static ui32 get_free_virt_blk(NDM ndm) { |
| ui32 free_b = ndm->free_virt_blk; |
| |
| // Check if there are any free blocks left. |
| if (free_b != (ui32)-1) { |
| ui32 b = free_b + 1; |
| |
| // Skip past initial bad blocks. |
| while (b <= ndm->free_ctrl_blk && ndmInitBadBlock(ndm, b)) ++b; |
| |
| // If we haven't gone into the blocks reserved for swapping bad |
| // control blocks, update the free block pointer. |
| if (b <= ndm->free_ctrl_blk) |
| ndm->free_virt_blk = b; |
| else |
| ndm->free_virt_blk = ndm->free_ctrl_blk = (ui32)-1; |
| } |
| |
| // Return free block number. |
| return free_b; |
| } |
| |
| // mark_extra_bblock: Mark a block bad while in middle of a transfer |
| // of another bad block (this is the transfer to block) |
| // |
| // Inputs: ndm = pointer to NDM control block |
| // In/Output: *bnp = bad block IN, substitute free block OUT |
| // |
| // Returns: 0 on success, -1 on failure |
| // |
| static int mark_extra_bblock(NDM ndm, ui32* bnp) { |
| ui32 free_b, i; |
| int found; |
| |
| // Clear virtual to physical translation caches. |
| ndm->last_wr_vbn = ndm->last_rd_vbn = (ui32)-1; |
| |
| // Adjust number of bad blocks. If too many, error. |
| if (++ndm->num_bad_blks > ndm->max_bad_blks) |
| return FsError(ENOSPC); |
| |
| // Get a free block to replace the bad block. |
| free_b = get_free_virt_blk(ndm); |
| if (free_b == (ui32)-1) |
| return FsError(ENOSPC); |
| |
| // Check that bad block is in the running bad block map only as a |
| // transfer to block (.val field) and only once. |
| for (i = 0, found = FALSE;; ++i) { |
| // If end of map, either found exactly once or error. |
| if (i == ndm->num_rbb) { |
| if (found) |
| break; |
| return FsError(EINVAL); |
| } |
| |
| // Bad block cannot be in the map as a transfer from block (.key). |
| if (ndm->run_bad_blk[i].key == *bnp) |
| return FsError(EINVAL); |
| |
| // If bad block in map, ensure first occurrence and remember find. |
| if (ndm->run_bad_blk[i].val == *bnp) { |
| if (found) |
| return FsError(EINVAL); |
| found = TRUE; |
| } |
| } |
| |
| // Add new entry to running bad block map with bad block. |
| ndm->run_bad_blk[ndm->num_rbb].key = *bnp; |
| ndm->run_bad_blk[ndm->num_rbb].val = free_b; |
| ++ndm->num_rbb; |
| |
| // Output the selected free block number and return success. |
| *bnp = free_b; |
| return 0; |
| } |
| |
| // run_bad_block: Check if a block is a running bad block |
| // |
| // Inputs: ndm = pointer to NDM control block |
| // b = block to check |
| // |
| // Returns: TRUE if block is bad, FALSE otherwise |
| // |
| static int run_bad_block(CNDM ndm, ui32 b) { |
| ui32 i; |
| |
| // Walk the run bad blocks map to see if block is in there. |
| for (i = 0; i < ndm->num_rbb; ++i) |
| if (ndm->run_bad_blk[i].key == b) |
| return TRUE; |
| |
| // Block number not found in list. Return FALSE. |
| return FALSE; |
| } |
| |
| // get_pbn: Get a physical block number from a virtual one |
| // |
| // Inputs: ndm = pointer to NDM control block |
| // vbn = virtual block number |
| // reason = reason for lookup: RD_MAPPING or WR_MAPPING |
| // |
| // Returns: physical block number on success, else (ui32)-1 |
| // |
| static ui32 get_pbn(NDM ndm, ui32 vbn, int reason) { |
| ui32 bn, i; |
| #if NDM_DEBUG |
| ui32 j; |
| |
| // Ensure the initial bad blocks map is valid (no duplicates). |
| for (i = 0;; ++i) { |
| if (i > ndm->max_bad_blks) |
| return (ui32)-1; |
| if (ndm->init_bad_blk[i] == ndm->num_dev_blks) |
| break; |
| for (j = i + 1; j <= ndm->max_bad_blks; ++j) { |
| if (ndm->init_bad_blk[j] == ndm->num_dev_blks) |
| break; |
| if (ndm->init_bad_blk[i] == ndm->init_bad_blk[j]) |
| return (ui32)-1; |
| } |
| } |
| |
| // Ensure running bad blocks map is valid (no key/value duplicates). |
| for (i = 0; i < ndm->num_rbb; ++i) { |
| if (ndm->run_bad_blk[i].key == (ui32)-1) |
| return (ui32)-1; |
| for (j = i + 1; j < ndm->num_rbb; ++j) { |
| if (ndm->run_bad_blk[i].key == ndm->run_bad_blk[j].key) |
| return (ui32)-1; |
| if (ndm->run_bad_blk[i].val == ndm->run_bad_blk[j].val && |
| ndm->run_bad_blk[i].val != (ui32)-1) |
| return (ui32)-1; |
| } |
| } |
| #endif // NDM_DEBUG |
| |
| // If no bad blocks, physical block number matches virtual. |
| if (ndm->num_bad_blks == 0) |
| return vbn; |
| |
| // If virtial block number matches last read or write lookup, return |
| // last read or write physical block number. |
| if (vbn == ndm->last_wr_vbn) |
| return ndm->last_wr_pbn; |
| if (vbn == ndm->last_rd_vbn) |
| return ndm->last_rd_pbn; |
| |
| // First determine where the block was before any running bad blocks |
| // happened. Do this by walking the initial bad blocks map. |
| for (i = 0, bn = vbn;; ++i) { |
| // Should never pass last entry, which holds 'num_dev_blks'. |
| if (i > ndm->max_bad_blks) |
| return (ui32)FsError(EINVAL); |
| |
| // '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 (vbn + i < ndm->init_bad_blk[i]) { |
| // Add 'i' to account for the initial bad blocks that precede |
| // this block. This mapping of the initial bad blocks supports |
| // use of images programmed using the 'skip bad block' method. |
| bn += i; |
| break; |
| } |
| } |
| |
| // At this point the initial bad block cannot be in the NDM area. |
| if (bn >= ndm->frst_reserved) |
| return (ui32)FsError(EINVAL); |
| |
| // Walk the running bad block map replacing initial block with the |
| // most current version (if any). |
| for (i = 0; i < ndm->num_rbb; ++i) |
| if (ndm->run_bad_blk[i].key == bn) |
| bn = ndm->run_bad_blk[i].val; |
| |
| // Ensure the block number is valid. |
| if (bn >= ndm->num_dev_blks) |
| return (ui32)FsError(EINVAL); |
| |
| // Update last read or write lookup cache. |
| if (reason == WR_MAPPING) { |
| ndm->last_wr_pbn = bn; |
| ndm->last_wr_vbn = vbn; |
| } else // reason == RD_MAPPING |
| { |
| ndm->last_rd_pbn = bn; |
| ndm->last_rd_vbn = vbn; |
| } |
| |
| // Return physical block number. |
| return bn; |
| } |
| |
| #if INC_FFS_NDM && INC_FTL_NDM |
| // get_part_type: Find out the partition type based on a block in that |
| // partition |
| // |
| // Inputs: ndm = pointer to NDM control block |
| // bn = physical block number |
| // |
| // Returns: Partition type if block is in a partition, 0 otherwise |
| // |
| static ui8 get_part_type(CNDM ndm, ui32 bn) { |
| ui32 i, vbn; |
| #if NDM_DEBUG |
| ui32 new_bn; |
| #endif |
| |
| // While block is in the NDM area, either it's a replacement for a |
| // partition block or it's an internal NDM control block. Search the |
| // NDM running bad blocks list to determine which of the two it is. |
| while (bn >= ndm->frst_reserved) { |
| // Walk the running bad block list to find if block belongs in it. |
| for (i = 0;; ++i) { |
| // If end of list is reached, block was not on it. This means |
| // block belongs to the NDM. Return 0. |
| if (i == ndm->num_rbb) |
| return 0; |
| |
| // If block found, continue search using the block it replaced. |
| if (ndm->run_bad_blk[i].val == bn) { |
| bn = ndm->run_bad_blk[i].key; |
| break; |
| } |
| } |
| } |
| |
| // Determine how many initial bad blocks preceed block in device. |
| for (i = 0; ndm->init_bad_blk[i] <= bn; ++i) |
| ; |
| |
| // Subtract the preceeding initial bad blocks to obtain the virtual |
| // block number corresponding to the physical block. |
| vbn = bn - i; |
| |
| #if NDM_DEBUG |
| // Check that reverse mapping from physical to virtual block worked |
| // correctly by matching it against the normal mapping. |
| PfAssert((new_bn = get_pbn(ndm, vbn, RD_MAPPING)) != (ui32)-1); |
| PfAssert(new_bn == bn || new_bn >= ndm->frst_reserved); |
| #endif |
| |
| // Loop over partitions to find one this virtual block belongs to. |
| for (i = 0; i < ndm->num_partitions; ++i) |
| if (ndm->partitions[i].first_block <= vbn && |
| ndm->partitions[i].first_block + ndm->partitions[i].num_blocks > vbn) |
| return ndm->partitions[i].type; |
| |
| // Partition not found. Return 0. |
| return 0; |
| } |
| #endif // INC_FFS_NDM && INC_FTL_NDM |
| |
| #if INC_FFS_NDM || INC_FTL_NDM_MLC || INC_FTL_NDM_SLC |
| // write_page: Write a page to flash for FFS and FTL |
| // |
| // Inputs: ndm = pointer to NDM control block |
| // pn = virtual page number |
| // buf = pointer to main page buffer array |
| // spare = pointer to spare page buffer array (1 if FFS) |
| // action = NDM_NONE, NDM_ECC, or NDM_ECC_VAL |
| // |
| // Returns: 0 on success, -2 on fatal error |
| // |
| static int write_page(NDM ndm, ui32 vpn, const ui8* data, ui8* spare, int action) { |
| ui32 vbn, bn, pn; |
| int rc; |
| |
| // Compute the virtual block number and check for error. |
| vbn = vpn / ndm->pgs_per_blk; |
| if (vbn >= ndm->num_vblks) { |
| FsError(EINVAL); |
| return -2; |
| } |
| |
| // Write page until successful or failure other than bad block. |
| for (;;) { |
| // Get the physical block number from virtual one. |
| bn = get_pbn(ndm, vbn, WR_MAPPING); |
| if (bn == (ui32)-1) |
| return -2; |
| |
| // Compute physical page number. |
| pn = bn * ndm->pgs_per_blk + vpn % ndm->pgs_per_blk; |
| |
| // Call TargetNDM driver to write the page. |
| rc = ndm->write_page(pn, data, spare, action, ndm->dev); |
| |
| // Return 0 if successful. |
| if (rc == 0) |
| return 0; |
| |
| // Else if chip error, mark block bad and do bad block recovery. |
| else if (rc == -1) { |
| if (ndmMarkBadBlock(ndm, pn, WRITE_PAGE)) |
| return -2; |
| } |
| |
| // Else fatal error, return -2. |
| else { |
| PfAssert(rc == -2); |
| FsError(EIO); |
| return -2; |
| } |
| } |
| } //lint !e818 |
| #endif // INC_FFS_NDM || INC_FTL_NDM_MLC || INC_FTL_NDM_SLC |
| |
| #if INC_FTL_NDM |
| // wr_pg_ftl: FTL driver function - write page (data + spare) |
| // |
| // Inputs: vpn = virtual page number |
| // data = pointer to buffer containing data to write |
| // spare = pointer to buffer containing spare bytes |
| // ndm_ptr = NDM control block handle |
| // |
| // Returns: 0 on success, -2 on fatal error |
| // |
| static int wr_pg_ftl(ui32 vpn, const void* data, void* spare, void* ndm_ptr) { |
| int action, status; |
| NDM ndm = ndm_ptr; |
| |
| // If volume block, just ECC, else request validity checks too. |
| if (RD32_LE(&((ui8*)spare)[5]) == (ui32)-1) |
| action = NDM_ECC; |
| else |
| action = NDM_ECC_VAL; |
| |
| // Grab exclusive access to TargetNDM internals. |
| semPend(ndm->sem, WAIT_FOREVER); |
| |
| // Write page to flash for FTL. |
| status = write_page(ndm, vpn, data, spare, action); |
| |
| // Release exclusive access to NDM and return status. |
| semPostBin(ndm->sem); |
| return status; |
| } //lint !e818 |
| |
| // rd_spare_ftl: FTL driver function - read/decode page spare area |
| // |
| // Inputs: vpn = virtual page number |
| // spare = buffer to read sparea area into |
| // ndm_ptr = NDM control block handle |
| // |
| // Returns: 0 on success, -1 on ECC error, -2 on fatal error, 1 if |
| // block page belongs to needs to be recycled |
| // |
| static int rd_spare_ftl(ui32 vpn, void* spare, void* ndm_ptr) { |
| ui32 vbn, bn, pn; |
| NDM ndm = ndm_ptr; |
| int status; |
| |
| // Compute virtual block number. Return -2 if out-of-range. |
| vbn = vpn / ndm->pgs_per_blk; |
| if (vbn >= ndm->num_vblks) { |
| FsError(EINVAL); |
| return -2; |
| } |
| |
| // Grab exclusive access to TargetNDM internals. |
| semPend(ndm->sem, WAIT_FOREVER); |
| |
| // Get physical block number from virtual one. Return -2 if error. |
| bn = get_pbn(ndm, vbn, RD_MAPPING); |
| if (bn == (ui32)-1) { |
| semPostBin(ndm->sem); |
| return -2; |
| } |
| |
| // Compute physical page number. |
| pn = bn * ndm->pgs_per_blk + vpn % ndm->pgs_per_blk; |
| |
| // Read and decode spare. Call FsError() if error. |
| status = ndm->read_decode_spare(pn, spare, ndm->dev); |
| if (status < 0) |
| FsError(EIO); |
| |
| // Release exclusive access to NDM and return status. |
| semPostBin(ndm->sem); |
| return status; |
| } |
| |
| // pg_check_ftl: FTL driver function - determine status of a page |
| // |
| // Inputs: vpn = virtual page number |
| // data = buffer that will hold page data |
| // spare = buffer that will hold page spare |
| // ndm_ptr = NDM control block handle |
| // |
| // Returns: -1 if error, else NDM_PAGE_ERASED (0), NDM_PAGE_VALID |
| // (1), or NDM_PAGE_INVALID (2) |
| // |
| static int pg_check_ftl(ui32 vpn, ui8* data, ui8* spare, void* ndm_ptr) { |
| NDM ndm = ndm_ptr; |
| ui32 vbn, bn, pn; |
| int status; |
| |
| // Compute the virtual block number. |
| vbn = vpn / ndm->pgs_per_blk; |
| if (vbn >= ndm->num_vblks) |
| return FsError(EINVAL); |
| |
| // Grab exclusive access to TargetNDM internals. |
| semPend(ndm->sem, WAIT_FOREVER); |
| |
| // Get the physical block number from virtual block number. |
| bn = get_pbn(ndm, vbn, RD_MAPPING); |
| if (bn == (ui32)-1) { |
| semPostBin(ndm->sem); |
| return -1; |
| } |
| |
| // Compute physical page number. |
| pn = bn * ndm->pgs_per_blk + vpn % ndm->pgs_per_blk; |
| |
| // Call the NDM driver to determine page status. |
| if (ndm->check_page(pn, data, spare, &status, ndm->dev)) { |
| semPostBin(ndm->sem); |
| return FsError(EIO); |
| } |
| |
| // Release exclusive access to NDM and return success. |
| semPostBin(ndm->sem); |
| return status; |
| } |
| #endif // INC_FTL_NDM |
| |
| #if INC_FFS_NDM || INC_FTL_NDM |
| // read_page: FFS/FTL driver function - read page (data only) |
| // |
| // Inputs: vpn = virtual page number |
| // buf = pointer to buffer to copy data to |
| // ndm_ptr = NDM control block handle |
| // |
| // Returns: 0 on success, -1 on uncorrectable ECC error, -2 on |
| // permanent fatal error, 1 if block page belongs to |
| // needs to be recycled |
| // |
| static int read_page(ui32 vpn, void* buf, void* ndm_ptr) { |
| NDM ndm = ndm_ptr; |
| ui32 vbn, bn, pn; |
| int status; |
| |
| // Compute the virtual block number based on virtual page number. |
| vbn = vpn / ndm->pgs_per_blk; |
| if (vbn >= ndm->num_vblks) { |
| FsError(EINVAL); |
| return -2; |
| } |
| |
| // Grab exclusive access to TargetNDM internals. |
| semPend(ndm->sem, WAIT_FOREVER); |
| |
| // Get the physical block number from virtual one. |
| bn = get_pbn(ndm, vbn, RD_MAPPING); |
| if (bn == (ui32)-1) { |
| semPostBin(ndm->sem); |
| return -2; |
| } |
| |
| // Compute physical page number. |
| pn = bn * ndm->pgs_per_blk + vpn % ndm->pgs_per_blk; |
| |
| // Read decode page data. |
| status = ndm->read_page(pn, buf, ndm->spare_buf, ndm->dev); |
| |
| // Release exclusive access to TargetNDM internals. |
| semPostBin(ndm->sem); |
| |
| // If error, set errno. Return status. |
| if (status) { |
| if (status == -1) |
| FsError(EINVAL); |
| else if (status == -2) |
| FsError(EIO); |
| } |
| return status; |
| } |
| |
| // ftl_xfr_page: FFS/FTL driver function - transfer a page |
| // |
| // Inputs: old_vpn = old virtual page number |
| // new_vpn = new virtual page number |
| // buf = temporary buffer for swapping main page data |
| // spare = buffer holding new page's spare data |
| // ndm_ptr = NDM control block handle |
| // |
| // Returns: 0 on success, -2 on fatal error, 1 on ECC decode error |
| // |
| static int ftl_xfr_page(ui32 old_vpn, ui32 new_vpn, ui8* buf, ui8* spare, void* ndm_ptr) { |
| ui32 old_vbn, new_vbn, old_bn, new_bn, old_pn, new_pn; |
| int status, action; |
| NDM ndm = ndm_ptr; |
| |
| // Grab exclusive access to TargetNDM internals. |
| semPend(ndm->sem, WAIT_FOREVER); |
| |
| // Based on spare buffer (NULL for FFS), determine if spare area |
| // is also to be encoded (FTL only). |
| #if INC_FFS_NDM && INC_FTL_NDM |
| if (spare == NULL) |
| #endif |
| #if INC_FFS_NDM |
| { |
| action = NDM_NONE; |
| spare = ndm->spare_buf; |
| WR32_BE(0xFFFFFFFF, &spare[EB_FRST_RESERVED]); |
| } |
| #endif |
| #if INC_FFS_NDM && INC_FTL_NDM |
| else |
| #endif |
| #if INC_FTL_NDM |
| { |
| // If FTL valid block counts, do ECC + validity, else ECC only. |
| if (RD32_LE(&spare[5]) != (ui32)-1) |
| action = NDM_ECC_VAL; |
| else |
| action = NDM_ECC; |
| } |
| #endif |
| |
| // Compute the old/new virtual block numbers based on virtual pages. |
| old_vbn = old_vpn / ndm->pgs_per_blk; |
| new_vbn = new_vpn / ndm->pgs_per_blk; |
| if (old_vbn >= ndm->num_vblks || new_vbn >= ndm->num_vblks) { |
| FsError(EINVAL); |
| semPostBin(ndm->sem); |
| return -2; |
| } |
| |
| // Get the physical old block number from virtual one. |
| old_bn = get_pbn(ndm, old_vbn, RD_MAPPING); |
| if (old_bn == (ui32)-1) { |
| semPostBin(ndm->sem); |
| return -2; |
| } |
| |
| // Compute the old physical page number. |
| old_pn = old_bn * ndm->pgs_per_blk + old_vpn % ndm->pgs_per_blk; |
| |
| // Copy page to new block until success or error other than bad block |
| for (;;) { |
| // Get the physical new block number for virtual one. |
| new_bn = get_pbn(ndm, new_vbn, WR_MAPPING); |
| if (new_bn == (ui32)-1) { |
| semPostBin(ndm->sem); |
| return -2; |
| } |
| |
| // Compute the new physical page number. |
| new_pn = new_bn * ndm->pgs_per_blk + new_vpn % ndm->pgs_per_blk; |
| |
| // Transfer data from old page to new page. |
| status = ndm->xfr_page(old_pn, new_pn, buf, ndm->tmp_spare, spare, action, ndm->dev_ndm); |
| |
| // Break if success (0) or ECC decode error (1). |
| if (status >= 0) |
| break; |
| |
| // Else if fatal error (-2), break to return -2. |
| if (status == -2) { |
| FsError(EIO); |
| break; |
| } |
| |
| // Else chip error (-1), mark block bad and do bad block recovery. |
| else { |
| PfAssert(status == -1); |
| if (ndmMarkBadBlock(ndm, new_pn, WRITE_PAGE)) { |
| status = -2; |
| break; |
| } |
| } |
| } |
| |
| // Release exclusive access to NDM and return status. |
| semPostBin(ndm->sem); |
| return status; |
| } //lint !e818 |
| #endif // INC_FFS_NDM || INC_FTL_NDM |
| |
| #if INC_FFS_NDM |
| // wr_pg_ffs: FFS driver function - write page (data + spare) |
| // |
| // Inputs: buf = pointer to buffer containing data to write |
| // vpn = virtual page number |
| // type = page type flag |
| // ndm_ptr = NDM control block handle |
| // |
| // Returns: 0 on success, -2 on fatal error |
| // |
| static int wr_pg_ffs(const void* buf, ui32 vpn, ui32 type, void* ndm_ptr) { |
| int status; |
| NDM ndm = ndm_ptr; |
| |
| // Grab exclusive access to TargetNDM internals. |
| semPend(ndm->sem, WAIT_FOREVER); |
| |
| // Prepare page's spare area. |
| memset(ndm->spare_buf, 0xFF, ndm->eb_size); |
| WR32_BE(type, &ndm->spare_buf[EB_FRST_RESERVED]); |
| |
| // Write page to flash for FFS. |
| status = write_page(ndm, vpn, buf, ndm->spare_buf, NDM_NONE); |
| |
| // Release exclusive access to NDM and return status. |
| semPostBin(ndm->sem); |
| return status; |
| } |
| |
| // rd_type_ffs: FFS driver function - read page type from spare |
| // |
| // Inputs: vpn = virtual page number |
| // ndm_ptr = NDM control block handle |
| // Output: *type = four byte type value for page |
| // |
| // Returns: 0 on success, -2 on fatal error |
| // |
| static int rd_type_ffs(ui32 vpn, ui32* type, void* ndm_ptr) { |
| ui32 vbn, bn, pn; |
| NDM ndm = ndm_ptr; |
| |
| // Compute the virtual block number based on virtual page number. |
| vbn = vpn / ndm->pgs_per_blk; |
| if (vbn >= ndm->num_vblks) { |
| FsError(EINVAL); |
| return -2; |
| } |
| |
| // Grab exclusive access to TargetNDM internals. |
| semPend(ndm->sem, WAIT_FOREVER); |
| |
| // Get the physical block number from virtual one. |
| bn = get_pbn(ndm, vbn, RD_MAPPING); |
| if (bn == (ui32)-1) { |
| semPostBin(ndm->sem); |
| return -2; |
| } |
| |
| // Compute physical page number. |
| pn = bn * ndm->pgs_per_blk + vpn % ndm->pgs_per_blk; |
| |
| // Read spare. Return -2 if fatal error. |
| if (ndm->read_spare(pn, ndm->spare_buf, ndm->dev)) { |
| FsError(EIO); |
| semPostBin(ndm->sem); |
| return -2; |
| } |
| |
| // Extract the type from the spare area. |
| *type = RD32_BE(&ndm->spare_buf[EB_FRST_RESERVED]); |
| |
| // Release exclusive access to NDM and return success. |
| semPostBin(ndm->sem); |
| return 0; |
| } |
| |
| // pg_blank_ffs: Check if a page is empty - both data and spare |
| // |
| // Inputs: vpn = virtual page number |
| // ndm_ptr = NDM control block handle |
| // |
| // Returns: TRUE if erased, FALSE if any 0 bit, -2 if fatal error |
| // |
| static int pg_blank_ffs(ui32 vpn, void* ndm_ptr) { |
| NDM ndm = ndm_ptr; |
| ui32 vbn, bn, pn; |
| int status; |
| |
| // Compute the virtual block number based on virtual page number. |
| vbn = vpn / ndm->pgs_per_blk; |
| if (vbn >= ndm->num_vblks) { |
| FsError(EINVAL); |
| return -2; |
| } |
| |
| // Grab exclusive access to TargetNDM internals. |
| semPend(ndm->sem, WAIT_FOREVER); |
| |
| // Get the physical block number from virtual one. |
| bn = get_pbn(ndm, vbn, RD_MAPPING); |
| if (bn == (ui32)-1) { |
| semPostBin(ndm->sem); |
| return -2; |
| } |
| |
| // Compute physical page number. |
| pn = bn * ndm->pgs_per_blk + vpn % ndm->pgs_per_blk; |
| |
| // Call driver to see if main and spare areas are erased. |
| status = ndm->page_blank(pn, ndm->main_buf, ndm->spare_buf, ndm->dev); |
| |
| // Release exclusive access to NDM and return status. |
| semPostBin(ndm->sem); |
| |
| // If error, set errno before returning status. |
| if (status < 0) |
| FsError(EIO); |
| return status; |
| } |
| #endif // INC_FFS_NDM |
| |
| #if INC_FFS_NDM_MLC || INC_FTL_NDM_MLC |
| // pair_offset: FTL driver function (MLC NAND) - pair offset |
| // |
| // Inputs: page_offset = page offset within block |
| // ndm_ptr = NDM control block handle |
| // |
| // Returns: Pair page offset within block if any, page offset |
| // otherwise |
| // |
| static ui32 pair_offset(ui32 page_offset, void* ndm_ptr) { |
| NDM ndm = ndm_ptr; |
| |
| return ndm->pair_offset(page_offset, ndm->dev); |
| } |
| #endif // INC_FFS_NDM_MLC || INC_FTL_NDM_MLC |
| |
| // Global Function Definitions |
| |
| // ndmMarkBadBlock: Mark virtual block bad and do bad block recovery |
| // |
| // Inputs: ndm = pointer to NDM control block |
| // arg = bad block or page depending on cause |
| // cause = ERASE_BLOCK or WRITE_PAGE failure |
| // |
| // Returns: 0 on success, -1 on failure |
| // |
| int ndmMarkBadBlock(NDM ndm, ui32 arg, ui32 cause) { |
| ui32 bad_b, bad_pn, free_b, i, old_pn, new_pn; |
| int status, transfer_finished; |
| #if INC_FFS_NDM && INC_FTL_NDM |
| ui8 part_type; |
| #endif |
| |
| // Clear virtual to physical translation caches. |
| ndm->last_wr_vbn = ndm->last_rd_vbn = (ui32)-1; |
| |
| // Get a free block to replace the bad virtual block. |
| free_b = get_free_virt_blk(ndm); |
| if (free_b == (ui32)-1) |
| return FsError(ENOSPC); |
| |
| // Based on cause, figure out bad block and first bad page. |
| if (cause == ERASE_BLOCK) { |
| bad_b = arg; |
| bad_pn = bad_b * ndm->pgs_per_blk; |
| } else { |
| bad_pn = arg; |
| bad_b = bad_pn / ndm->pgs_per_blk; |
| } |
| |
| #if INC_FFS_NDM && INC_FTL_NDM |
| // Figure out the partition type for bad block. |
| part_type = get_part_type(ndm, bad_b); |
| #endif |
| |
| // Search the running bad block list for this block. |
| for (i = 0;; ++i) { |
| // Check if reached end of list without finding match. |
| if (i == ndm->num_rbb) { |
| // Adjust bad block count. If too many, return -1. |
| if (++ndm->num_bad_blks > ndm->max_bad_blks) |
| return FsError(ENOSPC); |
| |
| // Add block as last list entry and then break. |
| ndm->run_bad_blk[ndm->num_rbb].key = bad_b; |
| ndm->run_bad_blk[ndm->num_rbb].val = free_b; |
| ++ndm->num_rbb; |
| break; |
| } |
| |
| // If in list (due to recovery of bad block transfer), update it. |
| if (ndm->run_bad_blk[i].key == bad_b) { |
| ndm->run_bad_blk[i].val = free_b; |
| break; |
| } |
| } |
| |
| // Loop until bad block recovery is finished. |
| for (transfer_finished = FALSE;;) { |
| // Call driver to erase the free block. |
| status = ndm->erase_block(free_b * ndm->pgs_per_blk, ndm->dev); |
| |
| // Check if the block erase succeeded. |
| if (status == 0) { |
| // Finished if block copy unneeded. |
| if (cause == ERASE_BLOCK) |
| break; |
| |
| // Prepare control information with bad block transfer data. |
| ndm->xfr_tblk = free_b; |
| ndm->xfr_fblk = bad_b; |
| ndm->xfr_bad_po = bad_pn % ndm->pgs_per_blk; |
| |
| // Write TargetNDM metadata (includes bad block lists). |
| if (ndmWrCtrl(ndm)) |
| return -1; |
| |
| // Transfer data from the bad block to the free block. |
| old_pn = bad_b * ndm->pgs_per_blk; |
| new_pn = free_b * ndm->pgs_per_blk; |
| for (i = 0;; ++i, ++old_pn, ++new_pn) { |
| int action; |
| |
| // If bad write page reached, transfer finished. |
| if (i == ndm->xfr_bad_po) { |
| transfer_finished = TRUE; |
| break; |
| } |
| |
| // If end of block reached, transfer finished. |
| if (i >= ndm->pgs_per_blk) { |
| transfer_finished = TRUE; |
| break; |
| } |
| |
| // Call driver to see if main and spare areas are erased. |
| status = ndm->page_blank(old_pn, ndm->main_buf, ndm->spare_buf, ndm->dev); |
| |
| // If erased, skip page. Else if I/O error, return -1. |
| if (status == TRUE) |
| continue; |
| else if (status < 0) |
| return FsError(EIO); |
| |
| // Read main data. Return -1 if ECC or fatal error. |
| status = ndm->read_page(old_pn, ndm->main_buf, ndm->spare_buf, ndm->dev); |
| if (status < 0) |
| return FsError(EIO); |
| |
| // If FTL partition, read old spare data. Return -1 if ECC or |
| // fatal error. |
| #if INC_FFS_NDM && INC_FTL_NDM |
| if (part_type != FFS_VOL) |
| #endif |
| #if INC_FTL_NDM |
| { |
| status = ndm->read_decode_spare(old_pn, ndm->spare_buf, ndm->dev); |
| if (status < 0) |
| return FsError(EIO); |
| } |
| #endif |
| |
| // Else if FFS partition, read spare without decoding. |
| #if INC_FFS_NDM && INC_FTL_NDM |
| else |
| #endif |
| #if INC_FFS_NDM |
| { |
| status = ndm->read_spare(old_pn, ndm->spare_buf, ndm->dev); |
| if (status == -2) |
| return FsError(EIO); |
| } |
| #endif |
| |
| // If FFS volume, no ECC on spare and no validity check. |
| #if INC_FFS_NDM && INC_FTL_NDM |
| if (part_type == FFS_VOL) |
| #endif |
| #if INC_FFS_NDM |
| action = NDM_NONE; |
| #endif |
| #if INC_FFS_NDM && INC_FTL_NDM |
| else |
| #endif |
| #if INC_FTL_NDM |
| // FTL volume. If volume page, just ECC the spare bytes. |
| if (RD32_LE(&ndm->spare_buf[5]) == (ui32)-1) |
| action = NDM_ECC; |
| |
| // Else map page, ECC the spare bytes and prep validity check. |
| else |
| action = NDM_ECC_VAL; |
| #endif |
| |
| // Write page to new location. Break if error occurs. |
| status = ndm->write_page(new_pn, ndm->main_buf, ndm->spare_buf, action, ndm->dev); |
| if (status) |
| break; |
| } |
| } |
| |
| // Break if the block transfer is finished. |
| if (transfer_finished) |
| break; |
| |
| // Return -1 if fatal error. |
| if (status == -2) |
| return FsError(EIO); |
| |
| // Else bad block. Mark it. Return -1 if error. |
| else { |
| PfAssert(status == -1); |
| if (mark_extra_bblock(ndm, &free_b)) |
| return -1; |
| } |
| } |
| |
| // Update control information to clear the bad block transfer state. |
| ndm->xfr_tblk = (ui32)-1; |
| if (ndmWrCtrl(ndm)) |
| return -1; |
| |
| // Return success. |
| return 0; |
| } |
| |
| // ndmWrCtrl: Write NDM control information to flash |
| // |
| // Input: ndm = pointer to NDM control block |
| // |
| // Returns: 0 on success, -1 on error |
| // |
| int ndmWrCtrl(NDM ndm) { |
| ui32 ctrl_blk, first_page = ndm->next_ctrl_start; |
| int status; |
| |
| // Keep writing control information until successful. |
| for (status = 0;;) { |
| // Check if this is first write to this control block. |
| if (first_page % ndm->pgs_per_blk == 0) { |
| // Call driver to erase the block. Return -1 if fatal error. |
| status = ndm->erase_block(first_page, ndm->dev); |
| if (status == -2) |
| return FsError(EIO); |
| if (status) |
| ctrl_blk = first_page / ndm->pgs_per_blk; |
| } |
| |
| // Check if block erase command succeeded or was unneeded. |
| if (status == 0) { |
| // Write the control information. Return -1 if fatal error. |
| // Outputs number of bad block, if there is a failure. |
| status = wr_ctrl_info(ndm, first_page, &ctrl_blk); |
| if (status == -2) |
| return -1; |
| |
| // Else break if successful. |
| else if (status == 0) |
| break; |
| PfAssert(status == -1); |
| } |
| |
| // Block failed. Mark it bad and get new control block. |
| if (mark_ctrl_bblock(ndm, &ctrl_blk)) |
| return -1; |
| first_page = ctrl_blk * ndm->pgs_per_blk; |
| } |
| |
| // For SLC devices, start next control write immediately after the |
| // last page in this control information. |
| first_page = ndm->last_ctrl_page + 1; |
| |
| #if INC_NDM_MLC |
| // For MLC devices, take into account page pair offset so that new |
| // write can not affect old metadata in case of power off. |
| if (ndm->dev_type == NDM_MLC) |
| first_page = ndmPastPrevPair(ndm, first_page); |
| #endif |
| |
| // If start of next write falls outside the current control block, |
| // move to the other control block. |
| ctrl_blk = ndm->last_ctrl_page / ndm->pgs_per_blk; |
| if (first_page / ndm->pgs_per_blk != ctrl_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 |
| return FsError(EINVAL); |
| first_page = ctrl_blk * ndm->pgs_per_blk; |
| } |
| |
| #if RDBACK_CHECK |
| // Read-back verify the TargetNDM metadata. |
| ndmCkMeta(ndm); |
| #endif |
| |
| // Save start of next control info write and return success. |
| ndm->next_ctrl_start = first_page; |
| return 0; |
| } |
| |
| // ndmUnformat: Unformat (erase all good blocks) an NDM |
| // |
| // Input: ndm = pointer to NDM control block |
| // |
| // Returns: 0 on success, -1 on failure |
| // |
| int ndmUnformat(NDM ndm) { |
| int status; |
| ui32 b; |
| |
| // Grab exclusive access to the NDM. |
| semPend(ndm->sem, WAIT_FOREVER); |
| |
| #if NV_NDM_CTRL_STORE |
| // Invalidate the saved first page of NDM control information. |
| NvNdmCtrlPgWr(0); |
| #endif |
| |
| // Walk through all the blocks, erasing good ones. |
| for (b = 0; b < ndm->num_dev_blks; ++b) { |
| // If block is an initial bad block, skip it. |
| if (ndmInitBadBlock(ndm, b)) |
| continue; |
| |
| // If block is a running bad block, skip it. |
| if (run_bad_block(ndm, b)) |
| continue; |
| |
| // Erase block. If command fails, mark as bad block. |
| status = ndm->erase_block(b * ndm->pgs_per_blk, ndm->dev); |
| if (status == -1) { |
| if (ndmMarkBadBlock(ndm, b, ERASE_BLOCK)) { |
| semPostBin(ndm->sem); |
| return -1; |
| } |
| } |
| |
| // If failure other than bad block, return -1. |
| else if (status) { |
| semPostBin(ndm->sem); |
| return FsError(EIO); |
| } |
| } |
| |
| // Remove all the volumes on the device. |
| status = ndmDelVols(ndm); |
| |
| // Release exclusive access to the NDM and return status. |
| semPostBin(ndm->sem); |
| return status; |
| } //lint !e818 |
| |
| // ndmGetNumVBlocks: Return number of virtual blocks in NDM |
| // |
| // Input: ndm = pointer to NDM control block |
| // |
| ui32 ndmGetNumVBlocks(CNDM ndm) { |
| return ndm->num_vblks; |
| } |
| |
| #if INC_FFS_NDM |
| // ndmAddVolFFS: Add an FFS volume based on an NDM partition |
| // |
| // Inputs: ndm = pointer to NDM control block |
| // part_num = NDM partition number |
| // ffs_dvr = FFS driver information |
| // |
| // Returns: 0 on success, -1 on error |
| // |
| int ndmAddVolFFS(NDM ndm, ui32 part_num, FfsVol* ffs_dvr) { |
| NDMPartition* part; |
| |
| // Check partition number. |
| if (part_num >= ndm->num_partitions) |
| return FsError(EINVAL); |
| part = &ndm->partitions[part_num]; |
| |
| // Check partition type. |
| if (part->type != FFS_VOL) |
| return FsError(EINVAL); |
| |
| // Check partition first block and number of blocks. |
| if (part->first_block + part->num_blocks > ndm->num_vblks) |
| return FsError(ENOSPC); |
| |
| // Set up the non-customizable part of the FFS driver. |
| ffs_dvr->name = part->name; |
| ffs_dvr->num_blocks = part->num_blocks; |
| ffs_dvr->block_size = ndm->block_size; |
| ffs_dvr->page_size = ndm->page_size; |
| ffs_dvr->driver.nand.start_page = part->first_block * ndm->pgs_per_blk; |
| ffs_dvr->driver.nand.write_page = wr_pg_ffs; |
| ffs_dvr->driver.nand.write_pages = ndmWritePages; |
| ffs_dvr->driver.nand.transfer_page = ftl_xfr_page; |
| ffs_dvr->driver.nand.read_page = read_page; |
| ffs_dvr->driver.nand.read_pages = ndmReadPages; |
| ffs_dvr->driver.nand.read_type = rd_type_ffs; |
| ffs_dvr->driver.nand.page_erased = pg_blank_ffs; |
| ffs_dvr->driver.nand.erase_block = ndmEraseBlock; |
| ffs_dvr->flags |= FSF_TRANSFER_PAGE | FSF_MULTI_ACCESS; |
| ffs_dvr->vol = ndm; |
| |
| // Set volume type and type-specific driver routines. |
| #if INC_FFS_NDM_SLC && INC_FFS_NDM_MLC |
| if (ndm->dev_type == NDM_SLC) |
| #endif |
| #if INC_FFS_NDM_SLC |
| { |
| ffs_dvr->type = FFS_NAND_SLC; |
| } |
| #endif |
| #if INC_FFS_NDM_SLC && INC_FFS_NDM_MLC |
| else |
| #endif |
| #if INC_FFS_NDM_MLC |
| { |
| ffs_dvr->type = FFS_NAND_MLC; |
| ffs_dvr->driver.nand.pair_offset = pair_offset; |
| } |
| #endif |
| |
| // Create FFS volume. |
| return FfsAddNdmVol(ffs_dvr, ndm->spare_buf); |
| } |
| #endif // INC_FFS_NDM |
| |
| #if INC_FTL_NDM |
| #if INC_SECT_FTL |
| // ndmAddFatFTL: Add a TargetFAT FTL to an NDM partition |
| // |
| // Inputs: ndm = pointer to NDM control block |
| // part_num = NDM partition number |
| // ftl = FTL driver information |
| // fat = FAT volume information |
| // |
| // Returns: Pointer to FTL control block on success, else NULL |
| // |
| void* ndmAddFatFTL(NDM ndm, ui32 part_num, FtlNdmVol* ftl, FatVol* fat) { |
| NDMPartition* part; |
| |
| // Check partition number. |
| if (part_num >= ndm->num_partitions) { |
| FsError(EINVAL); |
| return NULL; |
| } |
| part = &ndm->partitions[part_num]; |
| |
| // Check partition type. |
| if (part->type != FAT_VOL) { |
| FsError(EINVAL); |
| return NULL; |
| } |
| |
| // Check partition first block and number of blocks. |
| if (part->first_block + part->num_blocks > ndm->num_vblks) { |
| FsError(ENOSPC); |
| return NULL; |
| } |
| |
| // Set up the non-customizable part of the FTL/FAT drivers. |
| ftl->ndm = ndm; |
| ftl->start_page = part->first_block * ndm->pgs_per_blk; |
| ftl->num_blocks = part->num_blocks; |
| ftl->block_size = ndm->block_size; |
| ftl->page_size = ndm->page_size; |
| ftl->eb_size = ndm->eb_size; |
| ftl->erase_block = ndmEraseBlock; |
| ftl->write_data_and_spare = wr_pg_ftl; |
| ftl->write_pages = ndmWritePages; |
| ftl->read_spare = rd_spare_ftl; |
| ftl->read_pages = ndmReadPages; |
| ftl->page_check = pg_check_ftl; |
| ftl->transfer_page = ftl_xfr_page; |
| #if INC_FTL_NDM_MLC |
| ftl->pair_offset = pair_offset; |
| #endif |
| ftl->type = ndm->dev_type; |
| fat->name = part->name; |
| |
| // Create a new TargetFTL-NDM FAT FTL for this partition. |
| return FtlNdmAddFatFTL(ftl, fat); |
| } |
| |
| // ndmAddVolFatFTL: Add FTL and FAT volume to NDM partition |
| // |
| // Inputs: ndm = pointer to NDM control block |
| // part_num = NDM partition number |
| // ftl = FTL driver information |
| // fat = FAT volume information |
| // |
| // Returns: 0 on success, -1 on error |
| // |
| int ndmAddVolFatFTL(NDM ndm, ui32 part_num, FtlNdmVol* ftl, FatVol* fat) { |
| void* ftl_ndm; |
| |
| // Add a TargetFAT FTL to this TargetNDM partition. |
| ftl_ndm = ndmAddFatFTL(ndm, part_num, ftl, fat); |
| if (ftl_ndm == NULL) |
| return -1; |
| |
| // Register FAT volume on top of FTL. Delete FTL if error. |
| if (FatAddVol(fat)) { |
| FtlnFreeFTL(ftl_ndm); |
| return -1; |
| } |
| |
| // Return success. |
| return 0; |
| } |
| #endif // INC_SECT_FTL |
| |
| #if INC_PAGE_FTL |
| // ndmAddVolXfsFTL: Add FTL and XFS volume to NDM partition |
| // |
| // Inputs: ndm = pointer to NDM control block |
| // part_num = NDM partition number |
| // ftl = FTL driver information |
| // xfs = XFS volume information |
| // |
| // Returns: 0 on success, -1 on error |
| // |
| int ndmAddVolXfsFTL(NDM ndm, ui32 part_num, FtlNdmVol* ftl, XfsVol* xfs) { |
| NDMPartition* part; |
| void* ftl_ndm; |
| |
| // Check partition number. |
| if (part_num >= ndm->num_partitions) |
| return FsError(EINVAL); |
| part = &ndm->partitions[part_num]; |
| |
| // Check partition type. |
| if (part->type != XFS_VOL) |
| return FsError(EINVAL); |
| |
| // Check partition first block and number of blocks. |
| if (part->first_block + part->num_blocks > ndm->num_vblks) |
| return FsError(ENOSPC); |
| |
| // Set up the non-customizable part of the FTL/XFS drivers. |
| ftl->ndm = ndm; |
| ftl->start_page = part->first_block * ndm->pgs_per_blk; |
| ftl->num_blocks = part->num_blocks; |
| ftl->block_size = ndm->block_size; |
| ftl->page_size = ndm->page_size; |
| ftl->eb_size = ndm->eb_size; |
| ftl->erase_block = ndmEraseBlock; |
| ftl->write_data_and_spare = wr_pg_ftl; |
| ftl->write_pages = ndmWritePages; |
| ftl->read_spare = rd_spare_ftl; |
| ftl->read_pages = ndmReadPages; |
| ftl->page_check = pg_check_ftl; |
| ftl->transfer_page = ftl_xfr_page; |
| #if INC_FTL_NDM_MLC |
| ftl->pair_offset = pair_offset; |
| #endif |
| ftl->type = ndm->dev_type; |
| xfs->name = part->name; |
| |
| // Add a TargetXFS FTL to this TargetNDM partition. |
| ftl_ndm = FtlNdmAddXfsFTL(ftl, xfs); |
| if (ftl_ndm == NULL) |
| return -1; |
| |
| // Register XFS volume on top of FTL. Delete FTL if error. |
| if (XfsAddVol(xfs)) { |
| FtlnFreeFTL(ftl_ndm); |
| return -1; |
| } |
| |
| // Return success. |
| return 0; |
| } |
| #endif // INC_PAGE_FTL |
| #endif // INC_FTL_NDM_MLC || INC_FTL_NDM_SLC |
| |
| #if INC_FFS_NDM || INC_FTL_NDM_MLC || INC_FTL_NDM_SLC |
| // ndmReadPages: FFS/FTL driver function - read multiple consecutive |
| // pages from a single block (data only) |
| // |
| // Inputs: vpn = starting virtual page number |
| // count = number of consecutive virtual pages to read |
| // data = pointer to buffer to copy main page data to |
| // spare = points to array of page spare data sets |
| // ndm_ptr = NDM control block handle |
| // |
| // Returns: -2 on fatal error, -1 on error, 0 on success, 1 if |
| // block needs to be recycled |
| // |
| int ndmReadPages(ui32 vpn, ui32 count, void* data, void* spare, void* ndm_ptr) { |
| NDM ndm = ndm_ptr; |
| int status; |
| |
| // If NDM driver supplies read_pages(), use it. |
| if (ndm->read_pages) { |
| ui32 vbn, bn, pn; |
| |
| // Compute the virtual block number based on virtual page number. |
| vbn = vpn / ndm->pgs_per_blk; |
| if (vbn >= ndm->num_vblks) { |
| FsError(EINVAL); |
| return -2; |
| } |
| |
| // Grab exclusive access to TargetNDM internals. |
| semPend(ndm->sem, WAIT_FOREVER); |
| |
| // Get the physical block number from virtual one. |
| bn = get_pbn(ndm, vbn, RD_MAPPING); |
| if (bn == (ui32)-1) { |
| semPostBin(ndm->sem); |
| return -2; |
| } |
| |
| // Compute starting physical page number. |
| pn = bn * ndm->pgs_per_blk + vpn % ndm->pgs_per_blk; |
| |
| // Read pages. |
| status = ndm->read_pages(pn, count, data, spare, ndm->dev); |
| |
| // Release exclusive access to NDM and return status. |
| semPostBin(ndm->sem); |
| return status; |
| } |
| |
| // Else loop over all pages, reading one at a time. |
| else { |
| ui32 i; |
| int rd_status; |
| ui8* buf = data; |
| |
| // Loop over virtual pages. |
| for (status = 0, i = 0; i < count; ++i, ++vpn, buf += ndm->page_size) { |
| // Issue current page read request. |
| rd_status = read_page(vpn, buf, ndm_ptr); |
| |
| // If error, return. |
| if (rd_status < 0) |
| return rd_status; |
| |
| // If recycles needed for block, set return status. |
| if (rd_status == 1) |
| status = 1; |
| } |
| |
| // Return status. |
| return status; |
| } |
| } |
| |
| // ndmWritePages: FFS/FTL driver function - write multiple consecutive |
| // pages to a single block (data only) |
| // |
| // Inputs: vpn = starting virtual page number |
| // count = number of consecutive virtual pages to write |
| // data = points to array of page main data sets |
| // spare = points to array of page spare data sets |
| // ndm_ptr = NDM control block handle |
| // |
| // Returns: -1 on error, 0 on success |
| // |
| int ndmWritePages(ui32 vpn, ui32 count, const void* data, void* spare, void* ndm_ptr) { |
| int action, rc = 0; |
| NDM ndm = ndm_ptr; |
| |
| // Ensure all writes are to the same virtual block. |
| PfAssert(count); |
| PfAssert(vpn / ndm->pgs_per_blk == (vpn + count - 1) / ndm->pgs_per_blk); |
| |
| #if INC_FFS_NDM && INC_FTL_NDM |
| // If write triggerred by FFS, no extra action required on spare. |
| if (spare == NULL) |
| #endif |
| #if INC_FFS_NDM |
| action = NDM_NONE; |
| #endif |
| |
| #if INC_FFS_NDM && INC_FTL_NDM |
| else |
| #endif |
| |
| #if INC_FTL_NDM |
| // Else for FTL, prepare ECC and, if map page, validity checks too. |
| if (RD32_LE(&((ui8*)spare)[5]) == (ui32)-1) |
| action = NDM_ECC; |
| else |
| action = NDM_ECC_VAL; |
| #endif |
| |
| // Grab exclusive access to TargetNDM internals. |
| semPend(ndm->sem, WAIT_FOREVER); |
| |
| // If NDM driver supplies write_pages(), use it. |
| if (ndm->write_pages) { |
| ui32 vbn; |
| |
| // Compute the virtual block number based on virtual page number. |
| vbn = vpn / ndm->pgs_per_blk; |
| if (vbn >= ndm->num_vblks) { |
| semPostBin(ndm->sem); |
| return FsError(EINVAL); |
| } |
| |
| // Writing to flash until success or failure other than bad block. |
| for (;;) { |
| ui32 bn, pn; |
| |
| // Get the physical block number from virtual one. |
| bn = get_pbn(ndm, vbn, WR_MAPPING); |
| if (bn == (ui32)-1) { |
| rc = -1; |
| break; |
| } |
| |
| // Compute starting physical page number. |
| pn = bn * ndm->pgs_per_blk + vpn % ndm->pgs_per_blk; |
| |
| // Write pages. If successful, break from loop. |
| rc = ndm->write_pages(pn, count, data, spare, action, ndm->dev); |
| if (rc == 0) |
| break; |
| |
| // If fatal error, break to return -1. |
| if (rc == -2) { |
| rc = FsError(EIO); |
| break; |
| } |
| |
| // Else bad block, mark it bad. If error, break to return -1. |
| else { |
| PfAssert(rc == -1); |
| if (ndmMarkBadBlock(ndm, pn, WRITE_PAGE)) |
| break; |
| } |
| } |
| } |
| |
| // Else loop over all pages, writing one at a time. |
| else { |
| ui32 past = vpn + count; |
| const ui8* curr_data = data; |
| ui8* curr_spare = spare; |
| |
| // Loop over virtual pages. |
| for (; vpn < past; ++vpn) { |
| // Write current page. |
| rc = write_page(ndm, vpn, curr_data, curr_spare, action); |
| if (rc) |
| break; |
| |
| // Advance data pointer and - if not TargetFFS - spare pointer. |
| curr_data += ndm->page_size; |
| #if INC_FFS_NDM |
| if (action != NDM_NONE) |
| #endif |
| curr_spare += ndm->eb_size; |
| } |
| } |
| |
| // Release exclusive access to NDM and return status. |
| semPostBin(ndm->sem); |
| return rc; |
| } |
| #endif // INC_FFS_NDM || INC_FTL_NDM_MLC || INC_FTL_NDM_SLC |
| |
| // ndmGetNumPartitions: Retrieve number of current partitions in table |
| // |
| // Input: ndm = pointer to NDM control block |
| // |
| // Returns: Current number of partitions in NDM |
| // |
| ui32 ndmGetNumPartitions(CNDM ndm) { |
| return ndm->num_partitions; |
| } |
| |
| // ndmSetNumPartitions: Set number of current partitions in table |
| // |
| // Inputs: ndm = pointer to NDM control block |
| // num_partitions = number of desired partitions |
| // |
| // Returns: 0 on success, -1 on failure |
| // |
| int ndmSetNumPartitions(NDM ndm, ui32 num_partitions) { |
| NDMPartition* new_partitions; |
| |
| // If the number of partitions is unchanged, simply return success. |
| if (num_partitions == ndm->num_partitions) |
| return 0; |
| |
| // If number of partitions is 0, this is a delete table call. |
| if (num_partitions == 0) { |
| ndmDeletePartitionTable(ndm); |
| return 0; |
| } |
| |
| // Allocate space for the new partitions table. |
| new_partitions = FsCalloc(num_partitions, sizeof(NDMPartition)); |
| if (new_partitions == NULL) |
| return -1; |
| |
| // If there is a current partitions table, copy as much of it as |
| // possible to the new table before removing it. |
| if (ndm->partitions) { |
| ui32 i, max_i = MIN(num_partitions, ndm->num_partitions); |
| |
| for (i = 0; i < max_i; ++i) |
| memcpy(&new_partitions[i], &ndm->partitions[i], sizeof(NDMPartition)); |
| FsFree(ndm->partitions); |
| } |
| |
| // Set the new partition information in the NDM. |
| ndm->partitions = new_partitions; |
| ndm->num_partitions = num_partitions; |
| |
| // Return success. |
| return 0; |
| } |
| |
| // ndmGetPartition: Return partition entry handle |
| // |
| // Inputs: ndm = pointer to NDM control block |
| // part_num = partition number |
| // |
| // Returns: partition handle on success, NULL on error |
| // |
| const NDMPartition* ndmGetPartition(CNDM ndm, ui32 part_num) { |
| // If partition number out of bounds, error. |
| if (part_num >= ndm->num_partitions) { |
| FsError(EINVAL); |
| return NULL; |
| } |
| |
| // Return pointer to specified partition structure. |
| return &ndm->partitions[part_num]; |
| } |
| |
| // ndmWritePartition: Write a partition entry into partitions table |
| // |
| // Inputs: ndm = pointer to NDM control block |
| // part = buffer to get partition information from |
| // part_num = partition number |
| // name = partition name |
| // |
| // Returns: 0 on success, -1 on error |
| // |
| int ndmWritePartition(NDM ndm, const NDMPartition* part, ui32 part_num, const char* name) { |
| ui32 i; |
| |
| // Ensure name is not too long and copy it to partition structure. |
| if (strlen(name) >= NDM_PART_NAME_LEN) |
| return FsError(EINVAL); |
| strcpy((char*)part->name, name); |
| |
| // If partition first block or number of blocks invalid, error. |
| if (part->first_block >= ndm->num_vblks || |
| part->first_block + part->num_blocks > ndm->num_vblks) |
| return FsError(EINVAL); |
| |
| // If there are partitions already, check there is no overlap. |
| for (i = 0; i < ndm->num_partitions; ++i) { |
| // Skip partition we want to replace. |
| if (i == part_num) |
| continue; |
| |
| // Check for overlap only against valid partition entries. |
| if (ndm->partitions[i].type) { |
| if ((part->first_block >= ndm->partitions[i].first_block && |
| part->first_block < |
| ndm->partitions[i].first_block + ndm->partitions[i].num_blocks) || |
| (ndm->partitions[i].first_block >= part->first_block && |
| ndm->partitions[i].first_block < part->first_block + part->num_blocks)) |
| return FsError(EINVAL); |
| } |
| } |
| |
| // If partition number out of bounds, adjust partition table. |
| if (part_num >= ndm->num_partitions) |
| if (ndmSetNumPartitions(ndm, part_num + 1)) |
| return -1; |
| |
| // Write partition information and return success. |
| memcpy(&ndm->partitions[part_num], part, sizeof(NDMPartition)); |
| return 0; |
| } |
| |
| // ndmEraseBlock: Erase a block |
| // |
| // Inputs: vpn = base virtual page for block to be erased |
| // ndm_ptr = NDM control block handle |
| // |
| // Returns: 0 on success, -1 on fatal error |
| // |
| int ndmEraseBlock(ui32 vpn, void* ndm_ptr) { |
| NDM ndm = ndm_ptr; |
| ui32 vbn, bn, pn; |
| int status; |
| |
| // Compute the virtual block number based on virtual page number. |
| vbn = vpn / ndm->pgs_per_blk; |
| if (vbn >= ndm->num_vblks) |
| return FsError(EINVAL); |
| |
| // Grab exclusive access to TargetNDM internals. |
| semPend(ndm->sem, WAIT_FOREVER); |
| |
| // Get the physical block number from virtual one. |
| bn = get_pbn(ndm, vbn, WR_MAPPING); |
| if (bn == (ui32)-1) { |
| semPostBin(ndm->sem); |
| return -1; |
| } |
| |
| // Compute physical page number. |
| pn = bn * ndm->pgs_per_blk + vpn % ndm->pgs_per_blk; |
| |
| // Erase the block. |
| status = ndm->erase_block(pn, ndm->dev); |
| if (status < 0) { |
| // If chip error, do bad block recovery. Else return fatal error. |
| if (status == -1) |
| status = ndmMarkBadBlock(ndm, bn, ERASE_BLOCK); |
| else |
| FsError(EIO); |
| } |
| |
| // Release exclusive NDM access and return status. |
| semPostBin(ndm->sem); |
| return status; |
| } |
| |
| // ndmErasePartition: Erase all the virtual blocks in a partition |
| // |
| // Inputs: ndm = pointer to NDM control block |
| // part_num = partition number in table |
| // |
| // Returns: 0 on success, -1 on error |
| // |
| int ndmErasePartition(NDM ndm, ui32 part_num) { |
| ui32 i, vpn; |
| |
| // If invalid partition number, ignore call. Return error. |
| if (part_num >= ndm->num_partitions) |
| return FsError(EINVAL); |
| |
| // Compute virtual page for first page on first block in partition. |
| vpn = ndm->partitions[part_num].first_block * ndm->pgs_per_blk; |
| |
| // Loop over all virtual blocks in partition, erasing each. |
| for (i = 0; i < ndm->partitions[part_num].num_blocks; ++i, vpn += ndm->pgs_per_blk) |
| if (ndmEraseBlock(vpn, ndm)) |
| return -1; |
| |
| // Return success. |
| return 0; |
| } |
| |
| // ndmDeletePartition: Clear a partition entry from partition table |
| // |
| // Inputs: ndm = pointer to NDM control block |
| // part_num = partition number in table |
| // |
| void ndmDeletePartition(CNDM ndm, ui32 part_num) { |
| // If invalid partition number, ignore. |
| if (part_num >= ndm->num_partitions) { |
| FsError(EINVAL); |
| return; |
| } |
| |
| // Clear the partition information in the table for this entry. |
| memset(&ndm->partitions[part_num], 0, sizeof(NDMPartition)); |
| } |
| |
| // ndmDeletePartitionTable: Delete partition table |
| // |
| // Input: ndm = pointer to NDM control block |
| // |
| void ndmDeletePartitionTable(NDM ndm) { |
| // If no partitions, simply return. |
| if (ndm->num_partitions == 0) |
| return; |
| |
| // Remove partition table. |
| FsFreeClear(&ndm->partitions); |
| ndm->num_partitions = 0; |
| } |
| |
| // ndmSavePartitionTable: Save partition table to flash |
| // |
| // Input: ndm = pointer to NDM control block |
| // |
| // Returns: 0 on success, -1 on error |
| // |
| int ndmSavePartitionTable(NDM ndm) { |
| ndm->xfr_tblk = (ui32)-1; |
| return ndmWrCtrl(ndm); |
| } //lint !e818 |
| |
| #if BBL_INSERT_INC |
| // ndmExtractBBL: Save running bad block list count and data |
| // |
| // Input: ndm = pointer to NDM control block |
| // |
| // Returns: -1 if error, else number of running bad blocks |
| // |
| int ndmExtractBBL(NDM ndm) { |
| uint size; |
| Pair *pair, *tmp, *last_pair; |
| |
| // Save running bad block count. Return if there are none. |
| ExtractedCnt = ndm->num_rbb; |
| if (ExtractedCnt == 0) |
| return 0; |
| |
| // Allocate memory for running bad block list and copy list to it. |
| size = sizeof(Pair) * ExtractedCnt; |
| ExtractedList = malloc(size); |
| if (ExtractedList == NULL) |
| return -1; |
| memcpy(ExtractedList, ndm->run_bad_blk, size); |
| #if BBL_INSERT_DEBUG |
| show_rbbl(ndm->run_bad_blk, ndm->num_rbb); |
| show_rbbl(ExtractedList, ExtractedCnt); |
| #endif |
| |
| // Simplify list, eliminating chains. |
| pair = ExtractedList; |
| for (last_pair = pair + ExtractedCnt - 1; pair < last_pair; ++pair) { |
| if (pair->val != (ui32)-1) { |
| for (tmp = pair + 1; tmp <= last_pair; ++tmp) { |
| if (tmp->key == pair->val) { |
| pair->val = tmp->val; |
| tmp->val = (ui32)-1; |
| } |
| } |
| } |
| } |
| #if BBL_INSERT_DEBUG |
| show_rbbl(ExtractedList, ExtractedCnt); |
| #endif |
| |
| // Return number of blocks that failed so far. |
| return ExtractedCnt; |
| } |
| |
| // ndmInsertBBL: Import saved running bad block list |
| // |
| // Input: ndm = pointer to NDM control block |
| // |
| // Returns: 0 if successful, else -1 on error |
| // |
| int ndmInsertBBL(NDM ndm) { |
| ui32 free_b, old_pn, new_pn, past_end; |
| Pair *last_pair, *pair; |
| int action, rc; |
| |
| // Just return 0 if there were no running bad blocks. |
| if (ExtractedCnt == 0) |
| return 0; |
| |
| // Loop to recover each bad block. |
| pair = ExtractedList; |
| for (last_pair = pair + ExtractedCnt - 1; pair <= last_pair; ++pair) { |
| #if BBL_INSERT_DEBUG |
| printf("pair %u: vblk/key=%u, pblk/val=%d\n", pair - ExtractedList, pair->key, pair->val); |
| #endif |
| |
| // Adjust bad block count. If too many, error. |
| if (++ndm->num_bad_blks > ndm->max_bad_blks) |
| return FsError(ENOSPC); |
| |
| // Get a free block to replace the bad virtual block. |
| free_b = get_free_virt_blk(ndm); |
| if (free_b == (ui32)-1) |
| return FsError(ENOSPC); |
| |
| // If physical block assigned, copy its contents to free_b. |
| if (pair->val != (ui32)-1) { |
| // Keep looping until successful. |
| for (;;) { |
| // Call driver to erase the free block. |
| rc = ndm->erase_block(free_b * ndm->pgs_per_blk, ndm->dev); |
| if (rc == -2) |
| return FsError(EIO); |
| |
| // Check if block erase command succeeded. |
| if (rc == 0) { |
| // Transfer data from old pool block to new pool block. |
| old_pn = pair->val * ndm->pgs_per_blk; |
| new_pn = free_b * ndm->pgs_per_blk; |
| past_end = new_pn + ndm->pgs_per_blk; |
| for (; new_pn < past_end; ++old_pn, ++new_pn) { |
| // Call driver to see if main and spare areas are erased. |
| rc = ndm->page_blank(old_pn, ndm->main_buf, ndm->spare_buf, ndm->dev); |
| |
| // If erased, skip page. Else if I/O error, return -1. |
| if (rc == TRUE) |
| continue; |
| else if (rc < 0) |
| return FsError(EIO); |
| |
| // Read main data. Return -1 if ECC or fatal error. |
| rc = ndm->read_page(old_pn, ndm->main_buf, ndm->spare_buf, ndm->dev); |
| if (rc < 0) |
| return FsError(EIO); |
| |
| // Read old spare data. Return -1 if ECC or fatal error. |
| rc = ndm->read_decode_spare(old_pn, ndm->spare_buf, ndm->dev); |
| if (rc < 0) |
| return FsError(EIO); |
| |
| // If volume page, request just spare bytes ECC. |
| if (RD32_LE(&ndm->spare_buf[5]) == (ui32)-1) |
| action = NDM_ECC; |
| |
| // Else map page, request spare ECC/validity check prep. |
| else |
| action = NDM_ECC_VAL; |
| |
| // Write page. Return -1 if fatal err. Break if block bad. |
| rc = ndm->write_page(new_pn, ndm->main_buf, ndm->spare_buf, action, |
| ndm->dev); |
| if (rc == -2) |
| return -1; |
| else if (rc) |
| break; |
| } |
| |
| // Break if block copy loop completed successfully. |
| if (new_pn == past_end) |
| break; |
| } |
| |
| // Adjust bad block count. If too many, error. |
| if (++ndm->num_bad_blks > ndm->max_bad_blks) |
| return FsError(ENOSPC); |
| |
| // Add the failed copy block to the running bad block list. |
| ndm->run_bad_blk[ndm->num_rbb].val = (ui32)-1; |
| ndm->run_bad_blk[ndm->num_rbb].key = free_b; |
| ++ndm->num_rbb; |
| |
| // Get a free block to replace the bad virtual block. |
| free_b = get_free_virt_blk(ndm); |
| if (free_b == (ui32)-1) |
| return FsError(ENOSPC); |
| } |
| } |
| |
| // Add this extracted pair to the current running bad block list. |
| ndm->run_bad_blk[ndm->num_rbb].val = free_b; |
| ndm->run_bad_blk[ndm->num_rbb].key = pair->key; |
| ++ndm->num_rbb; |
| } |
| #if BBL_INSERT_DEBUG |
| show_rbbl(ndm->run_bad_blk, ndm->num_rbb); |
| #endif |
| |
| // Free bad block list memory, zero count, and return success. |
| free(ExtractedList); |
| ExtractedCnt = 0; |
| return 0; |
| } |
| #endif // BBL_INSERT_INC |
| |
| #if INC_NDM_MLC |
| // ndmPastPrevPair: Starting at specified page number, find first page |
| // that has no earlier paired page |
| // |
| // Inputs: ndm = pointer to NDM control block |
| // pn = number of prospective next free page |
| // |
| // Returns: first of next pages whose write failures that can't |
| // corrupt previously written pages on same block or -1 |
| // if no higher page on block past all previous pairs |
| // |
| ui32 ndmPastPrevPair(CNDM ndm, ui32 pn) { |
| ui32 n, po = pn % ndm->pgs_per_blk; |
| |
| // First page on block has no previous pairs to skip. |
| if (po == 0) |
| return pn; |
| |
| // If last page on block is not past previous pairs, no page is. |
| n = ndm->pgs_per_blk - 1; |
| if (ndm->pair_offset(n, ndm->dev) < po) |
| return (ui32)-1; |
| |
| // Move backward to find first page whose pair is at a lower offset |
| // than the input page. |
| for (;;) { |
| if (ndm->pair_offset(--n, ndm->dev) < po) |
| break; |
| PfAssert(n); |
| } |
| |
| // Return the page that is one page offset higher. |
| return (pn / ndm->pgs_per_blk) * ndm->pgs_per_blk + n + 1; |
| } |
| #endif |
| #endif // INC_NDM |