From f1589acf175eb7349a4a3c250d49d8c7a3e5cb0a Mon Sep 17 00:00:00 2001 From: rogeryou Date: Thu, 10 Mar 2022 17:54:58 +0800 Subject: [PATCH] modify SPINANDBlockDevice.cpp --- .../source/SPINANDBlockDevice.cpp | 323 ++++++++++++++++-- 1 file changed, 292 insertions(+), 31 deletions(-) diff --git a/storage/blockdevice/COMPONENT_SPINAND/source/SPINANDBlockDevice.cpp b/storage/blockdevice/COMPONENT_SPINAND/source/SPINANDBlockDevice.cpp index 6f11d8b15469..5a2928ce7acf 100644 --- a/storage/blockdevice/COMPONENT_SPINAND/source/SPINANDBlockDevice.cpp +++ b/storage/blockdevice/COMPONENT_SPINAND/source/SPINANDBlockDevice.cpp @@ -19,6 +19,7 @@ #include "SPINANDBlockDevice.h" #include #include "rtos/ThisThread.h" +#include "bch.h" #ifndef MBED_CONF_MBED_TRACE_ENABLE #define MBED_CONF_MBED_TRACE_ENABLE 0 @@ -50,8 +51,8 @@ using namespace mbed; #define SPINAND_STATUS_BIT_PROGRAM_FAIL 0x8 // Program failed #define SPINAND_STATUS_BIT_ECC_STATUS_MASK 0x30 // ECC status #define SPINAND_STATUS_ECC_STATUS_NO_ERR 0x00 -#define SPINAND_STATUS_ECC_STATUS_ERR_COR 0x00 -#define SPINAND_STATUS_ECC_STATUS_ERR_NO_COR 0x00 +#define SPINAND_STATUS_ECC_STATUS_ERR_COR 0x10 +#define SPINAND_STATUS_ECC_STATUS_ERR_NO_COR 0x20 // Secure OTP Register Bits #define SPINAND_SECURE_BIT_QE 0x01 // Quad enable @@ -108,6 +109,7 @@ using namespace mbed; #define SPINAND_BLOCK_OFFSET 0x40000 #define SPINAND_PAGE_OFFSET 0x1000 +#define SPINAND_PAGE_MASK 0xFFFFF000 #define SPI_NAND_ROW_ADDR_SIZE QSPI_CFG_ADDR_SIZE_16 #define SPI_NAND_COLUMN_ADDR_SIZE QSPI_CFG_ADDR_SIZE_24 @@ -189,12 +191,18 @@ int SPINANDBlockDevice::init() _alt_size = 0; _dummy_cycles = 8; + _page_shift = 12; + _ecc_bits = 0; if (QSPI_STATUS_OK != _qspi_set_frequency(_freq)) { tr_error("QSPI Set Frequency Failed"); status = SPINAND_BD_ERROR_DEVICE_ERROR; goto exit_point; } + if (!_read_otp_onfi()) { + return SPINAND_BD_ERROR_READY_FAILED; + } + // Synchronize Device if (false == _is_mem_ready()) { tr_error("Init - _is_mem_ready Failed"); @@ -250,6 +258,10 @@ int SPINANDBlockDevice::deinit() result = SPINAND_BD_ERROR_DEVICE_ERROR; } + if (_ecc_bits > 0) { + _bch_free(); + } + _is_initialized = false; _mutex.unlock(); @@ -264,6 +276,7 @@ int SPINANDBlockDevice::deinit() int SPINANDBlockDevice::read(void *buffer, bd_addr_t addr, bd_size_t size) { int status = SPINAND_BD_ERROR_OK; + bool read_failed = false; uint32_t offset = 0; uint32_t chunk = 0; bd_size_t read_bytes = 0; @@ -272,15 +285,66 @@ int SPINANDBlockDevice::read(void *buffer, bd_addr_t addr, bd_size_t size) while (size > 0) { // Read on _page_size_bytes boundaries (Default 2048 bytes a page) - offset = addr % MBED_CONF_SPINAND_SPINAND_PAGE_SIZE; - chunk = (offset + size < MBED_CONF_SPINAND_SPINAND_PAGE_SIZE) ? size : (MBED_CONF_SPINAND_SPINAND_PAGE_SIZE - offset); + offset = addr % _page_size; + chunk = (offset + size < _page_size) ? size : (_page_size - offset); read_bytes = chunk; _mutex.lock(); - if (QSPI_STATUS_OK != _qspi_send_read_command(_read_instruction, buffer, addr, read_bytes)) { - tr_error("Read Command failed"); - status = SPINAND_BD_ERROR_DEVICE_ERROR; + if (_ecc_bits == 0) { + if (QSPI_STATUS_OK != _qspi_send_read_command(_read_instruction, buffer, addr, read_bytes)) { + tr_error("Read Command failed"); + read_failed = true; + status = SPINAND_BD_ERROR_DEVICE_ERROR; + goto exit_point; + } + + uint8_t status_reg; + if (QSPI_STATUS_OK != _qspi_send_general_command(SPINAND_INST_GET_FEATURE, FEATURES_ADDR_STATUS, + NULL, 0, (char *) &status_reg, 1)) { + tr_error("Reading Status Register failed"); + read_failed = true; + status = SPINAND_BD_ERROR_DEVICE_ERROR; + goto exit_point; + } + + if ((status_reg & SPINAND_STATUS_BIT_ECC_STATUS_MASK) == SPINAND_STATUS_ECC_STATUS_ERR_NO_COR) { + tr_error("Reading data failed"); + status = SPINAND_BD_ERROR_DEVICE_ERROR; + read_failed = true; + goto exit_point; + } + } else { + uint8_t ecc_steps = _ecc_steps; + uint8_t *p = (uint8_t*)_page_buf; + + if (QSPI_STATUS_OK != _qspi_send_read_command(_read_instruction, (void *)_page_buf, addr & SPINAND_PAGE_MASK, _page_size + _oob_size)) { + tr_error("Read Command failed"); + read_failed = true; + status = SPINAND_BD_ERROR_DEVICE_ERROR; + goto exit_point; + } + + // calculate the software ECC + for (uint8_t i = 0; ecc_steps; ecc_steps--, i += _ecc_bytes, p += _ecc_size) { + _bch_calculate_ecc(p, _ecc_calc + i); + } + + memcpy(_ecc_code, _page_buf + _page_size + _ecc_layout_pos, _ecc_bytes * _ecc_steps); + + p = (uint8_t*)_page_buf; + ecc_steps = _ecc_steps; + for (uint8_t i = 0 ; ecc_steps; ecc_steps--, i += _ecc_bytes, p += _ecc_size) { + int res = _bch_correct_data(p, _ecc_code + i, _ecc_calc + i); + if (res < 0) { + tr_error("Reading data failed"); + status = SPINAND_BD_ERROR_DEVICE_ERROR; + read_failed = true; + goto exit_point; + } + } + + memcpy(buffer, _page_buf + offset, read_bytes); } buffer = static_cast< uint8_t *>(buffer) + chunk; @@ -290,6 +354,11 @@ int SPINANDBlockDevice::read(void *buffer, bd_addr_t addr, bd_size_t size) _mutex.unlock(); } +exit_point: + if (read_failed) { + _mutex.unlock(); + } + return status; } @@ -306,8 +375,8 @@ int SPINANDBlockDevice::program(const void *buffer, bd_addr_t addr, bd_size_t si while (size > 0) { // Write on _page_size_bytes boundaries (Default 2048 bytes a page) - offset = addr % MBED_CONF_SPINAND_SPINAND_PAGE_SIZE; - chunk = (offset + size < MBED_CONF_SPINAND_SPINAND_PAGE_SIZE) ? size : (MBED_CONF_SPINAND_SPINAND_PAGE_SIZE - offset); + offset = addr % _page_size; + chunk = (offset + size < _page_size) ? size : (_page_size - offset); written_bytes = chunk; _mutex.lock(); @@ -320,13 +389,48 @@ int SPINANDBlockDevice::program(const void *buffer, bd_addr_t addr, bd_size_t si goto exit_point; } - result = _qspi_send_program_command(_program_instruction, buffer, addr, &written_bytes); - if ((result != QSPI_STATUS_OK) || (chunk != written_bytes)) { - tr_error("Write failed"); - program_failed = true; - status = SPINAND_BD_ERROR_DEVICE_ERROR; - goto exit_point; - } + if (_ecc_bits == 0) { + result = _qspi_send_program_command(_program_instruction, buffer, addr, &written_bytes); + if ((result != QSPI_STATUS_OK) || (chunk != written_bytes)) { + tr_error("Write failed"); + program_failed = true; + status = SPINAND_BD_ERROR_DEVICE_ERROR; + goto exit_point; + } + } else { + uint8_t *p = (uint8_t*)_page_buf; + uint8_t ecc_steps = _ecc_steps; + + if (size < _page_size) { + tr_error("Write failed"); + program_failed = true; + status = SPINAND_BD_ERROR_DEVICE_ERROR; + goto exit_point; + } + + // prepare data + memset(_page_buf, 0xff, _page_size + _oob_size); + memcpy(_page_buf + offset, (uint8_t*)buffer, written_bytes); + + // calculate the software ECC + for (uint8_t i = 0; ecc_steps; ecc_steps--, i += _ecc_bytes, p += _ecc_size) { + _bch_calculate_ecc(p, _ecc_calc + i); + } + + // prepare ECC code + memcpy(_page_buf + _page_size + _ecc_layout_pos, _ecc_calc, _ecc_bytes * _ecc_steps); + + written_bytes = _page_size + _oob_size; + result = _qspi_send_program_command(_program_instruction, (void *)_page_buf, addr & SPINAND_PAGE_MASK, &written_bytes); + if ((result != QSPI_STATUS_OK)) { + tr_error("Write failed"); + program_failed = true; + status = SPINAND_BD_ERROR_DEVICE_ERROR; + goto exit_point; + } +} + + buffer = static_cast(buffer) + chunk; addr += SPINAND_PAGE_OFFSET; @@ -356,7 +460,7 @@ int SPINANDBlockDevice::erase(bd_addr_t addr, bd_size_t size) tr_debug("Erase - addr: %llu, size: %llu", addr, size); - if ((addr + size) > MBED_CONF_SPINAND_SPINAND_FLASH_SIZE) { + if ((addr + size) > _flash_size) { tr_error("Erase exceeds flash device size"); return SPINAND_BD_ERROR_INVALID_ERASE_PARAMS; } @@ -385,8 +489,8 @@ int SPINANDBlockDevice::erase(bd_addr_t addr, bd_size_t size) } addr += SPINAND_BLOCK_OFFSET; - if (size > MBED_CONF_SPINAND_SPINAND_BLOCK_SIZE) { - size -= MBED_CONF_SPINAND_SPINAND_BLOCK_SIZE; + if (size > _block_size) { + size -= _block_size; } else { size = 0; } @@ -412,23 +516,23 @@ int SPINANDBlockDevice::erase(bd_addr_t addr, bd_size_t size) bd_size_t SPINANDBlockDevice::get_read_size() const { // Return minimum read size in bytes for the device - return MBED_CONF_SPINAND_SPINAND_MIN_READ_SIZE; + return _page_size; } bd_size_t SPINANDBlockDevice::get_program_size() const { // Return minimum program/write size in bytes for the device - return MBED_CONF_SPINAND_SPINAND_MIN_PROG_SIZE; + return _page_size; } bd_size_t SPINANDBlockDevice::get_erase_size() const { - return MBED_CONF_SPINAND_SPINAND_BLOCK_SIZE; + return _block_size; } bd_size_t SPINANDBlockDevice::get_erase_size(bd_addr_t addr) const { - return MBED_CONF_SPINAND_SPINAND_BLOCK_SIZE; + return _block_size; } const char *SPINANDBlockDevice::get_type() const @@ -438,7 +542,7 @@ const char *SPINANDBlockDevice::get_type() const bd_size_t SPINANDBlockDevice::size() const { - return MBED_CONF_SPINAND_SPINAND_FLASH_SIZE; + return _flash_size; } int SPINANDBlockDevice::get_erase_value() const @@ -508,6 +612,64 @@ int SPINANDBlockDevice::remove_csel_instance(PinName csel) return status; } +bool SPINANDBlockDevice::_read_otp_onfi() +{ + uint8_t secur_reg = 0, onfi_table[256]; + + if (QSPI_STATUS_OK != _qspi_send_general_command(SPINAND_INST_GET_FEATURE, FEATURES_ADDR_SECURE_OTP, + NULL, 0, (char *) &secur_reg, 1)) { + tr_error("Reading Register failed"); + } + + secur_reg |= SPINAND_SECURE_BIT_OTP_EN; + if (QSPI_STATUS_OK != _qspi_send_general_command(SPINAND_INST_SET_FEATURE, FEATURES_ADDR_SECURE_OTP, + (char *) &secur_reg, 1, NULL, 0)) { + tr_error("Writing Security Register failed"); + return 0; + } + if (QSPI_STATUS_OK != _qspi_send_read_command(SPINAND_INST_READ_CACHE, onfi_table, 1<<_page_shift, sizeof(onfi_table))) { + tr_error("Writing Security Register failed"); + return 0; + } + if (onfi_table[0] == 'O' && onfi_table[1] == 'N' && onfi_table[2] == 'F' && onfi_table[3] == 'I') { + tr_info("ONFI table found\n"); + memcpy(_name, &onfi_table[32], sizeof(_name)); + _name[31] = 0; + _page_size = onfi_table[80] + (onfi_table[81] << 8) + (onfi_table[82] << 16); + _oob_size = onfi_table[84] + (onfi_table[85] << 8); + _page_num = onfi_table[92] + (onfi_table[93] << 8); + _block_num = onfi_table[96] + (onfi_table[97] << 8); + _block_size = _page_size * _page_num; + switch (_page_size) { + case 2048 : _page_shift = 12; break; + case 4096 : _page_shift = 13; break; + } + switch (_page_num) { + case 64 : _block_shift = _page_shift + 6; break; + case 128 : _block_shift = _page_shift + 7; break; + case 256 : _block_shift = _page_shift + 8; break; + } + _flash_size = _block_size * _block_num; + _ecc_bits = onfi_table[112]; + if (_ecc_bits > 0) { + _bch_init(_ecc_bits); + } else { + secur_reg |= SPINAND_SECURE_BIT_ECC_EN; + + if (QSPI_STATUS_OK != _qspi_send_general_command(SPINAND_INST_SET_FEATURE, FEATURES_ADDR_SECURE_OTP, + (char *) &secur_reg, 1, NULL, 0)) { + tr_error("Writing Register failed"); + } + } + } else { + tr_error("ONFI table not found"); + return 0; + } + secur_reg &= ~SPINAND_SECURE_BIT_OTP_EN; + _qspi_send_general_command(SPINAND_INST_SET_FEATURE, FEATURES_ADDR_SECURE_OTP,(char *) &secur_reg, 1, NULL, 0); + return 1; +} + int SPINANDBlockDevice::_set_quad_enable() { uint8_t secur_reg = 0; @@ -651,8 +813,8 @@ qspi_status_t SPINANDBlockDevice::_qspi_send_read_command(qspi_inst_t read_inst, data_width = QSPI_CFG_BUS_SINGLE; } else if (read_inst == SPINAND_INST_READ_CACHE2) { data_width = QSPI_CFG_BUS_DUAL; - } else if (read_inst == SPINAND_INST_READ_CACHE4) { - data_width = QSPI_CFG_BUS_QUAD; + } else { + data_width = QSPI_CFG_BUS_QUAD; //read_inst == SPINAND_INST_READ_CACHE4 } // Send read command to device driver @@ -664,7 +826,7 @@ qspi_status_t SPINANDBlockDevice::_qspi_send_read_command(qspi_inst_t read_inst, return status; } - if (QSPI_STATUS_OK != _qspi_send_general_command(SPINAND_INST_PAGE_READ, addr >> 12, NULL, 0, NULL, 0)) { + if (QSPI_STATUS_OK != _qspi_send_general_command(SPINAND_INST_PAGE_READ, addr >> _page_shift, NULL, 0, NULL, 0)) { tr_error("Read page from array failed"); } @@ -715,8 +877,8 @@ qspi_status_t SPINANDBlockDevice::_qspi_send_program_command(qspi_inst_t prog_in if (prog_inst == SPINAND_INST_PP_LOAD) { data_width = QSPI_CFG_BUS_SINGLE; - } else if (prog_inst == SPINAND_INST_4PP_LOAD) { - data_width = QSPI_CFG_BUS_QUAD; + } else { + data_width = QSPI_CFG_BUS_QUAD; //prog_inst == SPINAND_INST_4PP_LOAD } // Program load commands need 16 bit row address @@ -743,7 +905,7 @@ qspi_status_t SPINANDBlockDevice::_qspi_send_program_command(qspi_inst_t prog_in } // Program execute command - if (QSPI_STATUS_OK != _qspi_send_general_command(SPINAND_INST_PROGRAM_EXEC, addr >> 12, NULL, 0, NULL, 0)) { + if (QSPI_STATUS_OK != _qspi_send_general_command(SPINAND_INST_PROGRAM_EXEC, addr >> _page_shift, NULL, 0, NULL, 0)) { tr_error("Read page from array failed"); } @@ -774,7 +936,7 @@ qspi_status_t SPINANDBlockDevice::_qspi_send_erase_command(qspi_inst_t erase_ins } // Send erase command to driver - status = _qspi.command_transfer(erase_inst, (int)(addr >> 12), NULL, 0, NULL, 0); + status = _qspi.command_transfer(erase_inst, (int)(addr >> _page_shift), NULL, 0, NULL, 0); if (QSPI_STATUS_OK != status) { tr_error("QSPI Erase failed"); @@ -812,4 +974,103 @@ qspi_status_t SPINANDBlockDevice::_qspi_send_general_command(qspi_inst_t instruc return QSPI_STATUS_OK; } +void SPINANDBlockDevice::_bch_init(uint8_t ecc_bits) +{ + unsigned int m, t, i; + unsigned char *erased_page; + unsigned int eccsize = 512; + unsigned int eccbytes = 0; + + _ecc_bytes = eccbytes = DIV_ROUND_UP(ecc_bits * fls(8 * eccsize), 8); + _ecc_size = eccsize; + _ecc_steps = _page_size / eccsize; + _ecc_layout_pos = 2; // skip the bad block mark for Macronix spi nand + + m = fls(1 + 8 * eccsize); + t = (eccbytes * 8) / m; + + _nbc.bch = init_bch(m, t, 0); + if (!_nbc.bch) + return; + + /* verify that eccbytes has the expected value */ + if (_nbc.bch->ecc_bytes != eccbytes) { + tr_error("invalid eccbytes %u, should be %u\n", + eccbytes, _nbc.bch->ecc_bytes); + return; + } + + _page_buf = (uint8_t *)malloc(_page_size + _oob_size); + _ecc_calc = (uint8_t *)malloc(_ecc_steps * _ecc_bytes); + _ecc_code = (uint8_t *)malloc(_ecc_steps * _ecc_bytes); + _nbc.eccmask = (unsigned char*)malloc(eccbytes); + _nbc.errloc = (unsigned int*)malloc(t*sizeof(*_nbc.errloc)); + if (!_nbc.eccmask || !_nbc.errloc) { + return; + } + /* + * compute and store the inverted ecc of an erased ecc block + */ + erased_page = (unsigned char*)malloc(eccsize); + if (!erased_page) + return; + memset(_page_buf, 0xff, _page_size + _oob_size); + memset(erased_page, 0xff, eccsize); + memset(_nbc.eccmask, 0, eccbytes); + encode_bch(_nbc.bch, erased_page, eccsize, _nbc.eccmask); + free(erased_page); + + for (i = 0; i < eccbytes; i++) + _nbc.eccmask[i] ^= 0xff; +} + +void SPINANDBlockDevice::_bch_free() +{ + free_bch(_nbc.bch); + free(_nbc.errloc); + free(_nbc.eccmask); + free(_page_buf); + free(_ecc_calc); + free(_ecc_code); +} + +int SPINANDBlockDevice::_bch_calculate_ecc(unsigned char* buf, unsigned char* code) +{ + unsigned int i; + + memset(code, 0, _ecc_bytes); + + encode_bch(_nbc.bch, buf, _ecc_size, code); + + /* apply mask so that an erased page is a valid codeword */ + for (i = 0; i < _ecc_bytes; i++) { + code[i] ^= _nbc.eccmask[i]; + } + + return 0; +} + +int SPINANDBlockDevice::_bch_correct_data(unsigned char* buf, unsigned char* read_ecc, unsigned char* calc_ecc) +{ + unsigned int *errloc = _nbc.errloc; + int i, count; + + count = decode_bch(_nbc.bch, NULL, _ecc_size, read_ecc, calc_ecc, + NULL, errloc); + if (count > 0) { + for (i = 0; i < count; i++) { + if (errloc[i] < (_ecc_size*8)) + /* error is located in data, correct it */ + buf[(errloc[i] >> 3) ^ 3] ^= (1 << (errloc[i] & 7)); + /* else error in ecc, no action needed */ + + tr_error("corrected bitflip %04x:%d\n", (errloc[i]>>3)^3, errloc[i]&7); + } + } else if (count < 0) { + tr_error("ecc unrecoverable error\n"); + count = -EBADMSG; + } + return count; +} +