| // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause |
| /* |
| * Copyright (C) 2021 MediaTek Inc. All Rights Reserved. |
| * |
| * Author: Weijie Gao <weijie.gao@mediatek.com> |
| */ |
| |
| #include "nmbm-private.h" |
| |
| #include "nmbm-debug.h" |
| |
| #define NMBM_VER_MAJOR 1 |
| #define NMBM_VER_MINOR 0 |
| #define NMBM_VER NMBM_VERSION_MAKE(NMBM_VER_MAJOR, \ |
| NMBM_VER_MINOR) |
| |
| #define NMBM_ALIGN(v, a) (((v) + (a) - 1) & ~((a) - 1)) |
| |
| /*****************************************************************************/ |
| /* Logging related functions */ |
| /*****************************************************************************/ |
| |
| /* |
| * nmbm_log_lower - Print log using OS specific routine |
| * @nld: NMBM lower device structure |
| * @level: log level |
| * @fmt: format string |
| */ |
| static void nmbm_log_lower(struct nmbm_lower_device *nld, |
| enum nmbm_log_category level, const char *fmt, ...) |
| { |
| va_list ap; |
| |
| if (!nld->logprint) |
| return; |
| |
| va_start(ap, fmt); |
| nld->logprint(nld->arg, level, fmt, ap); |
| va_end(ap); |
| } |
| |
| /* |
| * nmbm_log - Print log using OS specific routine |
| * @ni: NMBM instance structure |
| * @level: log level |
| * @fmt: format string |
| */ |
| static void nmbm_log(struct nmbm_instance *ni, enum nmbm_log_category level, |
| const char *fmt, ...) |
| { |
| va_list ap; |
| |
| if (!ni) |
| return; |
| |
| if (!ni->lower.logprint || level < ni->log_display_level) |
| return; |
| |
| va_start(ap, fmt); |
| ni->lower.logprint(ni->lower.arg, level, fmt, ap); |
| va_end(ap); |
| } |
| |
| /* |
| * nmbm_set_log_level - Set log display level |
| * @ni: NMBM instance structure |
| * @level: log display level |
| */ |
| enum nmbm_log_category nmbm_set_log_level(struct nmbm_instance *ni, |
| enum nmbm_log_category level) |
| { |
| enum nmbm_log_category old; |
| |
| if (!ni) |
| return __NMBM_LOG_MAX; |
| |
| old = ni->log_display_level; |
| ni->log_display_level = level; |
| return old; |
| } |
| |
| /* |
| * nlog_table_creation - Print log of table creation event |
| * @ni: NMBM instance structure |
| * @main_table: whether the table is main info table |
| * @start_ba: start block address of the table |
| * @end_ba: block address after the end of the table |
| */ |
| static void nlog_table_creation(struct nmbm_instance *ni, bool main_table, |
| uint32_t start_ba, uint32_t end_ba) |
| { |
| if (start_ba == end_ba - 1) |
| nlog_info(ni, "%s info table has been written to block %u\n", |
| main_table ? "Main" : "Backup", start_ba); |
| else |
| nlog_info(ni, "%s info table has been written to block %u-%u\n", |
| main_table ? "Main" : "Backup", start_ba, end_ba - 1); |
| |
| nmbm_mark_block_color_info_table(ni, start_ba, end_ba - 1); |
| } |
| |
| /* |
| * nlog_table_update - Print log of table update event |
| * @ni: NMBM instance structure |
| * @main_table: whether the table is main info table |
| * @start_ba: start block address of the table |
| * @end_ba: block address after the end of the table |
| */ |
| static void nlog_table_update(struct nmbm_instance *ni, bool main_table, |
| uint32_t start_ba, uint32_t end_ba) |
| { |
| if (start_ba == end_ba - 1) |
| nlog_debug(ni, "%s info table has been updated in block %u\n", |
| main_table ? "Main" : "Backup", start_ba); |
| else |
| nlog_debug(ni, "%s info table has been updated in block %u-%u\n", |
| main_table ? "Main" : "Backup", start_ba, end_ba - 1); |
| |
| nmbm_mark_block_color_info_table(ni, start_ba, end_ba - 1); |
| } |
| |
| /* |
| * nlog_table_found - Print log of table found event |
| * @ni: NMBM instance structure |
| * @first_table: whether the table is first found info table |
| * @write_count: write count of the info table |
| * @start_ba: start block address of the table |
| * @end_ba: block address after the end of the table |
| */ |
| static void nlog_table_found(struct nmbm_instance *ni, bool first_table, |
| uint32_t write_count, uint32_t start_ba, |
| uint32_t end_ba) |
| { |
| if (start_ba == end_ba - 1) |
| nlog_info(ni, "%s info table with writecount %u found in block %u\n", |
| first_table ? "First" : "Second", write_count, |
| start_ba); |
| else |
| nlog_info(ni, "%s info table with writecount %u found in block %u-%u\n", |
| first_table ? "First" : "Second", write_count, |
| start_ba, end_ba - 1); |
| |
| nmbm_mark_block_color_info_table(ni, start_ba, end_ba - 1); |
| } |
| |
| /*****************************************************************************/ |
| /* Address conversion functions */ |
| /*****************************************************************************/ |
| |
| /* |
| * addr2ba - Convert a linear address to block address |
| * @ni: NMBM instance structure |
| * @addr: Linear address |
| */ |
| static uint32_t addr2ba(struct nmbm_instance *ni, uint64_t addr) |
| { |
| return addr >> ni->erasesize_shift; |
| } |
| |
| /* |
| * ba2addr - Convert a block address to linear address |
| * @ni: NMBM instance structure |
| * @ba: Block address |
| */ |
| static uint64_t ba2addr(struct nmbm_instance *ni, uint32_t ba) |
| { |
| return (uint64_t)ba << ni->erasesize_shift; |
| } |
| /* |
| * size2blk - Get minimum required blocks for storing specific size of data |
| * @ni: NMBM instance structure |
| * @size: size for storing |
| */ |
| static uint32_t size2blk(struct nmbm_instance *ni, uint64_t size) |
| { |
| return (size + ni->lower.erasesize - 1) >> ni->erasesize_shift; |
| } |
| |
| /*****************************************************************************/ |
| /* High level NAND chip APIs */ |
| /*****************************************************************************/ |
| |
| /* |
| * nmbm_reset_chip - Reset NAND device |
| * @nld: Lower NAND chip structure |
| */ |
| static void nmbm_reset_chip(struct nmbm_instance *ni) |
| { |
| if (ni->lower.reset_chip) |
| ni->lower.reset_chip(ni->lower.arg); |
| } |
| |
| /* |
| * nmbm_read_phys_page - Read page with retry |
| * @ni: NMBM instance structure |
| * @addr: linear address where the data will be read from |
| * @data: the main data to be read |
| * @oob: the oob data to be read |
| * @mode: mode for processing oob data |
| * |
| * Read a page for at most NMBM_TRY_COUNT times. |
| * |
| * Return 0 for success, positive value for ecc error, |
| * negative value for other errors |
| */ |
| static int nmbm_read_phys_page(struct nmbm_instance *ni, uint64_t addr, |
| void *data, void *oob, enum nmbm_oob_mode mode) |
| { |
| int tries, ret; |
| |
| for (tries = 0; tries < NMBM_TRY_COUNT; tries++) { |
| ret = ni->lower.read_page(ni->lower.arg, addr, data, oob, mode); |
| if (!ret) |
| return 0; |
| |
| nmbm_reset_chip(ni); |
| } |
| |
| if (ret < 0) |
| nlog_err(ni, "Page read failed at address 0x%08llx\n", addr); |
| |
| return ret; |
| } |
| |
| /* |
| * nmbm_write_phys_page - Write page with retry |
| * @ni: NMBM instance structure |
| * @addr: linear address where the data will be written to |
| * @data: the main data to be written |
| * @oob: the oob data to be written |
| * @mode: mode for processing oob data |
| * |
| * Write a page for at most NMBM_TRY_COUNT times. |
| */ |
| static bool nmbm_write_phys_page(struct nmbm_instance *ni, uint64_t addr, |
| const void *data, const void *oob, |
| enum nmbm_oob_mode mode) |
| { |
| int tries, ret; |
| |
| for (tries = 0; tries < NMBM_TRY_COUNT; tries++) { |
| ret = ni->lower.write_page(ni->lower.arg, addr, data, oob, mode); |
| if (!ret) |
| return true; |
| |
| nmbm_reset_chip(ni); |
| } |
| |
| nlog_err(ni, "Page write failed at address 0x%08llx\n", addr); |
| |
| return false; |
| } |
| |
| /* |
| * nmbm_erase_phys_block - Erase a block with retry |
| * @ni: NMBM instance structure |
| * @addr: Linear address |
| * |
| * Erase a block for at most NMBM_TRY_COUNT times. |
| */ |
| static bool nmbm_erase_phys_block(struct nmbm_instance *ni, uint64_t addr) |
| { |
| int tries, ret; |
| |
| for (tries = 0; tries < NMBM_TRY_COUNT; tries++) { |
| ret = ni->lower.erase_block(ni->lower.arg, addr); |
| if (!ret) |
| return true; |
| |
| nmbm_reset_chip(ni); |
| } |
| |
| nlog_err(ni, "Block erasure failed at address 0x%08llx\n", addr); |
| |
| return false; |
| } |
| |
| /* |
| * nmbm_check_bad_phys_block - Check whether a block is marked bad in OOB |
| * @ni: NMBM instance structure |
| * @ba: block address |
| */ |
| static bool nmbm_check_bad_phys_block(struct nmbm_instance *ni, uint32_t ba) |
| { |
| uint64_t addr = ba2addr(ni, ba); |
| int ret; |
| |
| if (ni->lower.is_bad_block) |
| return ni->lower.is_bad_block(ni->lower.arg, addr); |
| |
| /* Treat ECC error as read success */ |
| ret = nmbm_read_phys_page(ni, addr, NULL, |
| ni->page_cache + ni->lower.writesize, |
| NMBM_MODE_PLACE_OOB); |
| if (ret < 0) |
| return true; |
| |
| return ni->page_cache[ni->lower.writesize] != 0xff; |
| } |
| |
| /* |
| * nmbm_mark_phys_bad_block - Mark a block bad |
| * @ni: NMBM instance structure |
| * @addr: Linear address |
| */ |
| static int nmbm_mark_phys_bad_block(struct nmbm_instance *ni, uint32_t ba) |
| { |
| uint64_t addr = ba2addr(ni, ba); |
| enum nmbm_log_category level; |
| uint32_t off; |
| |
| nlog_info(ni, "Block %u [0x%08llx] will be marked bad\n", ba, addr); |
| |
| if (ni->lower.mark_bad_block) |
| return ni->lower.mark_bad_block(ni->lower.arg, addr); |
| |
| /* Whole page set to 0x00 */ |
| memset(ni->page_cache, 0, ni->rawpage_size); |
| |
| /* Write to all pages within this block, disable all errors */ |
| level = nmbm_set_log_level(ni, __NMBM_LOG_MAX); |
| |
| for (off = 0; off < ni->lower.erasesize; off += ni->lower.writesize) { |
| nmbm_write_phys_page(ni, addr + off, ni->page_cache, |
| ni->page_cache + ni->lower.writesize, |
| NMBM_MODE_RAW); |
| } |
| |
| nmbm_set_log_level(ni, level); |
| |
| return 0; |
| } |
| |
| /*****************************************************************************/ |
| /* NMBM related functions */ |
| /*****************************************************************************/ |
| |
| /* |
| * nmbm_check_header - Check whether a NMBM structure is valid |
| * @data: pointer to a NMBM structure with a NMBM header at beginning |
| * @size: Size of the buffer pointed by @header |
| * |
| * The size of the NMBM structure may be larger than NMBM header, |
| * e.g. block mapping table and block state table. |
| */ |
| static bool nmbm_check_header(const void *data, uint32_t size) |
| { |
| const struct nmbm_header *header = data; |
| struct nmbm_header nhdr; |
| uint32_t new_checksum; |
| |
| /* |
| * Make sure expected structure size is equal or smaller than |
| * buffer size. |
| */ |
| if (header->size > size) |
| return false; |
| |
| memcpy(&nhdr, data, sizeof(nhdr)); |
| |
| nhdr.checksum = 0; |
| new_checksum = nmbm_crc32(0, &nhdr, sizeof(nhdr)); |
| if (header->size > sizeof(nhdr)) |
| new_checksum = nmbm_crc32(new_checksum, |
| (const uint8_t *)data + sizeof(nhdr), |
| header->size - sizeof(nhdr)); |
| |
| if (header->checksum != new_checksum) |
| return false; |
| |
| return true; |
| } |
| |
| /* |
| * nmbm_update_checksum - Update checksum of a NMBM structure |
| * @header: pointer to a NMBM structure with a NMBM header at beginning |
| * |
| * The size of the NMBM structure must be specified by @header->size |
| */ |
| static void nmbm_update_checksum(struct nmbm_header *header) |
| { |
| header->checksum = 0; |
| header->checksum = nmbm_crc32(0, header, header->size); |
| } |
| |
| /* |
| * nmbm_get_spare_block_count - Calculate number of blocks should be reserved |
| * @block_count: number of blocks of data |
| * |
| * Calculate number of blocks should be reserved for data |
| */ |
| static uint32_t nmbm_get_spare_block_count(uint32_t block_count) |
| { |
| uint32_t val; |
| |
| val = (block_count + NMBM_SPARE_BLOCK_DIV / 2) / NMBM_SPARE_BLOCK_DIV; |
| val *= NMBM_SPARE_BLOCK_MULTI; |
| |
| if (val < NMBM_SPARE_BLOCK_MIN) |
| val = NMBM_SPARE_BLOCK_MIN; |
| |
| return val; |
| } |
| |
| /* |
| * nmbm_get_block_state_raw - Get state of a block from raw block state table |
| * @block_state: pointer to raw block state table (bitmap) |
| * @ba: block address |
| */ |
| static uint32_t nmbm_get_block_state_raw(nmbm_bitmap_t *block_state, |
| uint32_t ba) |
| { |
| uint32_t unit, shift; |
| |
| unit = ba / NMBM_BITMAP_BLOCKS_PER_UNIT; |
| shift = (ba % NMBM_BITMAP_BLOCKS_PER_UNIT) * NMBM_BITMAP_BITS_PER_BLOCK; |
| |
| return (block_state[unit] >> shift) & BLOCK_ST_MASK; |
| } |
| |
| /* |
| * nmbm_get_block_state - Get state of a block from block state table |
| * @ni: NMBM instance structure |
| * @ba: block address |
| */ |
| static uint32_t nmbm_get_block_state(struct nmbm_instance *ni, uint32_t ba) |
| { |
| return nmbm_get_block_state_raw(ni->block_state, ba); |
| } |
| |
| /* |
| * nmbm_set_block_state - Set state of a block to block state table |
| * @ni: NMBM instance structure |
| * @ba: block address |
| * @state: block state |
| * |
| * Set state of a block. If the block state changed, ni->block_state_changed |
| * will be increased. |
| */ |
| static bool nmbm_set_block_state(struct nmbm_instance *ni, uint32_t ba, |
| uint32_t state) |
| { |
| uint32_t unit, shift, orig; |
| nmbm_bitmap_t uv; |
| |
| unit = ba / NMBM_BITMAP_BLOCKS_PER_UNIT; |
| shift = (ba % NMBM_BITMAP_BLOCKS_PER_UNIT) * NMBM_BITMAP_BITS_PER_BLOCK; |
| |
| orig = (ni->block_state[unit] >> shift) & BLOCK_ST_MASK; |
| state &= BLOCK_ST_MASK; |
| |
| uv = ni->block_state[unit] & (~(BLOCK_ST_MASK << shift)); |
| uv |= state << shift; |
| ni->block_state[unit] = uv; |
| |
| if (state == BLOCK_ST_BAD) |
| nmbm_mark_block_color_bad(ni, ba); |
| |
| if (orig != state) { |
| ni->block_state_changed++; |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /* |
| * nmbm_block_walk_asc - Skip specified number of good blocks, ascending addr. |
| * @ni: NMBM instance structure |
| * @ba: start physical block address |
| * @nba: return physical block address after walk |
| * @count: number of good blocks to be skipped |
| * @limit: highest block address allowed for walking |
| * |
| * Start from @ba, skipping any bad blocks, counting @count good blocks, and |
| * return the next good block address. |
| * |
| * If no enough good blocks counted while @limit reached, false will be returned. |
| * |
| * If @count == 0, nearest good block address will be returned. |
| * @limit is not counted in walking. |
| */ |
| static bool nmbm_block_walk_asc(struct nmbm_instance *ni, uint32_t ba, |
| uint32_t *nba, uint32_t count, |
| uint32_t limit) |
| { |
| int32_t nblock = count; |
| |
| if (limit >= ni->block_count) |
| limit = ni->block_count - 1; |
| |
| while (ba < limit) { |
| if (nmbm_get_block_state(ni, ba) == BLOCK_ST_GOOD) |
| nblock--; |
| |
| if (nblock < 0) { |
| *nba = ba; |
| return true; |
| } |
| |
| ba++; |
| } |
| |
| return false; |
| } |
| |
| /* |
| * nmbm_block_walk_desc - Skip specified number of good blocks, descending addr |
| * @ni: NMBM instance structure |
| * @ba: start physical block address |
| * @nba: return physical block address after walk |
| * @count: number of good blocks to be skipped |
| * @limit: lowest block address allowed for walking |
| * |
| * Start from @ba, skipping any bad blocks, counting @count good blocks, and |
| * return the next good block address. |
| * |
| * If no enough good blocks counted while @limit reached, false will be returned. |
| * |
| * If @count == 0, nearest good block address will be returned. |
| * @limit is not counted in walking. |
| */ |
| static bool nmbm_block_walk_desc(struct nmbm_instance *ni, uint32_t ba, |
| uint32_t *nba, uint32_t count, uint32_t limit) |
| { |
| int32_t nblock = count; |
| |
| if (limit >= ni->block_count) |
| limit = ni->block_count - 1; |
| |
| while (ba > limit) { |
| if (nmbm_get_block_state(ni, ba) == BLOCK_ST_GOOD) |
| nblock--; |
| |
| if (nblock < 0) { |
| *nba = ba; |
| return true; |
| } |
| |
| ba--; |
| } |
| |
| return false; |
| } |
| |
| /* |
| * nmbm_block_walk - Skip specified number of good blocks from curr. block addr |
| * @ni: NMBM instance structure |
| * @ascending: whether to walk ascending |
| * @ba: start physical block address |
| * @nba: return physical block address after walk |
| * @count: number of good blocks to be skipped |
| * @limit: highest/lowest block address allowed for walking |
| * |
| * Start from @ba, skipping any bad blocks, counting @count good blocks, and |
| * return the next good block address. |
| * |
| * If no enough good blocks counted while @limit reached, false will be returned. |
| * |
| * If @count == 0, nearest good block address will be returned. |
| * @limit can be set to negative if no limit required. |
| * @limit is not counted in walking. |
| */ |
| static bool nmbm_block_walk(struct nmbm_instance *ni, bool ascending, |
| uint32_t ba, uint32_t *nba, int32_t count, |
| int32_t limit) |
| { |
| if (ascending) |
| return nmbm_block_walk_asc(ni, ba, nba, count, limit); |
| |
| return nmbm_block_walk_desc(ni, ba, nba, count, limit); |
| } |
| |
| /* |
| * nmbm_scan_badblocks - Scan and record all bad blocks |
| * @ni: NMBM instance structure |
| * |
| * Scan the entire lower NAND chip and record all bad blocks in to block state |
| * table. |
| */ |
| static void nmbm_scan_badblocks(struct nmbm_instance *ni) |
| { |
| uint32_t ba; |
| |
| for (ba = 0; ba < ni->block_count; ba++) { |
| if (nmbm_check_bad_phys_block(ni, ba)) { |
| nmbm_set_block_state(ni, ba, BLOCK_ST_BAD); |
| nlog_info(ni, "Bad block %u [0x%08llx]\n", ba, |
| ba2addr(ni, ba)); |
| } |
| } |
| } |
| |
| /* |
| * nmbm_build_mapping_table - Build initial block mapping table |
| * @ni: NMBM instance structure |
| * |
| * The initial mapping table will be compatible with the stratage of |
| * factory production. |
| */ |
| static void nmbm_build_mapping_table(struct nmbm_instance *ni) |
| { |
| uint32_t pb, lb; |
| |
| for (pb = 0, lb = 0; pb < ni->mgmt_start_ba; pb++) { |
| if (nmbm_get_block_state(ni, pb) == BLOCK_ST_BAD) |
| continue; |
| |
| /* Always map to the next good block */ |
| ni->block_mapping[lb++] = pb; |
| } |
| |
| ni->data_block_count = lb; |
| |
| /* Unusable/Management blocks */ |
| for (pb = lb; pb < ni->block_count; pb++) |
| ni->block_mapping[pb] = -1; |
| } |
| |
| /* |
| * nmbm_erase_range - Erase a range of blocks |
| * @ni: NMBM instance structure |
| * @ba: block address where the erasure will start |
| * @limit: top block address allowed for erasure |
| * |
| * Erase blocks within the specific range. Newly-found bad blocks will be |
| * marked. |
| * |
| * @limit is not counted into the allowed erasure address. |
| */ |
| static void nmbm_erase_range(struct nmbm_instance *ni, uint32_t ba, |
| uint32_t limit) |
| { |
| bool success; |
| |
| while (ba < limit) { |
| WATCHDOG_RESET(); |
| |
| if (nmbm_get_block_state(ni, ba) != BLOCK_ST_GOOD) |
| goto next_block; |
| |
| /* Insurance to detect unexpected bad block marked by user */ |
| if (nmbm_check_bad_phys_block(ni, ba)) { |
| nmbm_set_block_state(ni, ba, BLOCK_ST_BAD); |
| goto next_block; |
| } |
| |
| success = nmbm_erase_phys_block(ni, ba2addr(ni, ba)); |
| if (success) |
| goto next_block; |
| |
| nmbm_mark_phys_bad_block(ni, ba); |
| nmbm_set_block_state(ni, ba, BLOCK_ST_BAD); |
| |
| next_block: |
| ba++; |
| } |
| } |
| |
| /* |
| * nmbm_write_repeated_data - Write critical data to a block with retry |
| * @ni: NMBM instance structure |
| * @ba: block address where the data will be written to |
| * @data: the data to be written |
| * @size: size of the data |
| * |
| * Write data to every page of the block. Success only if all pages within |
| * this block have been successfully written. |
| * |
| * Make sure data size is not bigger than one page. |
| * |
| * This function will write and verify every page for at most |
| * NMBM_TRY_COUNT times. |
| */ |
| static bool nmbm_write_repeated_data(struct nmbm_instance *ni, uint32_t ba, |
| const void *data, uint32_t size) |
| { |
| uint64_t addr, off; |
| bool success; |
| int ret; |
| |
| if (size > ni->lower.writesize) |
| return false; |
| |
| addr = ba2addr(ni, ba); |
| |
| for (off = 0; off < ni->lower.erasesize; off += ni->lower.writesize) { |
| WATCHDOG_RESET(); |
| |
| /* Prepare page data. fill 0xff to unused region */ |
| memcpy(ni->page_cache, data, size); |
| memset(ni->page_cache + size, 0xff, ni->rawpage_size - size); |
| |
| success = nmbm_write_phys_page(ni, addr + off, ni->page_cache, |
| NULL, NMBM_MODE_PLACE_OOB); |
| if (!success) |
| return false; |
| |
| /* Verify the data just written. ECC error indicates failure */ |
| ret = nmbm_read_phys_page(ni, addr + off, ni->page_cache, NULL, |
| NMBM_MODE_PLACE_OOB); |
| if (ret) |
| return false; |
| |
| if (memcmp(ni->page_cache, data, size)) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /* |
| * nmbm_write_signature - Write signature to NAND chip |
| * @ni: NMBM instance structure |
| * @limit: top block address allowed for writing |
| * @signature: the signature to be written |
| * @signature_ba: the actual block address where signature is written to |
| * |
| * Write signature within a specific range, from chip bottom to limit. |
| * At most one block will be written. |
| * |
| * @limit is not counted into the allowed write address. |
| */ |
| static bool nmbm_write_signature(struct nmbm_instance *ni, uint32_t limit, |
| const struct nmbm_signature *signature, |
| uint32_t *signature_ba) |
| { |
| uint32_t ba = ni->block_count - 1; |
| bool success; |
| |
| while (ba > limit) { |
| WATCHDOG_RESET(); |
| |
| if (nmbm_get_block_state(ni, ba) != BLOCK_ST_GOOD) |
| goto next_block; |
| |
| /* Insurance to detect unexpected bad block marked by user */ |
| if (nmbm_check_bad_phys_block(ni, ba)) { |
| nmbm_set_block_state(ni, ba, BLOCK_ST_BAD); |
| goto next_block; |
| } |
| |
| success = nmbm_erase_phys_block(ni, ba2addr(ni, ba)); |
| if (!success) |
| goto skip_bad_block; |
| |
| success = nmbm_write_repeated_data(ni, ba, signature, |
| sizeof(*signature)); |
| if (success) { |
| *signature_ba = ba; |
| return true; |
| } |
| |
| skip_bad_block: |
| nmbm_mark_phys_bad_block(ni, ba); |
| nmbm_set_block_state(ni, ba, BLOCK_ST_BAD); |
| |
| next_block: |
| ba--; |
| }; |
| |
| return false; |
| } |
| |
| /* |
| * nmbn_read_data - Read data |
| * @ni: NMBM instance structure |
| * @addr: linear address where the data will be read from |
| * @data: the data to be read |
| * @size: the size of data |
| * |
| * Read data range. |
| * Every page will be tried for at most NMBM_TRY_COUNT times. |
| * |
| * Return 0 for success, positive value for ecc error, |
| * negative value for other errors |
| */ |
| static int nmbn_read_data(struct nmbm_instance *ni, uint64_t addr, void *data, |
| uint32_t size) |
| { |
| uint64_t off = addr; |
| uint8_t *ptr = data; |
| uint32_t sizeremain = size, chunksize, leading; |
| int ret; |
| |
| while (sizeremain) { |
| WATCHDOG_RESET(); |
| |
| leading = off & ni->writesize_mask; |
| chunksize = ni->lower.writesize - leading; |
| if (chunksize > sizeremain) |
| chunksize = sizeremain; |
| |
| if (chunksize == ni->lower.writesize) { |
| ret = nmbm_read_phys_page(ni, off - leading, ptr, NULL, |
| NMBM_MODE_PLACE_OOB); |
| if (ret) |
| return ret; |
| } else { |
| ret = nmbm_read_phys_page(ni, off - leading, |
| ni->page_cache, NULL, |
| NMBM_MODE_PLACE_OOB); |
| if (ret) |
| return ret; |
| |
| memcpy(ptr, ni->page_cache + leading, chunksize); |
| } |
| |
| off += chunksize; |
| ptr += chunksize; |
| sizeremain -= chunksize; |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * nmbn_write_verify_data - Write data with validation |
| * @ni: NMBM instance structure |
| * @addr: linear address where the data will be written to |
| * @data: the data to be written |
| * @size: the size of data |
| * |
| * Write data and verify. |
| * Every page will be tried for at most NMBM_TRY_COUNT times. |
| */ |
| static bool nmbn_write_verify_data(struct nmbm_instance *ni, uint64_t addr, |
| const void *data, uint32_t size) |
| { |
| uint64_t off = addr; |
| const uint8_t *ptr = data; |
| uint32_t sizeremain = size, chunksize, leading; |
| bool success; |
| int ret; |
| |
| while (sizeremain) { |
| WATCHDOG_RESET(); |
| |
| leading = off & ni->writesize_mask; |
| chunksize = ni->lower.writesize - leading; |
| if (chunksize > sizeremain) |
| chunksize = sizeremain; |
| |
| /* Prepare page data. fill 0xff to unused region */ |
| memset(ni->page_cache, 0xff, ni->rawpage_size); |
| memcpy(ni->page_cache + leading, ptr, chunksize); |
| |
| success = nmbm_write_phys_page(ni, off - leading, |
| ni->page_cache, NULL, |
| NMBM_MODE_PLACE_OOB); |
| if (!success) |
| return false; |
| |
| /* Verify the data just written. ECC error indicates failure */ |
| ret = nmbm_read_phys_page(ni, off - leading, ni->page_cache, |
| NULL, NMBM_MODE_PLACE_OOB); |
| if (ret) |
| return false; |
| |
| if (memcmp(ni->page_cache + leading, ptr, chunksize)) |
| return false; |
| |
| off += chunksize; |
| ptr += chunksize; |
| sizeremain -= chunksize; |
| } |
| |
| return true; |
| } |
| |
| /* |
| * nmbm_write_mgmt_range - Write management data into NAND within a range |
| * @ni: NMBM instance structure |
| * @addr: preferred start block address for writing |
| * @limit: highest block address allowed for writing |
| * @data: the data to be written |
| * @size: the size of data |
| * @actual_start_ba: actual start block address of data |
| * @actual_end_ba: block address after the end of data |
| * |
| * @limit is not counted into the allowed write address. |
| */ |
| static bool nmbm_write_mgmt_range(struct nmbm_instance *ni, uint32_t ba, |
| uint32_t limit, const void *data, |
| uint32_t size, uint32_t *actual_start_ba, |
| uint32_t *actual_end_ba) |
| { |
| const uint8_t *ptr = data; |
| uint32_t sizeremain = size, chunksize; |
| bool success; |
| |
| while (sizeremain && ba < limit) { |
| WATCHDOG_RESET(); |
| |
| chunksize = sizeremain; |
| if (chunksize > ni->lower.erasesize) |
| chunksize = ni->lower.erasesize; |
| |
| if (nmbm_get_block_state(ni, ba) != BLOCK_ST_GOOD) |
| goto next_block; |
| |
| /* Insurance to detect unexpected bad block marked by user */ |
| if (nmbm_check_bad_phys_block(ni, ba)) { |
| nmbm_set_block_state(ni, ba, BLOCK_ST_BAD); |
| goto next_block; |
| } |
| |
| success = nmbm_erase_phys_block(ni, ba2addr(ni, ba)); |
| if (!success) |
| goto skip_bad_block; |
| |
| success = nmbn_write_verify_data(ni, ba2addr(ni, ba), ptr, |
| chunksize); |
| if (!success) |
| goto skip_bad_block; |
| |
| if (sizeremain == size) |
| *actual_start_ba = ba; |
| |
| ptr += chunksize; |
| sizeremain -= chunksize; |
| |
| goto next_block; |
| |
| skip_bad_block: |
| nmbm_mark_phys_bad_block(ni, ba); |
| nmbm_set_block_state(ni, ba, BLOCK_ST_BAD); |
| |
| next_block: |
| ba++; |
| } |
| |
| if (sizeremain) |
| return false; |
| |
| *actual_end_ba = ba; |
| |
| return true; |
| } |
| |
| /* |
| * nmbm_generate_info_table_cache - Generate info table cache data |
| * @ni: NMBM instance structure |
| * |
| * Generate info table cache data to be written into flash. |
| */ |
| static bool nmbm_generate_info_table_cache(struct nmbm_instance *ni) |
| { |
| bool changed = false; |
| |
| memset(ni->info_table_cache, 0xff, ni->info_table_size); |
| |
| memcpy(ni->info_table_cache + ni->info_table.state_table_off, |
| ni->block_state, ni->state_table_size); |
| |
| memcpy(ni->info_table_cache + ni->info_table.mapping_table_off, |
| ni->block_mapping, ni->mapping_table_size); |
| |
| ni->info_table.header.magic = NMBM_MAGIC_INFO_TABLE; |
| ni->info_table.header.version = NMBM_VER; |
| ni->info_table.header.size = ni->info_table_size; |
| |
| if (ni->block_state_changed || ni->block_mapping_changed) { |
| ni->info_table.write_count++; |
| changed = true; |
| } |
| |
| memcpy(ni->info_table_cache, &ni->info_table, sizeof(ni->info_table)); |
| |
| nmbm_update_checksum((struct nmbm_header *)ni->info_table_cache); |
| |
| return changed; |
| } |
| |
| /* |
| * nmbm_write_info_table - Write info table into NAND within a range |
| * @ni: NMBM instance structure |
| * @ba: preferred start block address for writing |
| * @limit: highest block address allowed for writing |
| * @actual_start_ba: actual start block address of info table |
| * @actual_end_ba: block address after the end of info table |
| * |
| * @limit is counted into the allowed write address. |
| */ |
| static bool nmbm_write_info_table(struct nmbm_instance *ni, uint32_t ba, |
| uint32_t limit, uint32_t *actual_start_ba, |
| uint32_t *actual_end_ba) |
| { |
| return nmbm_write_mgmt_range(ni, ba, limit, ni->info_table_cache, |
| ni->info_table_size, actual_start_ba, |
| actual_end_ba); |
| } |
| |
| /* |
| * nmbm_mark_tables_clean - Mark info table `clean' |
| * @ni: NMBM instance structure |
| */ |
| static void nmbm_mark_tables_clean(struct nmbm_instance *ni) |
| { |
| ni->block_state_changed = 0; |
| ni->block_mapping_changed = 0; |
| } |
| |
| /* |
| * nmbm_try_reserve_blocks - Reserve blocks with compromisation |
| * @ni: NMBM instance structure |
| * @ba: start physical block address |
| * @nba: return physical block address after reservation |
| * @count: number of good blocks to be skipped |
| * @min_count: minimum number of good blocks to be skipped |
| * @limit: highest/lowest block address allowed for walking |
| * |
| * Reserve specific blocks. If failed, try to reserve as many as possible. |
| */ |
| static bool nmbm_try_reserve_blocks(struct nmbm_instance *ni, uint32_t ba, |
| uint32_t *nba, uint32_t count, |
| int32_t min_count, int32_t limit) |
| { |
| int32_t nblocks = count; |
| bool success; |
| |
| while (nblocks >= min_count) { |
| success = nmbm_block_walk(ni, true, ba, nba, nblocks, limit); |
| if (success) |
| return true; |
| |
| nblocks--; |
| } |
| |
| return false; |
| } |
| |
| /* |
| * nmbm_rebuild_info_table - Build main & backup info table from scratch |
| * @ni: NMBM instance structure |
| * @allow_no_gap: allow no spare blocks between two tables |
| */ |
| static bool nmbm_rebuild_info_table(struct nmbm_instance *ni) |
| { |
| uint32_t table_start_ba, table_end_ba, next_start_ba; |
| uint32_t main_table_end_ba; |
| bool success; |
| |
| /* Set initial value */ |
| ni->main_table_ba = 0; |
| ni->backup_table_ba = 0; |
| ni->mapping_blocks_ba = ni->mapping_blocks_top_ba; |
| |
| /* Write main table */ |
| success = nmbm_write_info_table(ni, ni->mgmt_start_ba, |
| ni->mapping_blocks_top_ba, |
| &table_start_ba, &table_end_ba); |
| if (!success) { |
| /* Failed to write main table, data will be lost */ |
| nlog_emerg(ni, "Unable to write at least one info table!\n"); |
| nlog_emerg(ni, "Please save your data before power off!\n"); |
| ni->protected = 1; |
| return false; |
| } |
| |
| /* Main info table is successfully written, record its offset */ |
| ni->main_table_ba = table_start_ba; |
| main_table_end_ba = table_end_ba; |
| |
| /* Adjust mapping_blocks_ba */ |
| ni->mapping_blocks_ba = table_end_ba; |
| |
| nmbm_mark_tables_clean(ni); |
| |
| nlog_table_creation(ni, true, table_start_ba, table_end_ba); |
| |
| /* Reserve spare blocks for main info table. */ |
| success = nmbm_try_reserve_blocks(ni, table_end_ba, |
| &next_start_ba, |
| ni->info_table_spare_blocks, 0, |
| ni->mapping_blocks_top_ba - |
| size2blk(ni, ni->info_table_size)); |
| if (!success) { |
| /* There is no spare block. */ |
| nlog_debug(ni, "No room for backup info table\n"); |
| return true; |
| } |
| |
| /* Write backup info table. */ |
| success = nmbm_write_info_table(ni, next_start_ba, |
| ni->mapping_blocks_top_ba, |
| &table_start_ba, &table_end_ba); |
| if (!success) { |
| /* There is no enough blocks for backup table. */ |
| nlog_debug(ni, "No room for backup info table\n"); |
| return true; |
| } |
| |
| /* Backup table is successfully written, record its offset */ |
| ni->backup_table_ba = table_start_ba; |
| |
| /* Adjust mapping_blocks_off */ |
| ni->mapping_blocks_ba = table_end_ba; |
| |
| /* Erase spare blocks of main table to clean possible interference data */ |
| nmbm_erase_range(ni, main_table_end_ba, ni->backup_table_ba); |
| |
| nlog_table_creation(ni, false, table_start_ba, table_end_ba); |
| |
| return true; |
| } |
| |
| /* |
| * nmbm_rescue_single_info_table - Rescue when there is only one info table |
| * @ni: NMBM instance structure |
| * |
| * This function is called when there is only one info table exists. |
| * This function may fail if we can't write new info table |
| */ |
| static bool nmbm_rescue_single_info_table(struct nmbm_instance *ni) |
| { |
| uint32_t table_start_ba, table_end_ba, write_ba; |
| bool success; |
| |
| /* Try to write new info table in front of existing table */ |
| success = nmbm_write_info_table(ni, ni->mgmt_start_ba, |
| ni->main_table_ba, |
| &table_start_ba, |
| &table_end_ba); |
| if (success) { |
| /* |
| * New table becomes the main table, existing table becomes |
| * the backup table. |
| */ |
| ni->backup_table_ba = ni->main_table_ba; |
| ni->main_table_ba = table_start_ba; |
| |
| nmbm_mark_tables_clean(ni); |
| |
| /* Erase spare blocks of main table to clean possible interference data */ |
| nmbm_erase_range(ni, table_end_ba, ni->backup_table_ba); |
| |
| nlog_table_creation(ni, true, table_start_ba, table_end_ba); |
| |
| return true; |
| } |
| |
| /* Try to reserve spare blocks for existing table */ |
| success = nmbm_try_reserve_blocks(ni, ni->mapping_blocks_ba, &write_ba, |
| ni->info_table_spare_blocks, 0, |
| ni->mapping_blocks_top_ba - |
| size2blk(ni, ni->info_table_size)); |
| if (!success) { |
| nlog_warn(ni, "Failed to rescue single info table\n"); |
| return false; |
| } |
| |
| /* Try to write new info table next to the existing table */ |
| while (write_ba >= ni->mapping_blocks_ba) { |
| WATCHDOG_RESET(); |
| |
| success = nmbm_write_info_table(ni, write_ba, |
| ni->mapping_blocks_top_ba, |
| &table_start_ba, |
| &table_end_ba); |
| if (success) |
| break; |
| |
| write_ba--; |
| } |
| |
| if (success) { |
| /* Erase spare blocks of main table to clean possible interference data */ |
| nmbm_erase_range(ni, ni->mapping_blocks_ba, table_start_ba); |
| |
| /* New table becomes the backup table */ |
| ni->backup_table_ba = table_start_ba; |
| ni->mapping_blocks_ba = table_end_ba; |
| |
| nmbm_mark_tables_clean(ni); |
| |
| nlog_table_creation(ni, false, table_start_ba, table_end_ba); |
| |
| return true; |
| } |
| |
| nlog_warn(ni, "Failed to rescue single info table\n"); |
| return false; |
| } |
| |
| /* |
| * nmbm_update_single_info_table - Update specific one info table |
| * @ni: NMBM instance structure |
| */ |
| static bool nmbm_update_single_info_table(struct nmbm_instance *ni, |
| bool update_main_table) |
| { |
| uint32_t write_start_ba, write_limit, table_start_ba, table_end_ba; |
| bool success; |
| |
| /* Determine the write range */ |
| if (update_main_table) { |
| write_start_ba = ni->main_table_ba; |
| write_limit = ni->backup_table_ba; |
| } else { |
| write_start_ba = ni->backup_table_ba; |
| write_limit = ni->mapping_blocks_top_ba; |
| } |
| |
| nmbm_mark_block_color_mgmt(ni, write_start_ba, write_limit - 1); |
| |
| success = nmbm_write_info_table(ni, write_start_ba, write_limit, |
| &table_start_ba, &table_end_ba); |
| if (success) { |
| if (update_main_table) { |
| ni->main_table_ba = table_start_ba; |
| } else { |
| ni->backup_table_ba = table_start_ba; |
| ni->mapping_blocks_ba = table_end_ba; |
| } |
| |
| nmbm_mark_tables_clean(ni); |
| |
| nlog_table_update(ni, update_main_table, table_start_ba, |
| table_end_ba); |
| |
| return true; |
| } |
| |
| if (update_main_table) { |
| /* |
| * If failed to update main table, make backup table the new |
| * main table, and call nmbm_rescue_single_info_table() |
| */ |
| nlog_warn(ni, "Unable to update %s info table\n", |
| update_main_table ? "Main" : "Backup"); |
| |
| ni->main_table_ba = ni->backup_table_ba; |
| ni->backup_table_ba = 0; |
| return nmbm_rescue_single_info_table(ni); |
| } |
| |
| /* Only one table left */ |
| ni->mapping_blocks_ba = ni->backup_table_ba; |
| ni->backup_table_ba = 0; |
| |
| return false; |
| } |
| |
| /* |
| * nmbm_rescue_main_info_table - Rescue when failed to write main info table |
| * @ni: NMBM instance structure |
| * |
| * This function is called when main info table failed to be written, and |
| * backup info table exists. |
| */ |
| static bool nmbm_rescue_main_info_table(struct nmbm_instance *ni) |
| { |
| uint32_t tmp_table_start_ba, tmp_table_end_ba, main_table_start_ba; |
| uint32_t main_table_end_ba, write_ba; |
| uint32_t info_table_erasesize = size2blk(ni, ni->info_table_size); |
| bool success; |
| |
| /* Try to reserve spare blocks for existing backup info table */ |
| success = nmbm_try_reserve_blocks(ni, ni->mapping_blocks_ba, &write_ba, |
| ni->info_table_spare_blocks, 0, |
| ni->mapping_blocks_top_ba - |
| info_table_erasesize); |
| if (!success) { |
| /* There is no spare block. Backup info table becomes the main table. */ |
| nlog_err(ni, "No room for temporary info table\n"); |
| ni->main_table_ba = ni->backup_table_ba; |
| ni->backup_table_ba = 0; |
| return true; |
| } |
| |
| /* Try to write temporary info table into spare unmapped blocks */ |
| while (write_ba >= ni->mapping_blocks_ba) { |
| WATCHDOG_RESET(); |
| |
| success = nmbm_write_info_table(ni, write_ba, |
| ni->mapping_blocks_top_ba, |
| &tmp_table_start_ba, |
| &tmp_table_end_ba); |
| if (success) |
| break; |
| |
| write_ba--; |
| } |
| |
| if (!success) { |
| /* Backup info table becomes the main table */ |
| nlog_err(ni, "Failed to update main info table\n"); |
| ni->main_table_ba = ni->backup_table_ba; |
| ni->backup_table_ba = 0; |
| return true; |
| } |
| |
| /* Adjust mapping_blocks_off */ |
| ni->mapping_blocks_ba = tmp_table_end_ba; |
| |
| nmbm_mark_block_color_mgmt(ni, ni->backup_table_ba, |
| tmp_table_end_ba - 1); |
| |
| /* |
| * Now write main info table at the beginning of management area. |
| * This operation will generally destroy the original backup info |
| * table. |
| */ |
| success = nmbm_write_info_table(ni, ni->mgmt_start_ba, |
| tmp_table_start_ba, |
| &main_table_start_ba, |
| &main_table_end_ba); |
| if (!success) { |
| /* Temporary info table becomes the main table */ |
| ni->main_table_ba = tmp_table_start_ba; |
| ni->backup_table_ba = 0; |
| |
| nmbm_mark_tables_clean(ni); |
| |
| nlog_err(ni, "Failed to update main info table\n"); |
| nmbm_mark_block_color_info_table(ni, tmp_table_start_ba, |
| tmp_table_end_ba - 1); |
| |
| return true; |
| } |
| |
| /* Main info table has been successfully written, record its offset */ |
| ni->main_table_ba = main_table_start_ba; |
| |
| nmbm_mark_tables_clean(ni); |
| |
| nlog_table_creation(ni, true, main_table_start_ba, main_table_end_ba); |
| |
| /* |
| * Temporary info table becomes the new backup info table if it's |
| * not overwritten. |
| */ |
| if (main_table_end_ba <= tmp_table_start_ba) { |
| ni->backup_table_ba = tmp_table_start_ba; |
| |
| nlog_table_creation(ni, false, tmp_table_start_ba, |
| tmp_table_end_ba); |
| |
| return true; |
| } |
| |
| /* Adjust mapping_blocks_off */ |
| ni->mapping_blocks_ba = main_table_end_ba; |
| |
| /* Try to reserve spare blocks for new main info table */ |
| success = nmbm_try_reserve_blocks(ni, main_table_end_ba, &write_ba, |
| ni->info_table_spare_blocks, 0, |
| ni->mapping_blocks_top_ba - |
| info_table_erasesize); |
| if (!success) { |
| /* There is no spare block. Only main table exists. */ |
| nlog_err(ni, "No room for backup info table\n"); |
| ni->backup_table_ba = 0; |
| return true; |
| } |
| |
| /* Write new backup info table. */ |
| while (write_ba >= main_table_end_ba) { |
| WATCHDOG_RESET(); |
| |
| success = nmbm_write_info_table(ni, write_ba, |
| ni->mapping_blocks_top_ba, |
| &tmp_table_start_ba, |
| &tmp_table_end_ba); |
| if (success) |
| break; |
| |
| write_ba--; |
| } |
| |
| if (!success) { |
| nlog_err(ni, "No room for backup info table\n"); |
| ni->backup_table_ba = 0; |
| return true; |
| } |
| |
| /* Backup info table has been successfully written, record its offset */ |
| ni->backup_table_ba = tmp_table_start_ba; |
| |
| /* Adjust mapping_blocks_off */ |
| ni->mapping_blocks_ba = tmp_table_end_ba; |
| |
| /* Erase spare blocks of main table to clean possible interference data */ |
| nmbm_erase_range(ni, main_table_end_ba, ni->backup_table_ba); |
| |
| nlog_table_creation(ni, false, tmp_table_start_ba, tmp_table_end_ba); |
| |
| return true; |
| } |
| |
| /* |
| * nmbm_update_info_table_once - Update info table once |
| * @ni: NMBM instance structure |
| * @force: force update |
| * |
| * Update both main and backup info table. Return true if at least one info |
| * table has been successfully written. |
| * This function only try to update info table once regard less of the result. |
| */ |
| static bool nmbm_update_info_table_once(struct nmbm_instance *ni, bool force) |
| { |
| uint32_t table_start_ba, table_end_ba; |
| uint32_t main_table_limit; |
| bool success; |
| |
| /* Do nothing if there is no change */ |
| if (!nmbm_generate_info_table_cache(ni) && !force) |
| return true; |
| |
| /* Check whether both two tables exist */ |
| if (!ni->backup_table_ba) { |
| main_table_limit = ni->mapping_blocks_top_ba; |
| goto write_main_table; |
| } |
| |
| nmbm_mark_block_color_mgmt(ni, ni->backup_table_ba, |
| ni->mapping_blocks_ba - 1); |
| |
| /* |
| * Write backup info table in its current range. |
| * Note that limit is set to mapping_blocks_top_off to provide as many |
| * spare blocks as possible for the backup table. If at last |
| * unmapped blocks are used by backup table, mapping_blocks_off will |
| * be adjusted. |
| */ |
| success = nmbm_write_info_table(ni, ni->backup_table_ba, |
| ni->mapping_blocks_top_ba, |
| &table_start_ba, &table_end_ba); |
| if (!success) { |
| /* |
| * There is nothing to do if failed to write backup table. |
| * Write the main table now. |
| */ |
| nlog_err(ni, "No room for backup table\n"); |
| ni->mapping_blocks_ba = ni->backup_table_ba; |
| ni->backup_table_ba = 0; |
| main_table_limit = ni->mapping_blocks_top_ba; |
| goto write_main_table; |
| } |
| |
| /* Backup table is successfully written, record its offset */ |
| ni->backup_table_ba = table_start_ba; |
| |
| /* Adjust mapping_blocks_off */ |
| ni->mapping_blocks_ba = table_end_ba; |
| |
| nmbm_mark_tables_clean(ni); |
| |
| /* The normal limit of main table */ |
| main_table_limit = ni->backup_table_ba; |
| |
| nlog_table_update(ni, false, table_start_ba, table_end_ba); |
| |
| write_main_table: |
| if (!ni->main_table_ba) |
| goto rebuild_tables; |
| |
| if (!ni->backup_table_ba) |
| nmbm_mark_block_color_mgmt(ni, ni->mgmt_start_ba, |
| ni->mapping_blocks_ba - 1); |
| else |
| nmbm_mark_block_color_mgmt(ni, ni->mgmt_start_ba, |
| ni->backup_table_ba - 1); |
| |
| /* Write main info table in its current range */ |
| success = nmbm_write_info_table(ni, ni->main_table_ba, |
| main_table_limit, &table_start_ba, |
| &table_end_ba); |
| if (!success) { |
| /* If failed to write main table, go rescue procedure */ |
| if (!ni->backup_table_ba) |
| goto rebuild_tables; |
| |
| return nmbm_rescue_main_info_table(ni); |
| } |
| |
| /* Main info table is successfully written, record its offset */ |
| ni->main_table_ba = table_start_ba; |
| |
| /* Adjust mapping_blocks_off */ |
| if (!ni->backup_table_ba) |
| ni->mapping_blocks_ba = table_end_ba; |
| |
| nmbm_mark_tables_clean(ni); |
| |
| nlog_table_update(ni, true, table_start_ba, table_end_ba); |
| |
| return true; |
| |
| rebuild_tables: |
| return nmbm_rebuild_info_table(ni); |
| } |
| |
| /* |
| * nmbm_update_info_table - Update info table |
| * @ni: NMBM instance structure |
| * |
| * Update both main and backup info table. Return true if at least one table |
| * has been successfully written. |
| * This function will try to update info table repeatedly until no new bad |
| * block found during updating. |
| */ |
| static bool nmbm_update_info_table(struct nmbm_instance *ni) |
| { |
| bool success; |
| |
| if (ni->protected) |
| return true; |
| |
| while (ni->block_state_changed || ni->block_mapping_changed) { |
| success = nmbm_update_info_table_once(ni, false); |
| if (!success) { |
| nlog_err(ni, "Failed to update info table\n"); |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| /* |
| * nmbm_map_block - Map a bad block to a unused spare block |
| * @ni: NMBM instance structure |
| * @lb: logic block addr to map |
| */ |
| static bool nmbm_map_block(struct nmbm_instance *ni, uint32_t lb) |
| { |
| uint32_t pb; |
| bool success; |
| |
| if (ni->mapping_blocks_ba == ni->mapping_blocks_top_ba) { |
| nlog_warn(ni, "No spare unmapped blocks.\n"); |
| return false; |
| } |
| |
| success = nmbm_block_walk(ni, false, ni->mapping_blocks_top_ba, &pb, 0, |
| ni->mapping_blocks_ba); |
| if (!success) { |
| nlog_warn(ni, "No spare unmapped blocks.\n"); |
| nmbm_update_info_table(ni); |
| ni->mapping_blocks_top_ba = ni->mapping_blocks_ba; |
| return false; |
| } |
| |
| ni->block_mapping[lb] = pb; |
| ni->mapping_blocks_top_ba--; |
| ni->block_mapping_changed++; |
| |
| nlog_info(ni, "Logic block %u mapped to physical blcok %u\n", lb, pb); |
| nmbm_mark_block_color_mapped(ni, pb); |
| |
| return true; |
| } |
| |
| /* |
| * nmbm_create_info_table - Create info table(s) |
| * @ni: NMBM instance structure |
| * |
| * This function assumes that the chip has no existing info table(s) |
| */ |
| static bool nmbm_create_info_table(struct nmbm_instance *ni) |
| { |
| uint32_t lb; |
| bool success; |
| |
| /* Set initial mapping_blocks_top_off */ |
| success = nmbm_block_walk(ni, false, ni->signature_ba, |
| &ni->mapping_blocks_top_ba, 1, |
| ni->mgmt_start_ba); |
| if (!success) { |
| nlog_err(ni, "No room for spare blocks\n"); |
| return false; |
| } |
| |
| /* Generate info table cache */ |
| nmbm_generate_info_table_cache(ni); |
| |
| /* Write info table */ |
| success = nmbm_rebuild_info_table(ni); |
| if (!success) { |
| nlog_err(ni, "Failed to build info tables\n"); |
| return false; |
| } |
| |
| /* Remap bad block(s) at end of data area */ |
| for (lb = ni->data_block_count; lb < ni->mgmt_start_ba; lb++) { |
| success = nmbm_map_block(ni, lb); |
| if (!success) |
| break; |
| |
| ni->data_block_count++; |
| } |
| |
| /* If state table and/or mapping table changed, update info table. */ |
| success = nmbm_update_info_table(ni); |
| if (!success) |
| return false; |
| |
| return true; |
| } |
| |
| /* |
| * nmbm_create_new - Create NMBM on a new chip |
| * @ni: NMBM instance structure |
| */ |
| static bool nmbm_create_new(struct nmbm_instance *ni) |
| { |
| bool success; |
| |
| /* Determine the boundary of management blocks */ |
| ni->mgmt_start_ba = ni->block_count * (NMBM_MGMT_DIV - ni->lower.max_ratio) / NMBM_MGMT_DIV; |
| |
| if (ni->lower.max_reserved_blocks && ni->block_count - ni->mgmt_start_ba > ni->lower.max_reserved_blocks) |
| ni->mgmt_start_ba = ni->block_count - ni->lower.max_reserved_blocks; |
| |
| nlog_info(ni, "NMBM management region starts at block %u [0x%08llx]\n", |
| ni->mgmt_start_ba, ba2addr(ni, ni->mgmt_start_ba)); |
| nmbm_mark_block_color_mgmt(ni, ni->mgmt_start_ba, ni->block_count - 1); |
| |
| /* Fill block state table & mapping table */ |
| nmbm_scan_badblocks(ni); |
| nmbm_build_mapping_table(ni); |
| |
| /* Write signature */ |
| ni->signature.header.magic = NMBM_MAGIC_SIGNATURE; |
| ni->signature.header.version = NMBM_VER; |
| ni->signature.header.size = sizeof(ni->signature); |
| ni->signature.nand_size = ni->lower.size; |
| ni->signature.block_size = ni->lower.erasesize; |
| ni->signature.page_size = ni->lower.writesize; |
| ni->signature.spare_size = ni->lower.oobsize; |
| ni->signature.mgmt_start_pb = ni->mgmt_start_ba; |
| ni->signature.max_try_count = NMBM_TRY_COUNT; |
| nmbm_update_checksum(&ni->signature.header); |
| |
| success = nmbm_write_signature(ni, ni->mgmt_start_ba, |
| &ni->signature, &ni->signature_ba); |
| if (!success) { |
| nlog_err(ni, "Failed to write signature to a proper offset\n"); |
| return false; |
| } |
| |
| nlog_info(ni, "Signature has been written to block %u [0x%08llx]\n", |
| ni->signature_ba, ba2addr(ni, ni->signature_ba)); |
| nmbm_mark_block_color_signature(ni, ni->signature_ba); |
| |
| /* Write info table(s) */ |
| success = nmbm_create_info_table(ni); |
| if (success) { |
| nlog_info(ni, "NMBM has been successfully created\n"); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /* |
| * nmbm_check_info_table_header - Check if a info table header is valid |
| * @ni: NMBM instance structure |
| * @data: pointer to the info table header |
| */ |
| static bool nmbm_check_info_table_header(struct nmbm_instance *ni, void *data) |
| { |
| struct nmbm_info_table_header *ifthdr = data; |
| |
| if (ifthdr->header.magic != NMBM_MAGIC_INFO_TABLE) |
| return false; |
| |
| if (ifthdr->header.size != ni->info_table_size) |
| return false; |
| |
| if (ifthdr->mapping_table_off - ifthdr->state_table_off < ni->state_table_size) |
| return false; |
| |
| if (ni->info_table_size - ifthdr->mapping_table_off < ni->mapping_table_size) |
| return false; |
| |
| return true; |
| } |
| |
| /* |
| * nmbm_check_info_table - Check if a whole info table is valid |
| * @ni: NMBM instance structure |
| * @start_ba: start block address of this table |
| * @end_ba: end block address of this table |
| * @data: pointer to the info table header |
| * @mapping_blocks_top_ba: return the block address of top remapped block |
| */ |
| static bool nmbm_check_info_table(struct nmbm_instance *ni, uint32_t start_ba, |
| uint32_t end_ba, void *data, |
| uint32_t *mapping_blocks_top_ba) |
| { |
| struct nmbm_info_table_header *ifthdr = data; |
| int32_t *block_mapping = (int32_t *)((uintptr_t)data + ifthdr->mapping_table_off); |
| nmbm_bitmap_t *block_state = (nmbm_bitmap_t *)((uintptr_t)data + ifthdr->state_table_off); |
| uint32_t minimum_mapping_pb = ni->signature_ba; |
| uint32_t ba; |
| |
| for (ba = 0; ba < ni->data_block_count; ba++) { |
| if ((block_mapping[ba] >= ni->data_block_count && block_mapping[ba] < end_ba) || |
| block_mapping[ba] == ni->signature_ba) |
| return false; |
| |
| if (block_mapping[ba] >= end_ba && block_mapping[ba] < minimum_mapping_pb) |
| minimum_mapping_pb = block_mapping[ba]; |
| } |
| |
| for (ba = start_ba; ba < end_ba; ba++) { |
| if (nmbm_get_block_state(ni, ba) != BLOCK_ST_GOOD) |
| continue; |
| |
| if (nmbm_get_block_state_raw(block_state, ba) != BLOCK_ST_GOOD) |
| return false; |
| } |
| |
| *mapping_blocks_top_ba = minimum_mapping_pb - 1; |
| |
| return true; |
| } |
| |
| /* |
| * nmbm_try_load_info_table - Try to load info table from a address |
| * @ni: NMBM instance structure |
| * @ba: start block address of the info table |
| * @eba: return the block address after end of the table |
| * @write_count: return the write count of this table |
| * @mapping_blocks_top_ba: return the block address of top remapped block |
| * @table_loaded: used to record whether ni->info_table has valid data |
| */ |
| static bool nmbm_try_load_info_table(struct nmbm_instance *ni, uint32_t ba, |
| uint32_t *eba, uint32_t *write_count, |
| uint32_t *mapping_blocks_top_ba, |
| bool table_loaded) |
| { |
| struct nmbm_info_table_header *ifthdr = (void *)ni->info_table_cache; |
| uint8_t *off = ni->info_table_cache; |
| uint32_t limit = ba + size2blk(ni, ni->info_table_size); |
| uint32_t start_ba = 0, chunksize, sizeremain = ni->info_table_size; |
| bool success, checkhdr = true; |
| int ret; |
| |
| while (sizeremain && ba < limit) { |
| WATCHDOG_RESET(); |
| |
| if (nmbm_get_block_state(ni, ba) != BLOCK_ST_GOOD) |
| goto next_block; |
| |
| if (nmbm_check_bad_phys_block(ni, ba)) { |
| nmbm_set_block_state(ni, ba, BLOCK_ST_BAD); |
| goto next_block; |
| } |
| |
| chunksize = sizeremain; |
| if (chunksize > ni->lower.erasesize) |
| chunksize = ni->lower.erasesize; |
| |
| /* Assume block with ECC error has no info table data */ |
| ret = nmbn_read_data(ni, ba2addr(ni, ba), off, chunksize); |
| if (ret < 0) |
| goto skip_bad_block; |
| else if (ret > 0) |
| return false; |
| |
| if (checkhdr) { |
| success = nmbm_check_info_table_header(ni, off); |
| if (!success) |
| return false; |
| |
| start_ba = ba; |
| checkhdr = false; |
| } |
| |
| off += chunksize; |
| sizeremain -= chunksize; |
| |
| goto next_block; |
| |
| skip_bad_block: |
| /* Only mark bad in memory */ |
| nmbm_set_block_state(ni, ba, BLOCK_ST_BAD); |
| |
| next_block: |
| ba++; |
| } |
| |
| if (sizeremain) |
| return false; |
| |
| success = nmbm_check_header(ni->info_table_cache, ni->info_table_size); |
| if (!success) |
| return false; |
| |
| *eba = ba; |
| *write_count = ifthdr->write_count; |
| |
| success = nmbm_check_info_table(ni, start_ba, ba, ni->info_table_cache, |
| mapping_blocks_top_ba); |
| if (!success) |
| return false; |
| |
| if (!table_loaded || ifthdr->write_count > ni->info_table.write_count) { |
| memcpy(&ni->info_table, ifthdr, sizeof(ni->info_table)); |
| memcpy(ni->block_state, |
| (uint8_t *)ifthdr + ifthdr->state_table_off, |
| ni->state_table_size); |
| memcpy(ni->block_mapping, |
| (uint8_t *)ifthdr + ifthdr->mapping_table_off, |
| ni->mapping_table_size); |
| ni->info_table.write_count = ifthdr->write_count; |
| } |
| |
| return true; |
| } |
| |
| /* |
| * nmbm_search_info_table - Search info table from specific address |
| * @ni: NMBM instance structure |
| * @ba: start block address to search |
| * @limit: highest block address allowed for searching |
| * @table_start_ba: return the start block address of this table |
| * @table_end_ba: return the block address after end of this table |
| * @write_count: return the write count of this table |
| * @mapping_blocks_top_ba: return the block address of top remapped block |
| * @table_loaded: used to record whether ni->info_table has valid data |
| */ |
| static bool nmbm_search_info_table(struct nmbm_instance *ni, uint32_t ba, |
| uint32_t limit, uint32_t *table_start_ba, |
| uint32_t *table_end_ba, |
| uint32_t *write_count, |
| uint32_t *mapping_blocks_top_ba, |
| bool table_loaded) |
| { |
| bool success; |
| |
| while (ba < limit - size2blk(ni, ni->info_table_size)) { |
| WATCHDOG_RESET(); |
| |
| success = nmbm_try_load_info_table(ni, ba, table_end_ba, |
| write_count, |
| mapping_blocks_top_ba, |
| table_loaded); |
| if (success) { |
| *table_start_ba = ba; |
| return true; |
| } |
| |
| ba++; |
| } |
| |
| return false; |
| } |
| |
| /* |
| * nmbm_load_info_table - Load info table(s) from a chip |
| * @ni: NMBM instance structure |
| * @ba: start block address to search info table |
| * @limit: highest block address allowed for searching |
| */ |
| static bool nmbm_load_info_table(struct nmbm_instance *ni, uint32_t ba, |
| uint32_t limit) |
| { |
| uint32_t main_table_end_ba, backup_table_end_ba, table_end_ba; |
| uint32_t main_mapping_blocks_top_ba, backup_mapping_blocks_top_ba; |
| uint32_t main_table_write_count, backup_table_write_count; |
| uint32_t i; |
| bool success; |
| |
| /* Set initial value */ |
| ni->main_table_ba = 0; |
| ni->backup_table_ba = 0; |
| ni->info_table.write_count = 0; |
| ni->mapping_blocks_top_ba = ni->signature_ba - 1; |
| ni->data_block_count = ni->signature.mgmt_start_pb; |
| |
| /* Find first info table */ |
| success = nmbm_search_info_table(ni, ba, limit, &ni->main_table_ba, |
| &main_table_end_ba, &main_table_write_count, |
| &main_mapping_blocks_top_ba, false); |
| if (!success) { |
| nlog_warn(ni, "No valid info table found\n"); |
| return false; |
| } |
| |
| table_end_ba = main_table_end_ba; |
| |
| nlog_table_found(ni, true, main_table_write_count, ni->main_table_ba, |
| main_table_end_ba); |
| |
| /* Find second info table */ |
| success = nmbm_search_info_table(ni, main_table_end_ba, limit, |
| &ni->backup_table_ba, &backup_table_end_ba, |
| &backup_table_write_count, &backup_mapping_blocks_top_ba, true); |
| if (!success) { |
| nlog_warn(ni, "Second info table not found\n"); |
| } else { |
| table_end_ba = backup_table_end_ba; |
| |
| nlog_table_found(ni, false, backup_table_write_count, |
| ni->backup_table_ba, backup_table_end_ba); |
| } |
| |
| /* Pick mapping_blocks_top_ba */ |
| if (!ni->backup_table_ba) { |
| ni->mapping_blocks_top_ba= main_mapping_blocks_top_ba; |
| } else { |
| if (main_table_write_count >= backup_table_write_count) |
| ni->mapping_blocks_top_ba = main_mapping_blocks_top_ba; |
| else |
| ni->mapping_blocks_top_ba = backup_mapping_blocks_top_ba; |
| } |
| |
| /* Set final mapping_blocks_ba */ |
| ni->mapping_blocks_ba = table_end_ba; |
| |
| /* Set final data_block_count */ |
| for (i = ni->signature.mgmt_start_pb; i > 0; i--) { |
| if (ni->block_mapping[i - 1] >= 0) { |
| ni->data_block_count = i; |
| break; |
| } |
| } |
| |
| /* Debug purpose: mark mapped blocks and bad blocks */ |
| for (i = 0; i < ni->data_block_count; i++) { |
| if (ni->block_mapping[i] > ni->mapping_blocks_top_ba) |
| nmbm_mark_block_color_mapped(ni, ni->block_mapping[i]); |
| } |
| |
| for (i = 0; i < ni->block_count; i++) { |
| if (nmbm_get_block_state(ni, i) == BLOCK_ST_BAD) |
| nmbm_mark_block_color_bad(ni, i); |
| } |
| |
| /* Regenerate the info table cache from the final selected info table */ |
| nmbm_generate_info_table_cache(ni); |
| |
| /* |
| * If only one table exists, try to write another table. |
| * If two tables have different write count, try to update info table |
| */ |
| if (!ni->backup_table_ba) { |
| success = nmbm_rescue_single_info_table(ni); |
| } else if (main_table_write_count != backup_table_write_count) { |
| /* Mark state & mapping tables changed */ |
| ni->block_state_changed = 1; |
| ni->block_mapping_changed = 1; |
| |
| success = nmbm_update_single_info_table(ni, |
| main_table_write_count < backup_table_write_count); |
| } else { |
| success = true; |
| } |
| |
| /* |
| * If there is no spare unmapped blocks, or still only one table |
| * exists, set the chip to read-only |
| */ |
| if (ni->mapping_blocks_ba == ni->mapping_blocks_top_ba) { |
| nlog_warn(ni, "No spare unmapped blocks. Device is now read-only\n"); |
| ni->protected = 1; |
| } else if (!success) { |
| nlog_warn(ni, "Only one info table found. Device is now read-only\n"); |
| ni->protected = 1; |
| } |
| |
| return true; |
| } |
| |
| /* |
| * nmbm_load_existing - Load NMBM from a new chip |
| * @ni: NMBM instance structure |
| */ |
| static bool nmbm_load_existing(struct nmbm_instance *ni) |
| { |
| bool success; |
| |
| /* Calculate the boundary of management blocks */ |
| ni->mgmt_start_ba = ni->signature.mgmt_start_pb; |
| |
| nlog_debug(ni, "NMBM management region starts at block %u [0x%08llx]\n", |
| ni->mgmt_start_ba, ba2addr(ni, ni->mgmt_start_ba)); |
| nmbm_mark_block_color_mgmt(ni, ni->mgmt_start_ba, |
| ni->signature_ba - 1); |
| |
| /* Look for info table(s) */ |
| success = nmbm_load_info_table(ni, ni->mgmt_start_ba, |
| ni->signature_ba); |
| if (success) { |
| nlog_info(ni, "NMBM has been successfully attached\n"); |
| return true; |
| } |
| |
| if (!(ni->lower.flags & NMBM_F_CREATE)) |
| return false; |
| |
| /* Fill block state table & mapping table */ |
| nmbm_scan_badblocks(ni); |
| nmbm_build_mapping_table(ni); |
| |
| /* Write info table(s) */ |
| success = nmbm_create_info_table(ni); |
| if (success) { |
| nlog_info(ni, "NMBM has been successfully created\n"); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /* |
| * nmbm_find_signature - Find signature in the lower NAND chip |
| * @ni: NMBM instance structure |
| * @signature_ba: used for storing block address of the signature |
| * @signature_ba: return the actual block address of signature block |
| * |
| * Find a valid signature from a specific range in the lower NAND chip, |
| * from bottom (highest address) to top (lowest address) |
| * |
| * Return true if found. |
| */ |
| static bool nmbm_find_signature(struct nmbm_instance *ni, |
| struct nmbm_signature *signature, |
| uint32_t *signature_ba) |
| { |
| struct nmbm_signature sig; |
| uint64_t off, addr; |
| uint32_t block_count, ba, limit; |
| bool success; |
| int ret; |
| |
| /* Calculate top and bottom block address */ |
| block_count = ni->lower.size >> ni->erasesize_shift; |
| ba = block_count; |
| limit = (block_count / NMBM_MGMT_DIV) * (NMBM_MGMT_DIV - ni->lower.max_ratio); |
| if (ni->lower.max_reserved_blocks && block_count - limit > ni->lower.max_reserved_blocks) |
| limit = block_count - ni->lower.max_reserved_blocks; |
| |
| while (ba >= limit) { |
| WATCHDOG_RESET(); |
| |
| ba--; |
| addr = ba2addr(ni, ba); |
| |
| if (nmbm_check_bad_phys_block(ni, ba)) |
| continue; |
| |
| /* Check every page. |
| * As long as at leaset one page contains valid signature, |
| * the block is treated as a valid signature block. |
| */ |
| for (off = 0; off < ni->lower.erasesize; |
| off += ni->lower.writesize) { |
| WATCHDOG_RESET(); |
| |
| ret = nmbn_read_data(ni, addr + off, &sig, |
| sizeof(sig)); |
| if (ret) |
| continue; |
| |
| /* Check for header size and checksum */ |
| success = nmbm_check_header(&sig, sizeof(sig)); |
| if (!success) |
| continue; |
| |
| /* Check for header magic */ |
| if (sig.header.magic == NMBM_MAGIC_SIGNATURE) { |
| /* Found it */ |
| memcpy(signature, &sig, sizeof(sig)); |
| *signature_ba = ba; |
| return true; |
| } |
| } |
| }; |
| |
| return false; |
| } |
| |
| /* |
| * is_power_of_2_u64 - Check whether a 64-bit integer is power of 2 |
| * @n: number to check |
| */ |
| static bool is_power_of_2_u64(uint64_t n) |
| { |
| return (n != 0 && ((n & (n - 1)) == 0)); |
| } |
| |
| /* |
| * nmbm_check_lower_members - Validate the members of lower NAND device |
| * @nld: Lower NAND chip structure |
| */ |
| static bool nmbm_check_lower_members(struct nmbm_lower_device *nld) |
| { |
| |
| if (!nld->size || !is_power_of_2_u64(nld->size)) { |
| nmbm_log_lower(nld, NMBM_LOG_ERR, |
| "Chip size %llu is not valid\n", nld->size); |
| return false; |
| } |
| |
| if (!nld->erasesize || !is_power_of_2(nld->erasesize)) { |
| nmbm_log_lower(nld, NMBM_LOG_ERR, |
| "Block size %u is not valid\n", nld->erasesize); |
| return false; |
| } |
| |
| if (!nld->writesize || !is_power_of_2(nld->writesize)) { |
| nmbm_log_lower(nld, NMBM_LOG_ERR, |
| "Page size %u is not valid\n", nld->writesize); |
| return false; |
| } |
| |
| if (!nld->oobsize || !is_power_of_2(nld->oobsize)) { |
| nmbm_log_lower(nld, NMBM_LOG_ERR, |
| "Page spare size %u is not valid\n", nld->oobsize); |
| return false; |
| } |
| |
| if (!nld->read_page || !nld->write_page || !nld->erase_block) { |
| nmbm_log_lower(nld, NMBM_LOG_ERR, |
| "read_page(), write_page() and erase_block() are required\n"); |
| return false; |
| } |
| |
| /* Data sanity check */ |
| if (!nld->max_ratio) |
| nld->max_ratio = 1; |
| |
| if (nld->max_ratio >= NMBM_MGMT_DIV - 1) { |
| nmbm_log_lower(nld, NMBM_LOG_ERR, |
| "max ratio %u is invalid\n", nld->max_ratio); |
| return false; |
| } |
| |
| if (nld->max_reserved_blocks && nld->max_reserved_blocks < NMBM_MGMT_BLOCKS_MIN) { |
| nmbm_log_lower(nld, NMBM_LOG_ERR, |
| "max reserved blocks %u is too small\n", nld->max_reserved_blocks); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /* |
| * nmbm_calc_structure_size - Calculate the instance structure size |
| * @nld: NMBM lower device structure |
| */ |
| size_t nmbm_calc_structure_size(struct nmbm_lower_device *nld) |
| { |
| uint32_t state_table_size, mapping_table_size, info_table_size; |
| uint32_t block_count; |
| |
| block_count = nmbm_lldiv(nld->size, nld->erasesize); |
| |
| /* Calculate info table size */ |
| state_table_size = ((block_count + NMBM_BITMAP_BLOCKS_PER_UNIT - 1) / |
| NMBM_BITMAP_BLOCKS_PER_UNIT) * NMBM_BITMAP_UNIT_SIZE; |
| mapping_table_size = block_count * sizeof(int32_t); |
| |
| info_table_size = NMBM_ALIGN(sizeof(struct nmbm_info_table_header), |
| nld->writesize); |
| info_table_size += NMBM_ALIGN(state_table_size, nld->writesize); |
| info_table_size += NMBM_ALIGN(mapping_table_size, nld->writesize); |
| |
| return info_table_size + state_table_size + mapping_table_size + |
| nld->writesize + nld->oobsize + sizeof(struct nmbm_instance); |
| } |
| |
| /* |
| * nmbm_init_structure - Initialize members of instance structure |
| * @ni: NMBM instance structure |
| */ |
| static void nmbm_init_structure(struct nmbm_instance *ni) |
| { |
| uint32_t pages_per_block, blocks_per_chip; |
| uintptr_t ptr; |
| |
| pages_per_block = ni->lower.erasesize / ni->lower.writesize; |
| blocks_per_chip = nmbm_lldiv(ni->lower.size, ni->lower.erasesize); |
| |
| ni->rawpage_size = ni->lower.writesize + ni->lower.oobsize; |
| ni->rawblock_size = pages_per_block * ni->rawpage_size; |
| ni->rawchip_size = blocks_per_chip * ni->rawblock_size; |
| |
| ni->writesize_mask = ni->lower.writesize - 1; |
| ni->erasesize_mask = ni->lower.erasesize - 1; |
| |
| ni->writesize_shift = ffs(ni->lower.writesize) - 1; |
| ni->erasesize_shift = ffs(ni->lower.erasesize) - 1; |
| |
| /* Calculate number of block this chip */ |
| ni->block_count = ni->lower.size >> ni->erasesize_shift; |
| |
| /* Calculate info table size */ |
| ni->state_table_size = ((ni->block_count + NMBM_BITMAP_BLOCKS_PER_UNIT - 1) / |
| NMBM_BITMAP_BLOCKS_PER_UNIT) * NMBM_BITMAP_UNIT_SIZE; |
| ni->mapping_table_size = ni->block_count * sizeof(*ni->block_mapping); |
| |
| ni->info_table_size = NMBM_ALIGN(sizeof(ni->info_table), |
| ni->lower.writesize); |
| ni->info_table.state_table_off = ni->info_table_size; |
| |
| ni->info_table_size += NMBM_ALIGN(ni->state_table_size, |
| ni->lower.writesize); |
| ni->info_table.mapping_table_off = ni->info_table_size; |
| |
| ni->info_table_size += NMBM_ALIGN(ni->mapping_table_size, |
| ni->lower.writesize); |
| |
| ni->info_table_spare_blocks = nmbm_get_spare_block_count( |
| size2blk(ni, ni->info_table_size)); |
| |
| /* Assign memory to members */ |
| ptr = (uintptr_t)ni + sizeof(*ni); |
| |
| ni->info_table_cache = (void *)ptr; |
| ptr += ni->info_table_size; |
| |
| ni->block_state = (void *)ptr; |
| ptr += ni->state_table_size; |
| |
| ni->block_mapping = (void *)ptr; |
| ptr += ni->mapping_table_size; |
| |
| ni->page_cache = (uint8_t *)ptr; |
| |
| /* Initialize block state table */ |
| ni->block_state_changed = 0; |
| memset(ni->block_state, 0xff, ni->state_table_size); |
| |
| /* Initialize block mapping table */ |
| ni->block_mapping_changed = 0; |
| } |
| |
| /* |
| * nmbm_attach - Attach to a lower device |
| * @nld: NMBM lower device structure |
| * @ni: NMBM instance structure |
| */ |
| int nmbm_attach(struct nmbm_lower_device *nld, struct nmbm_instance *ni) |
| { |
| bool success; |
| |
| if (!nld || !ni) |
| return -EINVAL; |
| |
| /* Set default log level */ |
| ni->log_display_level = NMBM_DEFAULT_LOG_LEVEL; |
| |
| /* Check lower members */ |
| success = nmbm_check_lower_members(nld); |
| if (!success) |
| return -EINVAL; |
| |
| /* Initialize NMBM instance */ |
| memcpy(&ni->lower, nld, sizeof(struct nmbm_lower_device)); |
| nmbm_init_structure(ni); |
| |
| success = nmbm_find_signature(ni, &ni->signature, &ni->signature_ba); |
| if (!success) { |
| if (!(nld->flags & NMBM_F_CREATE)) { |
| nlog_err(ni, "Signature not found\n"); |
| return -ENODEV; |
| } |
| |
| success = nmbm_create_new(ni); |
| if (!success) |
| return -ENODEV; |
| |
| return 0; |
| } |
| |
| nlog_info(ni, "Signature found at block %u [0x%08llx]\n", |
| ni->signature_ba, ba2addr(ni, ni->signature_ba)); |
| nmbm_mark_block_color_signature(ni, ni->signature_ba); |
| |
| if (ni->signature.header.version != NMBM_VER) { |
| nlog_err(ni, "NMBM version %u.%u is not supported\n", |
| NMBM_VERSION_MAJOR_GET(ni->signature.header.version), |
| NMBM_VERSION_MINOR_GET(ni->signature.header.version)); |
| return -EINVAL; |
| } |
| |
| if (ni->signature.nand_size != nld->size || |
| ni->signature.block_size != nld->erasesize || |
| ni->signature.page_size != nld->writesize || |
| ni->signature.spare_size != nld->oobsize) { |
| nlog_err(ni, "NMBM configuration mismatch\n"); |
| return -EINVAL; |
| } |
| |
| success = nmbm_load_existing(ni); |
| if (!success) |
| return -ENODEV; |
| |
| return 0; |
| } |
| |
| /* |
| * nmbm_detach - Detach from a lower device, and save all tables |
| * @ni: NMBM instance structure |
| */ |
| int nmbm_detach(struct nmbm_instance *ni) |
| { |
| if (!ni) |
| return -EINVAL; |
| |
| nmbm_update_info_table(ni); |
| |
| nmbm_mark_block_color_normal(ni, 0, ni->block_count - 1); |
| |
| return 0; |
| } |
| |
| /* |
| * nmbm_erase_logic_block - Erase a logic block |
| * @ni: NMBM instance structure |
| * @nmbm_erase_logic_block: logic block address |
| * |
| * Logic block will be mapped to physical block before erasing. |
| * Bad block found during erasinh will be remapped to a good block if there is |
| * still at least one good spare block available. |
| */ |
| static int nmbm_erase_logic_block(struct nmbm_instance *ni, uint32_t block_addr) |
| { |
| uint32_t pb; |
| bool success; |
| |
| retry: |
| /* Map logic block to physical block */ |
| pb = ni->block_mapping[block_addr]; |
| |
| /* Whether the logic block is good (has valid mapping) */ |
| if ((int32_t)pb < 0) { |
| nlog_debug(ni, "Logic block %u is a bad block\n", block_addr); |
| return -EIO; |
| } |
| |
| /* Remap logic block if current physical block is a bad block */ |
| if (nmbm_get_block_state(ni, pb) == BLOCK_ST_BAD || |
| nmbm_get_block_state(ni, pb) == BLOCK_ST_NEED_REMAP) |
| goto remap_logic_block; |
| |
| /* Insurance to detect unexpected bad block marked by user */ |
| if (nmbm_check_bad_phys_block(ni, pb)) { |
| nlog_warn(ni, "Found unexpected bad block possibly marked by user\n"); |
| nmbm_set_block_state(ni, pb, BLOCK_ST_BAD); |
| goto remap_logic_block; |
| } |
| |
| success = nmbm_erase_phys_block(ni, ba2addr(ni, pb)); |
| if (success) |
| return 0; |
| |
| /* Mark bad block */ |
| nmbm_mark_phys_bad_block(ni, pb); |
| nmbm_set_block_state(ni, pb, BLOCK_ST_BAD); |
| |
| remap_logic_block: |
| /* Try to assign a new block */ |
| success = nmbm_map_block(ni, block_addr); |
| if (!success) { |
| /* Mark logic block unusable, and update info table */ |
| ni->block_mapping[block_addr] = -1; |
| if (nmbm_get_block_state(ni, pb) != BLOCK_ST_NEED_REMAP) |
| nmbm_set_block_state(ni, pb, BLOCK_ST_BAD); |
| nmbm_update_info_table(ni); |
| return -EIO; |
| } |
| |
| /* Update info table before erasing */ |
| if (nmbm_get_block_state(ni, pb) != BLOCK_ST_NEED_REMAP) |
| nmbm_set_block_state(ni, pb, BLOCK_ST_BAD); |
| nmbm_update_info_table(ni); |
| |
| goto retry; |
| } |
| |
| /* |
| * nmbm_erase_block_range - Erase logic blocks |
| * @ni: NMBM instance structure |
| * @addr: logic linear address |
| * @size: erase range |
| * @failed_addr: return failed block address if error occurs |
| */ |
| int nmbm_erase_block_range(struct nmbm_instance *ni, uint64_t addr, |
| uint64_t size, uint64_t *failed_addr) |
| { |
| uint32_t start_ba, end_ba; |
| int ret; |
| |
| if (!ni) |
| return -EINVAL; |
| |
| /* Sanity check */ |
| if (ni->protected) { |
| nlog_debug(ni, "Device is forced read-only\n"); |
| return -EROFS; |
| } |
| |
| if (addr >= ba2addr(ni, ni->data_block_count)) { |
| nlog_err(ni, "Address 0x%llx is invalid\n", addr); |
| return -EINVAL; |
| } |
| |
| if (addr + size > ba2addr(ni, ni->data_block_count)) { |
| nlog_err(ni, "Erase range 0xllxu is too large\n", size); |
| return -EINVAL; |
| } |
| |
| if (!size) { |
| nlog_warn(ni, "No blocks to be erased\n"); |
| return 0; |
| } |
| |
| start_ba = addr2ba(ni, addr); |
| end_ba = addr2ba(ni, addr + size - 1); |
| |
| while (start_ba <= end_ba) { |
| WATCHDOG_RESET(); |
| |
| ret = nmbm_erase_logic_block(ni, start_ba); |
| if (ret) { |
| if (failed_addr) |
| *failed_addr = ba2addr(ni, start_ba); |
| return ret; |
| } |
| |
| start_ba++; |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * nmbm_read_logic_page - Read page based on logic address |
| * @ni: NMBM instance structure |
| * @addr: logic linear address |
| * @data: buffer to store main data. optional. |
| * @oob: buffer to store oob data. optional. |
| * @mode: read mode |
| */ |
| static int nmbm_read_logic_page(struct nmbm_instance *ni, uint64_t addr, |
| void *data, void *oob, enum nmbm_oob_mode mode) |
| { |
| uint32_t lb, pb, offset; |
| uint64_t paddr; |
| int ret; |
| |
| /* Extract block address and in-block offset */ |
| lb = addr2ba(ni, addr); |
| offset = addr & ni->erasesize_mask; |
| |
| /* Map logic block to physical block */ |
| pb = ni->block_mapping[lb]; |
| |
| /* Whether the logic block is good (has valid mapping) */ |
| if ((int32_t)pb < 0) { |
| nlog_debug(ni, "Logic block %u is a bad block\n", lb); |
| return -EIO; |
| } |
| |
| /* Fail if physical block is marked bad */ |
| if (nmbm_get_block_state(ni, pb) == BLOCK_ST_BAD) |
| return -EIO; |
| |
| /* Assemble new address */ |
| paddr = ba2addr(ni, pb) + offset; |
| |
| ret = nmbm_read_phys_page(ni, paddr, data, oob, mode); |
| if (!ret) |
| return 0; |
| |
| /* For ECC error, return positive value only */ |
| if (ret > 0) |
| return 1; |
| |
| /* |
| * Do not remap bad block here. Just mark this block in state table. |
| * Remap this block on erasing. |
| */ |
| nmbm_set_block_state(ni, pb, BLOCK_ST_NEED_REMAP); |
| nmbm_update_info_table(ni); |
| |
| return -EIO; |
| } |
| |
| /* |
| * nmbm_read_single_page - Read one page based on logic address |
| * @ni: NMBM instance structure |
| * @addr: logic linear address |
| * @data: buffer to store main data. optional. |
| * @oob: buffer to store oob data. optional. |
| * @mode: read mode |
| */ |
| int nmbm_read_single_page(struct nmbm_instance *ni, uint64_t addr, void *data, |
| void *oob, enum nmbm_oob_mode mode) |
| { |
| if (!ni) |
| return -EINVAL; |
| |
| /* Sanity check */ |
| if (ni->protected) { |
| nlog_debug(ni, "Device is forced read-only\n"); |
| return -EROFS; |
| } |
| |
| if (addr >= ba2addr(ni, ni->data_block_count)) { |
| nlog_err(ni, "Address 0x%llx is invalid\n", addr); |
| return -EINVAL; |
| } |
| |
| return nmbm_read_logic_page(ni, addr, data, oob, mode); |
| } |
| |
| /* |
| * nmbm_read_range - Read data without oob |
| * @ni: NMBM instance structure |
| * @addr: logic linear address |
| * @size: data size to read |
| * @data: buffer to store main data to be read |
| * @mode: read mode |
| * @retlen: return actual data size read |
| */ |
| int nmbm_read_range(struct nmbm_instance *ni, uint64_t addr, size_t size, |
| void *data, enum nmbm_oob_mode mode, size_t *retlen) |
| { |
| uint64_t off = addr; |
| uint8_t *ptr = data; |
| size_t sizeremain = size, chunksize, leading; |
| int ret; |
| |
| if (!ni) |
| return -EINVAL; |
| |
| /* Sanity check */ |
| if (ni->protected) { |
| nlog_debug(ni, "Device is forced read-only\n"); |
| return -EROFS; |
| } |
| |
| if (addr >= ba2addr(ni, ni->data_block_count)) { |
| nlog_err(ni, "Address 0x%llx is invalid\n", addr); |
| return -EINVAL; |
| } |
| |
| if (addr + size > ba2addr(ni, ni->data_block_count)) { |
| nlog_err(ni, "Read range 0x%llx is too large\n", size); |
| return -EINVAL; |
| } |
| |
| if (!size) { |
| nlog_warn(ni, "No data to be read\n"); |
| return 0; |
| } |
| |
| while (sizeremain) { |
| WATCHDOG_RESET(); |
| |
| leading = off & ni->writesize_mask; |
| chunksize = ni->lower.writesize - leading; |
| if (chunksize > sizeremain) |
| chunksize = sizeremain; |
| |
| if (chunksize == ni->lower.writesize) { |
| ret = nmbm_read_logic_page(ni, off - leading, ptr, |
| NULL, mode); |
| if (ret) |
| break; |
| } else { |
| ret = nmbm_read_logic_page(ni, off - leading, |
| ni->page_cache, NULL, |
| mode); |
| if (ret) |
| break; |
| |
| memcpy(ptr, ni->page_cache + leading, chunksize); |
| } |
| |
| off += chunksize; |
| ptr += chunksize; |
| sizeremain -= chunksize; |
| } |
| |
| if (retlen) |
| *retlen = size - sizeremain; |
| |
| return ret; |
| } |
| |
| /* |
| * nmbm_write_logic_page - Read page based on logic address |
| * @ni: NMBM instance structure |
| * @addr: logic linear address |
| * @data: buffer contains main data. optional. |
| * @oob: buffer contains oob data. optional. |
| * @mode: write mode |
| */ |
| static int nmbm_write_logic_page(struct nmbm_instance *ni, uint64_t addr, |
| const void *data, const void *oob, |
| enum nmbm_oob_mode mode) |
| { |
| uint32_t lb, pb, offset; |
| uint64_t paddr; |
| bool success; |
| |
| /* Extract block address and in-block offset */ |
| lb = addr2ba(ni, addr); |
| offset = addr & ni->erasesize_mask; |
| |
| /* Map logic block to physical block */ |
| pb = ni->block_mapping[lb]; |
| |
| /* Whether the logic block is good (has valid mapping) */ |
| if ((int32_t)pb < 0) { |
| nlog_debug(ni, "Logic block %u is a bad block\n", lb); |
| return -EIO; |
| } |
| |
| /* Fail if physical block is marked bad */ |
| if (nmbm_get_block_state(ni, pb) == BLOCK_ST_BAD) |
| return -EIO; |
| |
| /* Assemble new address */ |
| paddr = ba2addr(ni, pb) + offset; |
| |
| success = nmbm_write_phys_page(ni, paddr, data, oob, mode); |
| if (success) |
| return 0; |
| |
| /* |
| * Do not remap bad block here. Just mark this block in state table. |
| * Remap this block on erasing. |
| */ |
| nmbm_set_block_state(ni, pb, BLOCK_ST_NEED_REMAP); |
| nmbm_update_info_table(ni); |
| |
| return -EIO; |
| } |
| |
| /* |
| * nmbm_write_single_page - Write one page based on logic address |
| * @ni: NMBM instance structure |
| * @addr: logic linear address |
| * @data: buffer contains main data. optional. |
| * @oob: buffer contains oob data. optional. |
| * @mode: write mode |
| */ |
| int nmbm_write_single_page(struct nmbm_instance *ni, uint64_t addr, |
| const void *data, const void *oob, |
| enum nmbm_oob_mode mode) |
| { |
| if (!ni) |
| return -EINVAL; |
| |
| /* Sanity check */ |
| if (ni->protected) { |
| nlog_debug(ni, "Device is forced read-only\n"); |
| return -EROFS; |
| } |
| |
| if (addr >= ba2addr(ni, ni->data_block_count)) { |
| nlog_err(ni, "Address 0x%llx is invalid\n", addr); |
| return -EINVAL; |
| } |
| |
| return nmbm_write_logic_page(ni, addr, data, oob, mode); |
| } |
| |
| /* |
| * nmbm_write_range - Write data without oob |
| * @ni: NMBM instance structure |
| * @addr: logic linear address |
| * @size: data size to write |
| * @data: buffer contains data to be written |
| * @mode: write mode |
| * @retlen: return actual data size written |
| */ |
| int nmbm_write_range(struct nmbm_instance *ni, uint64_t addr, size_t size, |
| const void *data, enum nmbm_oob_mode mode, |
| size_t *retlen) |
| { |
| uint64_t off = addr; |
| const uint8_t *ptr = data; |
| size_t sizeremain = size, chunksize, leading; |
| int ret; |
| |
| if (!ni) |
| return -EINVAL; |
| |
| /* Sanity check */ |
| if (ni->protected) { |
| nlog_debug(ni, "Device is forced read-only\n"); |
| return -EROFS; |
| } |
| |
| if (addr >= ba2addr(ni, ni->data_block_count)) { |
| nlog_err(ni, "Address 0x%llx is invalid\n", addr); |
| return -EINVAL; |
| } |
| |
| if (addr + size > ba2addr(ni, ni->data_block_count)) { |
| nlog_err(ni, "Write size 0x%zx is too large\n", size); |
| return -EINVAL; |
| } |
| |
| if (!size) { |
| nlog_warn(ni, "No data to be written\n"); |
| return 0; |
| } |
| |
| while (sizeremain) { |
| WATCHDOG_RESET(); |
| |
| leading = off & ni->writesize_mask; |
| chunksize = ni->lower.writesize - leading; |
| if (chunksize > sizeremain) |
| chunksize = sizeremain; |
| |
| if (chunksize == ni->lower.writesize) { |
| ret = nmbm_write_logic_page(ni, off - leading, ptr, |
| NULL, mode); |
| if (ret) |
| break; |
| } else { |
| memset(ni->page_cache, 0xff, leading); |
| memcpy(ni->page_cache + leading, ptr, chunksize); |
| |
| ret = nmbm_write_logic_page(ni, off - leading, |
| ni->page_cache, NULL, |
| mode); |
| if (ret) |
| break; |
| } |
| |
| off += chunksize; |
| ptr += chunksize; |
| sizeremain -= chunksize; |
| } |
| |
| if (retlen) |
| *retlen = size - sizeremain; |
| |
| return ret; |
| } |
| |
| /* |
| * nmbm_check_bad_block - Check whether a logic block is usable |
| * @ni: NMBM instance structure |
| * @addr: logic linear address |
| */ |
| int nmbm_check_bad_block(struct nmbm_instance *ni, uint64_t addr) |
| { |
| uint32_t lb, pb; |
| |
| if (!ni) |
| return -EINVAL; |
| |
| if (addr >= ba2addr(ni, ni->data_block_count)) { |
| nlog_err(ni, "Address 0x%llx is invalid\n", addr); |
| return -EINVAL; |
| } |
| |
| lb = addr2ba(ni, addr); |
| |
| /* Map logic block to physical block */ |
| pb = ni->block_mapping[lb]; |
| |
| if ((int32_t)pb < 0) |
| return 1; |
| |
| if (nmbm_get_block_state(ni, pb) == BLOCK_ST_BAD) |
| return 1; |
| |
| return 0; |
| } |
| |
| /* |
| * nmbm_mark_bad_block - Mark a logic block unusable |
| * @ni: NMBM instance structure |
| * @addr: logic linear address |
| */ |
| int nmbm_mark_bad_block(struct nmbm_instance *ni, uint64_t addr) |
| { |
| uint32_t lb, pb; |
| |
| if (!ni) |
| return -EINVAL; |
| |
| if (addr >= ba2addr(ni, ni->data_block_count)) { |
| nlog_err(ni, "Address 0x%llx is invalid\n", addr); |
| return -EINVAL; |
| } |
| |
| lb = addr2ba(ni, addr); |
| |
| /* Map logic block to physical block */ |
| pb = ni->block_mapping[lb]; |
| |
| if ((int32_t)pb < 0) |
| return 0; |
| |
| ni->block_mapping[lb] = -1; |
| nmbm_mark_phys_bad_block(ni, pb); |
| nmbm_set_block_state(ni, pb, BLOCK_ST_BAD); |
| nmbm_update_info_table(ni); |
| |
| return 0; |
| } |
| |
| /* |
| * nmbm_get_avail_size - Get available user data size |
| * @ni: NMBM instance structure |
| */ |
| uint64_t nmbm_get_avail_size(struct nmbm_instance *ni) |
| { |
| if (!ni) |
| return 0; |
| |
| return (uint64_t)ni->data_block_count << ni->erasesize_shift; |
| } |
| |
| /* |
| * nmbm_get_lower_device - Get lower device structure |
| * @ni: NMBM instance structure |
| * @nld: pointer to hold the data of lower device structure |
| */ |
| int nmbm_get_lower_device(struct nmbm_instance *ni, struct nmbm_lower_device *nld) |
| { |
| if (!ni) |
| return -EINVAL; |
| |
| if (nld) |
| memcpy(nld, &ni->lower, sizeof(*nld)); |
| |
| return 0; |
| } |
| |
| #include "nmbm-debug.inl" |