Merge pull request #14989 from LDong-Arm/sfdp_sector_maps_multi

SFDP: Add support for multiple configurations and sector maps
pull/15065/head
Jaeden Amero 2021-09-13 10:09:27 +01:00 committed by GitHub
commit 756830e776
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 1142 additions and 101 deletions

View File

@ -34,6 +34,7 @@ target_sources(mbed-core
source/I2CSlave.cpp
source/InterruptIn.cpp
source/MbedCRC.cpp
source/OSPI.cpp
source/PortIn.cpp
source/PortInOut.cpp
source/PortOut.cpp

View File

@ -115,12 +115,7 @@ public:
QSPI(const qspi_pinmap_t &pinmap, int mode = 0);
QSPI(const qspi_pinmap_t &&, int = 0) = delete; // prevent passing of temporary objects
virtual ~QSPI()
{
lock();
qspi_free(&_qspi);
unlock();
}
virtual ~QSPI();
/** Configure the data transmission format
*

View File

@ -92,6 +92,13 @@ QSPI::QSPI(const qspi_pinmap_t &pinmap, int mode) : _qspi()
MBED_ASSERT(success);
}
QSPI::~QSPI()
{
lock();
qspi_free(&_qspi);
unlock();
}
qspi_status_t QSPI::configure_format(qspi_bus_width_t inst_width, qspi_bus_width_t address_width, qspi_address_size_t address_size, qspi_bus_width_t alt_width, qspi_alt_size_t alt_size, qspi_bus_width_t data_width, int dummy_cycles)
{
// Check that alt_size/alt_width are a valid combination

View File

@ -5,6 +5,7 @@ if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME AND BUILD_TESTING)
if(BUILD_GREENTEA_TESTS)
# add greentea test
else()
add_subdirectory(COMPONENT_QSPIF)
add_subdirectory(tests/UNITTESTS)
endif()
endif()

View File

@ -327,7 +327,9 @@ private:
mbed::bd_size_t tx_length, const char *rx_buffer, mbed::bd_size_t rx_length);
// Send command to read from the SFDP table
int _ospi_send_read_sfdp_command(mbed::bd_addr_t addr, void *rx_buffer, mbed::bd_size_t rx_length);
int _ospi_send_read_sfdp_command(mbed::bd_addr_t addr, mbed::sfdp_cmd_addr_size_t addr_size,
uint8_t inst, uint8_t dummy_cycles,
void *rx_buffer, mbed::bd_size_t rx_length);
// Read the contents of status registers 1 and 2 into a buffer (buffer must have a length of 2)
ospi_status_t _ospi_read_status_registers(uint8_t *reg_buffer);
@ -366,11 +368,11 @@ private:
/* SFDP Detection and Parsing Functions */
/****************************************/
// Parse and Detect required Basic Parameters from Table
int _sfdp_parse_basic_param_table(mbed::Callback<int(mbed::bd_addr_t, void *, mbed::bd_size_t)> sfdp_reader,
int _sfdp_parse_basic_param_table(mbed::Callback<int(mbed::bd_addr_t, mbed::sfdp_cmd_addr_size_t, uint8_t, uint8_t, void *, mbed::bd_size_t)> sfdp_reader,
mbed::sfdp_hdr_info &sfdp_info);
// Parse and Detect 4-Byte Address Instruction Parameters from Table
int _sfdp_parse_4_byte_inst_table(mbed::Callback<int(mbed::bd_addr_t, void *, mbed::bd_size_t)> sfdp_reader,
int _sfdp_parse_4_byte_inst_table(mbed::Callback<int(mbed::bd_addr_t, mbed::sfdp_cmd_addr_size_t, uint8_t, uint8_t, void *, mbed::bd_size_t)> sfdp_reader,
mbed::sfdp_hdr_info &sfdp_info);
// Detect the soft reset protocol and reset - returns error if soft reset is not supported

View File

@ -921,12 +921,19 @@ int OSPIFBlockDevice::remove_csel_instance(PinName csel)
/*********************************************************/
/********** SFDP Parsing and Detection Functions *********/
/*********************************************************/
int OSPIFBlockDevice::_sfdp_parse_basic_param_table(Callback<int(bd_addr_t, void *, bd_size_t)> sfdp_reader,
int OSPIFBlockDevice::_sfdp_parse_basic_param_table(Callback<int(bd_addr_t, mbed::sfdp_cmd_addr_size_t, uint8_t, uint8_t, void *, bd_size_t)> sfdp_reader,
sfdp_hdr_info &sfdp_info)
{
uint8_t param_table[SFDP_BASIC_PARAMS_TBL_SIZE]; /* Up To 20 DWORDS = 80 Bytes */
int status = sfdp_reader(sfdp_info.bptbl.addr, param_table, sfdp_info.bptbl.size);
int status = sfdp_reader(
sfdp_info.bptbl.addr,
SFDP_READ_CMD_ADDR_TYPE,
SFDP_READ_CMD_INST,
SFDP_READ_CMD_DUMMY_CYCLES,
param_table,
sfdp_info.bptbl.size
);
if (status != OSPI_STATUS_OK) {
tr_error("Init - Read SFDP First Table Failed");
return -1;
@ -1383,12 +1390,19 @@ int OSPIFBlockDevice::_sfdp_detect_reset_protocol_and_reset(uint8_t *basic_param
return status;
}
int OSPIFBlockDevice::_sfdp_parse_4_byte_inst_table(Callback<int(bd_addr_t, void *, bd_size_t)> sfdp_reader,
int OSPIFBlockDevice::_sfdp_parse_4_byte_inst_table(Callback<int(mbed::bd_addr_t, mbed::sfdp_cmd_addr_size_t, uint8_t, uint8_t, void *, mbed::bd_size_t)> sfdp_reader,
sfdp_hdr_info &sfdp_info)
{
uint8_t four_byte_inst_table[SFDP_DEFAULT_4_BYTE_INST_TABLE_SIZE_BYTES]; /* Up To 2 DWORDS = 8 Bytes */
int status = sfdp_reader(sfdp_info.fbatbl.addr, four_byte_inst_table, sfdp_info.fbatbl.size);
int status = sfdp_reader(
sfdp_info.fbatbl.addr,
SFDP_READ_CMD_ADDR_TYPE,
SFDP_READ_CMD_INST,
SFDP_READ_CMD_DUMMY_CYCLES,
four_byte_inst_table,
sfdp_info.fbatbl.size
);
if (status != OSPI_STATUS_OK) {
tr_error("Init - Read SFDP Four Byte Inst Table Failed");
return -1;
@ -1823,13 +1837,39 @@ ospi_status_t OSPIFBlockDevice::_ospi_send_general_command(ospi_inst_t instructi
return OSPI_STATUS_OK;
}
int OSPIFBlockDevice::_ospi_send_read_sfdp_command(bd_addr_t addr, void *rx_buffer, bd_size_t rx_length)
int OSPIFBlockDevice::_ospi_send_read_sfdp_command(mbed::bd_addr_t addr, mbed::sfdp_cmd_addr_size_t addr_size,
uint8_t inst, uint8_t dummy_cycles,
void *rx_buffer, mbed::bd_size_t rx_length)
{
size_t rx_len = rx_length;
uint8_t *rx_buffer_tmp = (uint8_t *)rx_buffer;
// Set default here to avoid uninitialized variable warning
ospi_address_size_t address_size = _address_size;
int address = addr;
switch (addr_size) {
case SFDP_CMD_ADDR_3_BYTE:
address_size = OSPI_CFG_ADDR_SIZE_24;
break;
case SFDP_CMD_ADDR_4_BYTE:
address_size = OSPI_CFG_ADDR_SIZE_32;
break;
case SFDP_CMD_ADDR_SIZE_VARIABLE: // use current setting
break;
case SFDP_CMD_ADDR_NONE: // no address in command
address = static_cast<int>(OSPI_NO_ADDRESS_COMMAND);
break;
default:
tr_error("Invalid SFDP command address size: 0x%02X", addr_size);
return -1;
}
if (dummy_cycles == SFDP_CMD_DUMMY_CYCLES_VARIABLE) {
// use current setting
dummy_cycles = _dummy_cycles;
}
// SFDP read instruction requires 1-1-1 bus mode with 8 dummy cycles and a 3-byte address
ospi_status_t status = _ospi.configure_format(OSPI_CFG_BUS_SINGLE, OSPI_CFG_INST_SIZE_8, OSPI_CFG_BUS_SINGLE, OSPI_CFG_ADDR_SIZE_24, OSPI_CFG_BUS_SINGLE, 0, OSPI_CFG_BUS_SINGLE, OSPIF_RSFDP_DUMMY_CYCLES);
ospi_status_t status = _ospi.configure_format(OSPI_CFG_BUS_SINGLE, OSPI_CFG_INST_SIZE_8, OSPI_CFG_BUS_SINGLE, address_size, OSPI_CFG_BUS_SINGLE, 0, OSPI_CFG_BUS_SINGLE, dummy_cycles);
if (OSPI_STATUS_OK != status) {
tr_error("_ospi_configure_format failed");
return status;
@ -1837,7 +1877,8 @@ int OSPIFBlockDevice::_ospi_send_read_sfdp_command(bd_addr_t addr, void *rx_buff
// Don't check the read status until after we've configured the format back to 1-1-1, to avoid leaving the interface in an
// incorrect state if the read fails.
status = _ospi.read(OSPIF_INST_RSFDP, -1, (unsigned int) addr, (char *) rx_buffer, &rx_len);
size_t rx_len = rx_length;
status = _ospi.read(inst, -1, address, static_cast<char *>(rx_buffer), &rx_len);
ospi_status_t format_status = _ospi.configure_format(OSPI_CFG_BUS_SINGLE, OSPI_CFG_INST_SIZE_8, OSPI_CFG_BUS_SINGLE, _address_size, OSPI_CFG_BUS_SINGLE, 0, OSPI_CFG_BUS_SINGLE, 0);
// All commands other than Read and RSFDP use default 1-1-1 bus mode (Program/Erase are constrained by flash memory performance more than bus performance)

View File

@ -11,3 +11,7 @@ target_sources(mbed-storage-qspif
INTERFACE
source/QSPIFBlockDevice.cpp
)
if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME AND BUILD_TESTING)
add_subdirectory(UNITTESTS)
endif()

View File

@ -0,0 +1 @@
*

View File

@ -0,0 +1,39 @@
# Copyright (c) 2021 Arm Limited. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
include(GoogleTest)
add_executable(qspif-unittest)
target_compile_definitions(qspif-unittest
PRIVATE
DEVICE_QSPI=1
MBED_CONF_QSPIF_QSPI_MIN_READ_SIZE=1
MBED_CONF_QSPIF_QSPI_MIN_PROG_SIZE=1
)
target_include_directories(qspif-unittest
PRIVATE
${mbed-os_SOURCE_DIR}/storage/blockdevice/COMPONENT_QSPIF/include
)
target_sources(qspif-unittest
PRIVATE
${mbed-os_SOURCE_DIR}/storage/blockdevice/COMPONENT_QSPIF/source/QSPIFBlockDevice.cpp
test_QSPIFBlockDevice.cpp
)
target_link_libraries(qspif-unittest
PRIVATE
mbed-headers-blockdevice
mbed-headers-drivers
mbed-headers-platform
mbed-headers-rtos
mbed-stubs-blockdevice
mbed-stubs-platform
mbed-stubs-drivers
mbed-stubs-rtos
gmock_main
)
gtest_discover_tests(qspif-unittest PROPERTIES LABELS "storage")

View File

@ -0,0 +1,336 @@
/* Copyright (c) 2021 Arm Limited
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <cassert>
#include "gtest/gtest.h"
#include "gmock/gmock.h"
#include "QSPIF/QSPIFBlockDevice.h"
// S25FS512S's CR3NV register needs a quirk
static const int S25FS512S_CR1NV = 0x2;
static const int S25FS512S_CR3NV = 0x4;
// JEDEC Manufacturer and Device ID, set by the test cases
static uint8_t const *device_id;
namespace mbed {
/**
* Fake implementation of mbed::QSPI class for QSPIFBlockDevice unit tests.
*
* Note: Data output by this is either dummy or based on Cypress S25FS512S.
* We can't use gMock for this because
* - The entire mbed::QSPI is non-virtual. Mocks are derived classes, and
* when a derived class instance is used as an instance of its base class,
* non-virtual members of the base class get used.
* - QSPIFBlockDevice's member _qspi (an instance of mbed::QSPI) is a value
* instead of a reference, in order to support initializing from pins directly.
* Also, mbed::QSPI is declared as NonCopyable whose copy-constructor is deleted,
* so we can't copy a preexisting mbed::QSPI instance into QSPIFBlockDevice.
* There's no way to dependency inject a mock instance.
*/
QSPI::QSPI(PinName io0, PinName io1, PinName io2, PinName io3, PinName sclk, PinName ssel, int mode) : _qspi()
{
}
QSPI::~QSPI()
{
}
qspi_status_t QSPI::configure_format(qspi_bus_width_t inst_width, qspi_bus_width_t address_width, qspi_address_size_t address_size, qspi_bus_width_t alt_width, qspi_alt_size_t alt_size, qspi_bus_width_t data_width, int dummy_cycles)
{
return QSPI_STATUS_OK;
}
qspi_status_t QSPI::set_frequency(int hz)
{
return QSPI_STATUS_OK;
}
qspi_status_t QSPI::read(int address, char *rx_buffer, size_t *rx_length)
{
return QSPI_STATUS_OK;
}
qspi_status_t QSPI::write(int address, const char *tx_buffer, size_t *tx_length)
{
return QSPI_STATUS_OK;
}
qspi_status_t QSPI::read(qspi_inst_t instruction, int alt, int address, char *rx_buffer, size_t *rx_length)
{
if (!rx_buffer) {
return QSPI_STATUS_INVALID_PARAMETER;
}
switch (address) {
// CR1NV and CR3NV registers' factory default values are both 0x0
case S25FS512S_CR1NV:
case S25FS512S_CR3NV:
if (!rx_length || *rx_length < 1) {
return QSPI_STATUS_INVALID_PARAMETER;
}
rx_buffer[0] = 0x00;
break;
}
switch (instruction) {
case mbed::SFDP_READ_CMD_INST: // read SFDP table
if (!rx_length || *rx_length < SFDP_BASIC_PARAMS_TBL_SIZE) {
return QSPI_STATUS_INVALID_PARAMETER;
}
// set dummy data (zeros), to avoid warning of uninitialized
// data from Valgrind and inconsistent test behavior.
memset(rx_buffer, 0x00, *rx_length);
// soft reset needs one instruction to enable reset,
// another instruction to perform reset
rx_buffer[61] = 0x30;
break;
}
return QSPI_STATUS_OK;
}
qspi_status_t QSPI::write(qspi_inst_t instruction, int alt, int address, const char *tx_buffer, size_t *tx_length)
{
return QSPI_STATUS_OK;
}
qspi_status_t QSPI::command_transfer(qspi_inst_t instruction, int address, const char *tx_buffer, size_t tx_length, const char *rx_buffer, size_t rx_length)
{
static char status_registers[2];
switch (instruction) {
case 0x01: // write status registers
if (!tx_buffer || tx_length < 2) {
return QSPI_STATUS_INVALID_PARAMETER;
}
memcpy(status_registers, tx_buffer, tx_length);
break;
case 0x05: // read status register 1
if (!rx_buffer || rx_length < 1) {
return QSPI_STATUS_INVALID_PARAMETER;
}
const_cast<char *>(rx_buffer)[0] = status_registers[0];
break;
case 0x35: // read status register 2
if (!rx_buffer || rx_length < 1) {
return QSPI_STATUS_INVALID_PARAMETER;
}
const_cast<char *>(rx_buffer)[0] = status_registers[1];
break;
case 0x06: // set write enabled bit
status_registers[0] |= 0x2;
break;
case 0x9F: // read Manufacturer and JDEC Device ID
assert(nullptr != device_id); // Test cases should set device_id
if (!rx_buffer || rx_length < 3) {
return QSPI_STATUS_INVALID_PARAMETER;
}
memcpy(const_cast<char *>(rx_buffer), device_id, 3);
break;
}
return QSPI_STATUS_OK;
}
void QSPI::lock()
{
}
void QSPI::unlock()
{
}
bool QSPI::_initialize()
{
return true;
}
bool QSPI::_initialize_direct()
{
return true;
}
void QSPI::_build_qspi_command(qspi_inst_t instruction, int address, int alt)
{
}
/**
* Fake implementation of SFDP functions.
* They can't be mocked with gMock which supports only class member functions,
* not free/global functions.
*/
static Callback<int(bd_addr_t, sfdp_cmd_addr_size_t, uint8_t, uint8_t, void *, bd_size_t)> test_sfdp_reader;
int sfdp_parse_headers(Callback<int(bd_addr_t, sfdp_cmd_addr_size_t, uint8_t, uint8_t, void *, bd_size_t)> sfdp_reader, sfdp_hdr_info &sfdp_info)
{
// The SFDP Basic Parameters Table size is used as a QSPI buffer
// size, so it must be set.
sfdp_info.bptbl.size = SFDP_BASIC_PARAMS_TBL_SIZE;
return 0;
}
int sfdp_parse_sector_map_table(Callback<int(bd_addr_t, sfdp_cmd_addr_size_t, uint8_t, uint8_t, void *, bd_size_t)> sfdp_reader, sfdp_hdr_info &sfdp_info)
{
// The real implementation of this function queries
// the CR3NV register on S25FS512S.
test_sfdp_reader = sfdp_reader;
return 0;
}
size_t sfdp_detect_page_size(uint8_t *bptbl_ptr, size_t bptbl_size)
{
return 0;
}
int sfdp_detect_erase_types_inst_and_size(uint8_t *bptbl_ptr, sfdp_hdr_info &sfdp_info)
{
return 0;
}
int sfdp_find_addr_region(bd_addr_t offset, const sfdp_hdr_info &sfdp_info)
{
return 0;
}
int sfdp_iterate_next_largest_erase_type(uint8_t bitfield,
bd_size_t size,
bd_addr_t offset,
int region,
const sfdp_smptbl_info &smptbl)
{
return 0;
}
int sfdp_detect_device_density(uint8_t *bptbl_ptr, sfdp_bptbl_info &bptbl_info)
{
return 0;
}
int sfdp_detect_addressability(uint8_t *bptbl_ptr, sfdp_bptbl_info &bptbl_info)
{
return 0;
}
} // namespace mbed
class TestQSPIFBlockDevice : public testing::Test {
protected:
TestQSPIFBlockDevice()
: bd(PinName(0), PinName(1), PinName(2), PinName(3), PinName(4), PinName(5)) // fake pins
{
}
virtual void TearDown()
{
device_id = nullptr;
}
QSPIFBlockDevice bd;
};
/**
* Test that the quirk on the reported values of the CR1NV and CR3NV
* registers is applied when the flash chip is S25FS512S.
*/
TEST_F(TestQSPIFBlockDevice, TestS25FS512SQuirkApplied)
{
// Use flash chip S25FS512S
const uint8_t id_S25FS512S[] { 0x01, 0x02, 0x20 };
device_id = id_S25FS512S;
EXPECT_EQ(QSPIF_BD_ERROR_OK, bd.init());
const uint8_t id_N25Q128A[] { 0x20, 0xBB, 0x18 };
// Read the CR1NV register
uint8_t CR1NV;
EXPECT_EQ(QSPIF_BD_ERROR_OK, mbed::test_sfdp_reader(
S25FS512S_CR1NV,
mbed::SFDP_CMD_ADDR_SIZE_VARIABLE,
0x65,
mbed::SFDP_CMD_DUMMY_CYCLES_VARIABLE,
&CR1NV,
sizeof(CR1NV)));
// Read the CR3NV register
uint8_t CR3NV;
EXPECT_EQ(QSPIF_BD_ERROR_OK, mbed::test_sfdp_reader(
S25FS512S_CR3NV,
mbed::SFDP_CMD_ADDR_SIZE_VARIABLE,
0x65,
mbed::SFDP_CMD_DUMMY_CYCLES_VARIABLE,
&CR3NV,
sizeof(CR3NV)));
// The hardware value of CR3NV[1] is 0 but S25FS512S's SFDP table
// expects it to be 1.
EXPECT_EQ(0x2, CR3NV & 0x02);
// The factory default configuration is CR1NV[2] == 0 and CR3NV[3] == 0
// (eight 4KB sectors overlaying the start of the flash) but we treat
// the flash chip as if CR1NV[2] == 0 and CR3NV[3] == 1 (no overlaying 4KB
// sectors), as Mbed OS does not support this type of flash layout.
EXPECT_EQ(0x0, CR1NV & 0x4);
EXPECT_EQ(0x8, CR3NV & 0x8);
// Deinitialization
EXPECT_EQ(QSPIF_BD_ERROR_OK, bd.deinit());
}
/**
* Test that the quirk on the reported values of the CR1NV and CR3NV
* registers is not applied when the flash chip is not S25FS512S.
* Note: Other flash chips most likely do not have CR1NV or CR3NV, but
* they might have something else readable at the same addresses.
*/
TEST_F(TestQSPIFBlockDevice, TestS25FS512SQuirkNotApplied)
{
// Use flash chip N25Q128A
const uint8_t id_N25Q128A[] { 0x20, 0xBB, 0x18 };
device_id = id_N25Q128A;
EXPECT_EQ(QSPIF_BD_ERROR_OK, bd.init());
// Read the value at the address of S25FS512S's CR1NV register,
// assuming this address is readable.
uint8_t CR1NV;
EXPECT_EQ(QSPIF_BD_ERROR_OK, mbed::test_sfdp_reader(
S25FS512S_CR1NV,
mbed::SFDP_CMD_ADDR_SIZE_VARIABLE,
0x65,
mbed::SFDP_CMD_DUMMY_CYCLES_VARIABLE,
&CR1NV,
sizeof(CR1NV)));
// Read the value at the address of S25FS512S's CR3NV register,
// assuming this address is readable.
uint8_t CR3NV;
EXPECT_EQ(QSPIF_BD_ERROR_OK, mbed::test_sfdp_reader(
S25FS512S_CR3NV,
mbed::SFDP_CMD_ADDR_SIZE_VARIABLE,
0x65,
mbed::SFDP_CMD_DUMMY_CYCLES_VARIABLE,
&CR3NV,
sizeof(CR3NV)));
// Values (0) reported by the fake QSPI::read() should be unmodifed
EXPECT_EQ(0, CR1NV);
EXPECT_EQ(0, CR3NV);
// Deinitialization
EXPECT_EQ(QSPIF_BD_ERROR_OK, bd.deinit());
}

View File

@ -277,7 +277,9 @@ private:
mbed::bd_size_t tx_length, const char *rx_buffer, mbed::bd_size_t rx_length);
// Send command to read from the SFDP table
int _qspi_send_read_sfdp_command(mbed::bd_addr_t addr, void *rx_buffer, mbed::bd_size_t rx_length);
int _qspi_send_read_sfdp_command(mbed::bd_addr_t addr, mbed::sfdp_cmd_addr_size_t addr_size,
uint8_t inst, uint8_t dummy_cycles,
void *rx_buffer, mbed::bd_size_t rx_length);
// Read the contents of status registers 1 and 2 into a buffer (buffer must have a length of 2)
qspi_status_t _qspi_read_status_registers(uint8_t *reg_buffer);
@ -313,7 +315,7 @@ private:
/* SFDP Detection and Parsing Functions */
/****************************************/
// Parse and Detect required Basic Parameters from Table
int _sfdp_parse_basic_param_table(mbed::Callback<int(mbed::bd_addr_t, void *, mbed::bd_size_t)> sfdp_reader,
int _sfdp_parse_basic_param_table(mbed::Callback<int(bd_addr_t, mbed::sfdp_cmd_addr_size_t, uint8_t, uint8_t, void *, bd_size_t)> sfdp_reader,
mbed::sfdp_hdr_info &sfdp_info);
// Detect the soft reset protocol and reset - returns error if soft reset is not supported
@ -374,6 +376,9 @@ private:
bool _needs_fast_mode;
// S25FS512S needs a quirk
bool _S25FS512S_quirk;
// Clear block protection
qspif_clear_protection_method_t _clear_protection_method;

View File

@ -17,7 +17,7 @@
#include "blockdevice/internal/SFDP.h"
#include "platform/Callback.h"
#include "QSPIFBlockDevice.h"
#include "QSPIF/QSPIFBlockDevice.h"
#include <string.h>
#include "rtos/ThisThread.h"
@ -25,7 +25,7 @@
#define MBED_CONF_MBED_TRACE_ENABLE 0
#endif
#include "mbed_trace.h"
#include "mbed-trace/mbed_trace.h"
#define TRACE_GROUP "QSPIF"
using namespace std::chrono;
@ -176,6 +176,9 @@ QSPIFBlockDevice::QSPIFBlockDevice(PinName io0, PinName io1, PinName io2, PinNam
// Set default 4-byte addressing extension register write instruction
_attempt_4_byte_addressing = true;
_4byte_msb_reg_write_inst = QSPIF_INST_4BYTE_REG_WRITE_DEFAULT;
// Quirk for Cypress S25FS512S
_S25FS512S_quirk = false;
}
int QSPIFBlockDevice::init()
@ -614,12 +617,19 @@ int QSPIFBlockDevice::remove_csel_instance(PinName csel)
/*********************************************************/
/********** SFDP Parsing and Detection Functions *********/
/*********************************************************/
int QSPIFBlockDevice::_sfdp_parse_basic_param_table(Callback<int(bd_addr_t, void *, bd_size_t)> sfdp_reader,
int QSPIFBlockDevice::_sfdp_parse_basic_param_table(Callback<int(bd_addr_t, mbed::sfdp_cmd_addr_size_t, uint8_t, uint8_t, void *, bd_size_t)> sfdp_reader,
sfdp_hdr_info &sfdp_info)
{
uint8_t param_table[SFDP_BASIC_PARAMS_TBL_SIZE]; /* Up To 20 DWORDS = 80 Bytes */
int status = sfdp_reader(sfdp_info.bptbl.addr, param_table, sfdp_info.bptbl.size);
int status = sfdp_reader(
sfdp_info.bptbl.addr,
SFDP_READ_CMD_ADDR_TYPE,
SFDP_READ_CMD_INST,
SFDP_READ_CMD_DUMMY_CYCLES,
param_table,
sfdp_info.bptbl.size
);
if (status != QSPI_STATUS_OK) {
tr_error("Init - Read SFDP First Table Failed");
return -1;
@ -1092,6 +1102,23 @@ int QSPIFBlockDevice::_handle_vendor_quirks()
tr_debug("Applying quirks for ISSI");
_num_status_registers = 1;
break;
case 0x01:
if (vendor_device_ids[1] == 0x02 && vendor_device_ids[2] == 0x20) {
tr_debug("Applying quirks for Cypress S25FS512S");
// On a Cypress S25FS512S flash chip
// * The SFDP table expects the register bitfield CR3NV[1] to be 1
// but its actual value on the hardware is 0. In order for SFDP parsing
// to work, the quirk reports CR3NV[1] as 1.
// * All three possible configurations support 256KB sectors across
// the entire chip. But when CR3NV[3] is 0, eight 4KB sectors overlay
// either the first 32KB or the last 32KB of the chip, whereas when
// CR3NV[3] is 1 there are no overlaying 4KB sectors. Mbed OS can't
// handle this type of overlay, so the quirk reports CR3NV[3] as 1 to
// let the code treat the chip as if it has no overlay. (Also CR1NV[2]
// is required to be 0 when CR3NV[3] is 1.)
_S25FS512S_quirk = true;
}
break;
}
return 0;
@ -1405,23 +1432,53 @@ qspi_status_t QSPIFBlockDevice::_qspi_send_general_command(qspi_inst_t instructi
return QSPI_STATUS_OK;
}
int QSPIFBlockDevice::_qspi_send_read_sfdp_command(bd_addr_t addr, void *rx_buffer, bd_size_t rx_length)
int QSPIFBlockDevice::_qspi_send_read_sfdp_command(mbed::bd_addr_t addr, mbed::sfdp_cmd_addr_size_t addr_size,
uint8_t inst, uint8_t dummy_cycles,
void *rx_buffer, mbed::bd_size_t rx_length)
{
size_t rx_len = rx_length;
// Set default here to avoid uninitialized variable warning
qspi_address_size_t address_size = _address_size;
int address = addr;
switch (addr_size) {
case SFDP_CMD_ADDR_3_BYTE:
address_size = QSPI_CFG_ADDR_SIZE_24;
break;
case SFDP_CMD_ADDR_4_BYTE:
address_size = QSPI_CFG_ADDR_SIZE_32;
break;
case SFDP_CMD_ADDR_SIZE_VARIABLE: // use current setting
break;
case SFDP_CMD_ADDR_NONE: // no address in command
address = static_cast<int>(QSPI_NO_ADDRESS_COMMAND);
break;
default:
tr_error("Invalid SFDP command address size: 0x%02X", addr_size);
return -1;
}
// SFDP read instruction requires 1-1-1 bus mode with 8 dummy cycles and a 3-byte address
qspi_status_t status = _qspi.configure_format(QSPI_CFG_BUS_SINGLE, QSPI_CFG_BUS_SINGLE, QSPI_CFG_ADDR_SIZE_24, QSPI_CFG_BUS_SINGLE, 0, QSPI_CFG_BUS_SINGLE, QSPIF_RSFDP_DUMMY_CYCLES);
if (dummy_cycles == SFDP_CMD_DUMMY_CYCLES_VARIABLE) {
// use current setting
dummy_cycles = _dummy_cycles;
}
qspi_status_t status = _qspi.configure_format(QSPI_CFG_BUS_SINGLE, QSPI_CFG_BUS_SINGLE,
address_size, QSPI_CFG_BUS_SINGLE,
0, QSPI_CFG_BUS_SINGLE, dummy_cycles);
if (QSPI_STATUS_OK != status) {
tr_error("_qspi_configure_format failed");
return status;
}
// Don't check the read status until after we've configured the format back to 1-1-1, to avoid leaving the interface in an
// incorrect state if the read fails.
status = _qspi.read(QSPIF_INST_RSFDP, -1, (unsigned int) addr, (char *) rx_buffer, &rx_len);
// Don't check the read status until after we've configured the format back to 1-1-1,
// to avoid leaving the interface in an incorrect state if the read fails.
size_t rx_len = rx_length;
status = _qspi.read(inst, -1, address, static_cast<char *>(rx_buffer), &rx_len);
qspi_status_t format_status = _qspi.configure_format(QSPI_CFG_BUS_SINGLE, QSPI_CFG_BUS_SINGLE, _address_size, QSPI_CFG_BUS_SINGLE, 0, QSPI_CFG_BUS_SINGLE, 0);
// All commands other than Read and RSFDP use default 1-1-1 bus mode (Program/Erase are constrained by flash memory performance more than bus performance)
qspi_status_t format_status = _qspi.configure_format(QSPI_CFG_BUS_SINGLE, QSPI_CFG_BUS_SINGLE,
address_size, QSPI_CFG_BUS_SINGLE,
0, QSPI_CFG_BUS_SINGLE, 0);
// All commands other than Read and RSFDP use default 1-1-1 bus mode
// (Program/Erase are constrained by flash memory performance more than bus performance)
if (QSPI_STATUS_OK != format_status) {
tr_error("_qspi_configure_format failed");
return format_status;
@ -1432,6 +1489,20 @@ int QSPIFBlockDevice::_qspi_send_read_sfdp_command(bd_addr_t addr, void *rx_buff
return status;
}
// Handle S25FS512S quirk.
const mbed::bd_addr_t S25FS512S_CR1NV = 0x2;
const mbed::bd_addr_t S25FS512S_CR3NV = 0x4;
if (_S25FS512S_quirk) {
if (addr == S25FS512S_CR3NV) {
// If we reach here, rx_buffer is guaranteed to be non-null
// because it's been checked by _qspi.read() above.
static_cast<uint8_t *>(rx_buffer)[0] |= (1 << 1);
static_cast<uint8_t *>(rx_buffer)[0] |= (1 << 3);
} else if (addr == S25FS512S_CR1NV) {
static_cast<uint8_t *>(rx_buffer)[0] &= ~(1 << 2);
}
}
return QSPI_STATUS_OK;
}

View File

@ -221,10 +221,12 @@ private:
/* SFDP Detection and Parsing Functions */
/****************************************/
// Send SFDP Read command to Driver
int _spi_send_read_sfdp_command(mbed::bd_addr_t addr, void *rx_buffer, mbed::bd_size_t rx_length);
int _spi_send_read_sfdp_command(mbed::bd_addr_t addr, mbed::sfdp_cmd_addr_size_t addr_size,
uint8_t inst, uint8_t dummy_cycles,
void *rx_buffer, mbed::bd_size_t rx_length);
// Parse and Detect required Basic Parameters from Table
int _sfdp_parse_basic_param_table(mbed::Callback<int(mbed::bd_addr_t, void *, mbed::bd_size_t)> sfdp_reader,
int _sfdp_parse_basic_param_table(mbed::Callback<int(bd_addr_t, mbed::sfdp_cmd_addr_size_t, uint8_t, uint8_t, void *, bd_size_t)> sfdp_reader,
mbed::sfdp_hdr_info &hdr_info);
// Detect fastest read Bus mode supported by device

View File

@ -497,13 +497,32 @@ spif_bd_error SPIFBlockDevice::_spi_send_read_command(int read_inst, uint8_t *bu
return SPIF_BD_ERROR_OK;
}
int SPIFBlockDevice::_spi_send_read_sfdp_command(bd_addr_t addr, void *rx_buffer, bd_size_t rx_length)
int SPIFBlockDevice::_spi_send_read_sfdp_command(mbed::bd_addr_t addr, mbed::sfdp_cmd_addr_size_t addr_size,
uint8_t inst, uint8_t dummy_cycles,
void *rx_buffer, mbed::bd_size_t rx_length)
{
// Set 1-1-1 bus mode for SFDP header parsing
// Initial SFDP read tables are read with 8 dummy cycles
_dummy_and_mode_cycles = 8;
switch (addr_size) {
case SFDP_CMD_ADDR_3_BYTE:
_address_size = SPIF_ADDR_SIZE_3_BYTES;
break;
case SFDP_CMD_ADDR_4_BYTE:
_address_size = SPIF_ADDR_SIZE_4_BYTES;
break;
case SFDP_CMD_ADDR_SIZE_VARIABLE: // use current setting
break;
case SFDP_CMD_ADDR_NONE: // no address in command
addr = static_cast<int>(SPI_NO_ADDRESS_COMMAND);
break;
default:
tr_error("Invalid SFDP command address size: 0x%02X", addr_size);
return -1;
}
int status = _spi_send_read_command(SPIF_SFDP, (uint8_t *)rx_buffer, addr, rx_length);
if (dummy_cycles != SFDP_CMD_DUMMY_CYCLES_VARIABLE) {
_dummy_and_mode_cycles = dummy_cycles;
}
int status = _spi_send_read_command(inst, static_cast<uint8_t *>(rx_buffer), addr, rx_length);
if (status < 0) {
tr_error("_spi_send_read_sfdp_command failed");
}
@ -588,12 +607,19 @@ spif_bd_error SPIFBlockDevice::_spi_send_general_command(int instruction, bd_add
/*********************************************************/
/********** SFDP Parsing and Detection Functions *********/
/*********************************************************/
int SPIFBlockDevice::_sfdp_parse_basic_param_table(Callback<int(bd_addr_t, void *, bd_size_t)> sfdp_reader,
mbed::sfdp_hdr_info &sfdp_info)
int SPIFBlockDevice::_sfdp_parse_basic_param_table(Callback<int(bd_addr_t, mbed::sfdp_cmd_addr_size_t, uint8_t, uint8_t, void *, bd_size_t)> sfdp_reader,
sfdp_hdr_info &sfdp_info)
{
uint8_t param_table[SFDP_BASIC_PARAMS_TBL_SIZE]; /* Up To 20 DWORDS = 80 Bytes */
int status = sfdp_reader(sfdp_info.bptbl.addr, param_table, sfdp_info.bptbl.size);
int status = sfdp_reader(
sfdp_info.bptbl.addr,
SFDP_READ_CMD_ADDR_TYPE,
SFDP_READ_CMD_INST,
SFDP_READ_CMD_DUMMY_CYCLES,
param_table,
sfdp_info.bptbl.size
);
if (status != SPIF_BD_ERROR_OK) {
tr_error("init - Read SFDP First Table Failed");
return -1;

View File

@ -47,6 +47,22 @@ constexpr int SFDP_ERASE_BITMASK_ALL = 0x0F; ///< Erase type All
constexpr int SFDP_MAX_NUM_OF_ERASE_TYPES = 4; ///< Maximum number of different erase types (erase granularity)
// Size of a command specified by SFDP
enum sfdp_cmd_addr_size_t {
SFDP_CMD_ADDR_NONE = 0x00, // No address in command
SFDP_CMD_ADDR_3_BYTE = 0x01, // 3-byte address
SFDP_CMD_ADDR_4_BYTE = 0x02, // 4-byte address
SFDP_CMD_ADDR_SIZE_VARIABLE = 0x03 // Address size from current setting
};
// Parameters for SFDP Read command
constexpr sfdp_cmd_addr_size_t SFDP_READ_CMD_ADDR_TYPE = SFDP_CMD_ADDR_3_BYTE; // Read SFDP has 3-byte address
constexpr uint8_t SFDP_READ_CMD_INST = 0x5A; // Read SFDP instruction
constexpr uint8_t SFDP_READ_CMD_DUMMY_CYCLES = 8; // READ SFDP dummy cycles
// Special value from SFDP for using dummy cycles from current setting
constexpr uint8_t SFDP_CMD_DUMMY_CYCLES_VARIABLE = 0xF;
/** JEDEC Basic Flash Parameter Table info */
struct sfdp_bptbl_info {
uint32_t addr; ///< Address
@ -92,7 +108,7 @@ struct sfdp_hdr_info {
*
* @return MBED_SUCCESS on success, negative error code on failure
*/
int sfdp_parse_headers(Callback<int(bd_addr_t, void *, bd_size_t)> sfdp_reader, sfdp_hdr_info &sfdp_info);
int sfdp_parse_headers(Callback<int(bd_addr_t, sfdp_cmd_addr_size_t, uint8_t, uint8_t, void *, bd_size_t)> sfdp_reader, sfdp_hdr_info &sfdp_info);
/** Parse Sector Map Parameter Table
* Retrieves the table from a device and parses the information contained by the table
@ -102,7 +118,7 @@ int sfdp_parse_headers(Callback<int(bd_addr_t, void *, bd_size_t)> sfdp_reader,
*
* @return MBED_SUCCESS on success, negative error code on failure
*/
int sfdp_parse_sector_map_table(Callback<int(bd_addr_t, void *, bd_size_t)> sfdp_reader, sfdp_hdr_info &sfdp_info);
int sfdp_parse_sector_map_table(Callback<int(bd_addr_t, sfdp_cmd_addr_size_t, uint8_t, uint8_t, void *, bd_size_t)> sfdp_reader, sfdp_hdr_info &sfdp_info);
/** Detect page size used for writing on flash
*

View File

@ -181,7 +181,7 @@ int sfdp_parse_single_param_header(sfdp_prm_hdr *phdr_ptr, sfdp_hdr_info &hdr_in
return 0;
}
int sfdp_parse_headers(Callback<int(bd_addr_t, void *, bd_size_t)> sfdp_reader, sfdp_hdr_info &sfdp_info)
int sfdp_parse_headers(Callback<int(bd_addr_t, sfdp_cmd_addr_size_t, uint8_t, uint8_t, void *, bd_size_t)> sfdp_reader, sfdp_hdr_info &sfdp_info)
{
bd_addr_t addr = 0x0;
int number_of_param_headers = 0;
@ -191,7 +191,14 @@ int sfdp_parse_headers(Callback<int(bd_addr_t, void *, bd_size_t)> sfdp_reader,
data_length = SFDP_HEADER_SIZE;
uint8_t sfdp_header[SFDP_HEADER_SIZE];
int status = sfdp_reader(addr, sfdp_header, data_length);
int status = sfdp_reader(
addr,
SFDP_READ_CMD_ADDR_TYPE,
SFDP_READ_CMD_INST,
SFDP_READ_CMD_DUMMY_CYCLES,
sfdp_header,
data_length
);
if (status < 0) {
tr_error("Retrieving SFDP Header failed");
return -1;
@ -213,7 +220,14 @@ int sfdp_parse_headers(Callback<int(bd_addr_t, void *, bd_size_t)> sfdp_reader,
// Loop over Param Headers and parse them (currently supports Basic Param Table and Sector Region Map Table)
for (int idx = 0; idx < number_of_param_headers; idx++) {
status = sfdp_reader(addr, param_header, data_length);
status = sfdp_reader(
addr,
SFDP_READ_CMD_ADDR_TYPE,
SFDP_READ_CMD_INST,
SFDP_READ_CMD_DUMMY_CYCLES,
param_header,
data_length
);
if (status < 0) {
tr_error("Retrieving a parameter header %d failed", idx + 1);
return -1;
@ -231,7 +245,111 @@ int sfdp_parse_headers(Callback<int(bd_addr_t, void *, bd_size_t)> sfdp_reader,
return 0;
}
int sfdp_parse_sector_map_table(Callback<int(bd_addr_t, void *, bd_size_t)> sfdp_reader, sfdp_hdr_info &sfdp_info)
static constexpr size_t min_descriptor_size = 8; // two DWORDs
static inline bool is_last_descriptor(const uint8_t *descriptor)
{
// Last descriptor of the current type (detection command/sector map)
MBED_ASSERT(nullptr != descriptor);
return descriptor[0] & 0x01;
}
static inline bool is_sector_map_descriptor(const uint8_t *descriptor)
{
// true - sector map descriptor
// false - configuration detection command descriptor
MBED_ASSERT(nullptr != descriptor);
return descriptor[0] & 0x02;
}
static int sfdp_detect_sector_map_configuration(
Callback<int(bd_addr_t, sfdp_cmd_addr_size_t, uint8_t, uint8_t, void *, bd_size_t)> sfdp_reader,
sfdp_hdr_info &sfdp_info,
uint8_t *&descriptor,
const uint8_t *table_end,
uint8_t &config)
{
config = 0;
// If the table starts with a sector map descriptor instead of a configuration
// detection command descriptor, this device has only one configuration (i.e. is
// not configurable) with ID equal to 0.
if (is_sector_map_descriptor(descriptor)) {
return 0;
}
// Loop through all configuration detection descriptors and run detection commands
while (!is_sector_map_descriptor(descriptor) && (descriptor + min_descriptor_size <= table_end)) {
uint8_t instruction = descriptor[1];
uint8_t dummy_cycles = descriptor[2] & 0x0F;
auto addr_size = static_cast<sfdp_cmd_addr_size_t>(descriptor[2] >> 6);
uint8_t mask = descriptor[3];
uint32_t cmd_addr;
memcpy(&cmd_addr, &descriptor[4], sizeof(cmd_addr)); // last 32 bits of the descriptor
uint8_t rx;
int status = sfdp_reader(cmd_addr, addr_size, instruction, dummy_cycles, &rx, sizeof(rx));
if (status < 0) {
tr_error("Sector Map: Configuration detection command failed");
return -1;
}
// Shift existing bits to the left, so we can add the newly detected bit
config <<= 1;
// The mask may apply to any bit of rx, so we can't directly combine
// (rx & mask) with config. Instead, treat (rx & mask) as a boolean.
if (rx & mask) {
config |= 0x01;
}
if (is_last_descriptor(descriptor)) {
// We've processed the last configuration detection command descriptor
descriptor += min_descriptor_size; // Increment the descriptor for the caller
return 0;
}
descriptor += min_descriptor_size; // next descriptor
}
tr_error("Sector Map: Incomplete configuration detection command descriptors");
return -1;
}
static int sfdp_locate_sector_map_by_config(
const uint8_t config,
sfdp_hdr_info &sfdp_info,
uint8_t *&descriptor,
const uint8_t *table_end)
{
// The size of a sector map descriptor depends on the number of regions. Before
// the number of regions is calculated, use the minimum possible size in the a loop condition.
while (is_sector_map_descriptor(descriptor) && (descriptor + min_descriptor_size <= table_end)) {
size_t regions = descriptor[2] + 1; // Region ID starts at 0
size_t current_descriptor_size = (1 /*header*/ + regions) * 4 /*DWORD size*/;
if (descriptor + current_descriptor_size > table_end) {
tr_error("Sector Map: Incomplete sector map descriptor at the end of the table");
return -1;
}
if (descriptor[1] == config) {
// matching sector map found
return 0;
}
if (is_last_descriptor(descriptor)) {
// We've processed the last sector map descriptor
tr_error("Sector Map: Failed to find a sector map that matches the current configuration");
return -1;
}
descriptor += current_descriptor_size; // next descriptor
}
tr_error("Sector Map: Incomplete sector map descriptors");
return -1;
}
int sfdp_parse_sector_map_table(Callback<int(bd_addr_t, sfdp_cmd_addr_size_t, uint8_t, uint8_t, void *, bd_size_t)> sfdp_reader, sfdp_hdr_info &sfdp_info)
{
uint32_t tmp_region_size = 0;
uint8_t type_mask;
@ -254,9 +372,9 @@ int sfdp_parse_sector_map_table(Callback<int(bd_addr_t, void *, bd_size_t)> sfdp
* - sector map configuration detection commands
* - configurations
* - regions in each configuration
* is variable -> the size of this table is variable
* are variable -> the size of this table is variable
*/
auto smptbl_buff = std::make_unique<uint8_t[]>(sfdp_info.smptbl.size);
auto smptbl_buff = std::unique_ptr<uint8_t[]>(new (std::nothrow) uint8_t[sfdp_info.smptbl.size]);
if (!smptbl_buff) {
tr_error("Failed to allocate memory");
return -1;
@ -264,19 +382,39 @@ int sfdp_parse_sector_map_table(Callback<int(bd_addr_t, void *, bd_size_t)> sfdp
tr_debug("Parsing Sector Map Table - addr: 0x%" PRIx32 ", Size: %d", sfdp_info.smptbl.addr, sfdp_info.smptbl.size);
int status = sfdp_reader(sfdp_info.smptbl.addr, smptbl_buff.get(), sfdp_info.smptbl.size);
int status = sfdp_reader(
sfdp_info.smptbl.addr,
SFDP_READ_CMD_ADDR_TYPE,
SFDP_READ_CMD_INST,
SFDP_READ_CMD_DUMMY_CYCLES,
smptbl_buff.get(),
sfdp_info.smptbl.size
);
if (status < 0) {
tr_error("Sector Map: Table retrieval failed");
return -1;
}
// Currently we support only Single Map Descriptor
if (!((smptbl_buff[0] & 0x3) == 0x03) && (smptbl_buff[1] == 0x0)) {
tr_error("Sector Map: Supporting Only Single Map Descriptor (not map commands)");
return -1;
uint8_t *table = smptbl_buff.get();
uint8_t *descriptor = table;
// Detect which configuration is in use
uint8_t active_config_id = 0x00;
status = sfdp_detect_sector_map_configuration(sfdp_reader, sfdp_info, descriptor, table + sfdp_info.smptbl.size, active_config_id);
if (status != 0) {
tr_error("Failed to detect sector map configuration");
return status;
}
sfdp_info.smptbl.region_cnt = smptbl_buff[2] + 1;
// Locate the sector map for the configuration
status = sfdp_locate_sector_map_by_config(active_config_id, sfdp_info, descriptor, table + sfdp_info.smptbl.size);
if (status != 0) {
tr_error("Failed to locate a matching sector map");
return status;
}
// Find the number of regions from the sector map
sfdp_info.smptbl.region_cnt = descriptor[2] + 1;
if (sfdp_info.smptbl.region_cnt > SFDP_SECTOR_MAP_MAX_REGIONS) {
tr_error("Sector Map: Supporting up to %d regions, current setup to %d regions - fail",
SFDP_SECTOR_MAP_MAX_REGIONS,
@ -284,13 +422,13 @@ int sfdp_parse_sector_map_table(Callback<int(bd_addr_t, void *, bd_size_t)> sfdp
return -1;
}
// Loop through Regions and set for each one: size, supported erase types, high boundary offset
// Calculate minimum Common Erase Type for all Regions
// Loop through the regions and set for each one: size, supported erase types, high boundary offset
// Calculate the minimum common erase type for all regions
for (auto idx = 0; idx < sfdp_info.smptbl.region_cnt; idx++) {
tmp_region_size = ((*((uint32_t *)&smptbl_buff[(idx + 1) * 4])) >> 8) & 0x00FFFFFF; // bits 9-32
tmp_region_size = ((*((uint32_t *)&descriptor[(idx + 1) * 4])) >> 8) & 0x00FFFFFF; // bits 9-32
sfdp_info.smptbl.region_size[idx] = (tmp_region_size + 1) * 256; // Region size is 0 based multiple of 256 bytes;
sfdp_info.smptbl.region_erase_types_bitfld[idx] = smptbl_buff[(idx + 1) * 4] & 0x0F; // bits 1-4
sfdp_info.smptbl.region_erase_types_bitfld[idx] = descriptor[(idx + 1) * 4] & 0x0F; // bits 1-4
min_common_erase_type_bits &= sfdp_info.smptbl.region_erase_types_bitfld[idx];

View File

@ -19,62 +19,90 @@
#include "blockdevice/internal/SFDP.h"
using ::testing::_;
using ::testing::Expectation;
using ::testing::MockFunction;
using ::testing::Return;
// The following data is used by multiple test cases.
/**
* The Sector Map Parameter Table of Cypress S25FS512S:
* https://www.cypress.com/file/216376/download Table 71.
*/
static const mbed::bd_addr_t sector_map_start_addr = 0xD81000;
static const mbed::bd_addr_t register_CR1NV = 0x000002;
static const mbed::bd_addr_t register_CR3NV = 0x000004;
static const uint8_t sector_map_multiple_descriptors[] = {
// Detect 1
0xFC, 0x65, 0xFF, 0x08,
0x04, 0x00, 0x00, 0x00,
// Detect 2
0xFC, 0x65, 0xFF, 0x04,
0x02, 0x00, 0x00, 0x00,
// Detect 3
0xFD, 0x65, 0xFF, 0x02,
0x04, 0x00, 0x00, 0x00,
// Config 1
0xFE, 0x01, 0x02, 0xFF, // header
0xF1, 0x7F, 0x00, 0x00, // region 0
0xF4, 0x7F, 0x03, 0x00, // region 1
0xF4, 0xFF, 0xFB, 0x03, // region 2
// No Config 2
// Config 3
0xFE, 0x03, 0x02, 0xFF, // header
0xF4, 0xFF, 0xFB, 0x03, // region 0
0xF4, 0x7F, 0x03, 0x00, // region 1
0xF1, 0x7F, 0x00, 0x00, // region 2
// Config 4
0xFF, 0x05, 0x00, 0xFF, // header
0xF4, 0xFF, 0xFF, 0x03 // region 0
};
/**
* Based on Cypress S25FS512S, modified to have one descriptor,
* three regions for test purpose.
*/
static const uint8_t sector_map_single_descriptor[] {
0xFF, 0x01, 0x02, 0xFF, // header, highest region = 0x02
0xFF, 0x00, 0x02, 0xFF, // header, highest region = 0x02
0xF1, 0x7F, 0x00, 0x00, // region 0
0xF4, 0x7F, 0x03, 0x00, // region 1
0xF4, 0xFF, 0xFB, 0x03 // region 2
};
/**
* Based on Cypress S25FS512S, modified to have one descriptor,
* twelve regions for test purpose.
*/
static const uint8_t sector_map_single_descriptor_twelve_regions[] {
0xFF, 0x01, 0x0B, 0xFF, // header, highest region = 0x0B
0xF1, 0x7F, 0x00, 0x00, // region 0
0xF4, 0x7F, 0x03, 0x00, // region 1
0xF4, 0xFF, 0xFB, 0x03, // region 2
0xF1, 0x7F, 0x00, 0x00, // region 3
0xF4, 0x7F, 0x03, 0x00, // region 4
0xF4, 0xFF, 0xFB, 0x03, // region 5
0xF1, 0x7F, 0x00, 0x00, // region 6
0xF4, 0x7F, 0x03, 0x00, // region 7
0xF4, 0xFF, 0xFB, 0x03, // region 8
0xF1, 0x7F, 0x00, 0x00, // region 9
0xF4, 0x7F, 0x03, 0x00, // region 10
0xF4, 0xFF, 0xFB, 0x03, // region 11
};
class TestSFDP : public testing::Test {
public:
mbed::Callback<int(mbed::bd_addr_t, void*, bd_size_t)> sfdp_reader_callback;
mbed::Callback<int(mbed::bd_addr_t, mbed::sfdp_cmd_addr_size_t, uint8_t, uint8_t, void*, bd_size_t)> sfdp_reader_callback;
protected:
TestSFDP() : sfdp_reader_callback(this, &TestSFDP::sfdp_reader) {};
int sfdp_reader(mbed::bd_addr_t addr, void *buff, bd_size_t buff_size)
int sfdp_reader(mbed::bd_addr_t addr, mbed::sfdp_cmd_addr_size_t addr_size, uint8_t instr, uint8_t cycles, void *buff, bd_size_t buff_size)
{
int mock_return = sfdp_reader_mock.Call(addr, buff, buff_size);
int mock_return = sfdp_reader_mock.Call(addr, addr_size, instr, cycles, buff, buff_size);
if (mock_return != 0) {
return mock_return;
}
memcpy(buff, sector_descriptors, sector_descriptors_size);
// The following register values give Configuration ID = 0x03.
uint8_t *out = static_cast<uint8_t*>(buff);
switch (addr) {
case sector_map_start_addr:
memcpy(buff, sector_descriptors, sector_descriptors_size);
break;
case register_CR1NV:
out[0] = 0x04;
break;
case register_CR3NV:
out[0] = 0x02;
break;
}
return 0;
};
@ -87,7 +115,7 @@ protected:
sector_descriptors_size = table_size;
}
MockFunction<int(mbed::bd_addr_t, void*, bd_size_t)> sfdp_reader_mock;
MockFunction<int(mbed::bd_addr_t, uint8_t, uint8_t, uint8_t, void*, bd_size_t)> sfdp_reader_mock;
const uint8_t *sector_descriptors;
bd_size_t sector_descriptors_size;
};
@ -191,7 +219,7 @@ TEST_F(TestSFDP, TestNoSectorMap)
header_info.bptbl.device_size_bytes = device_size;
// No need to read anything
EXPECT_CALL(sfdp_reader_mock, Call(_, _, _)).Times(0);
EXPECT_CALL(sfdp_reader_mock, Call(_, _, _, _, _, _)).Times(0);
EXPECT_EQ(0, sfdp_parse_sector_map_table(sfdp_reader_callback, header_info));
@ -208,9 +236,17 @@ TEST_F(TestSFDP, TestSingleSectorConfig)
mbed::sfdp_hdr_info header_info;
set_sector_map_param_table(header_info.smptbl, sector_map_single_descriptor, sizeof(sector_map_single_descriptor));
EXPECT_CALL(sfdp_reader_mock, Call(sector_map_start_addr, _, sizeof(sector_map_single_descriptor)))
.Times(1)
.WillOnce(Return(0));
EXPECT_CALL(
sfdp_reader_mock,
Call(
sector_map_start_addr,
mbed::SFDP_READ_CMD_ADDR_TYPE,
mbed::SFDP_READ_CMD_INST,
mbed::SFDP_READ_CMD_DUMMY_CYCLES,
_,
sizeof(sector_map_single_descriptor)
)
).Times(1).WillOnce(Return(0));
EXPECT_EQ(0, sfdp_parse_sector_map_table(sfdp_reader_callback, header_info));
@ -241,9 +277,17 @@ TEST_F(TestSFDP, TestSFDPReadFailure)
mbed::sfdp_hdr_info header_info;
set_sector_map_param_table(header_info.smptbl, sector_map_single_descriptor, sizeof(sector_map_single_descriptor));
EXPECT_CALL(sfdp_reader_mock, Call(sector_map_start_addr, _, sizeof(sector_map_single_descriptor)))
.Times(1)
.WillOnce(Return(-1)); // Emulate read failure
EXPECT_CALL(
sfdp_reader_mock,
Call(
sector_map_start_addr,
mbed::SFDP_READ_CMD_ADDR_TYPE,
mbed::SFDP_READ_CMD_INST,
mbed::SFDP_READ_CMD_DUMMY_CYCLES,
_,
sizeof(sector_map_single_descriptor)
)
).Times(1).WillOnce(Return(-1)); // Emulate read failure
EXPECT_EQ(-1, sfdp_parse_sector_map_table(sfdp_reader_callback, header_info));
}
@ -254,6 +298,26 @@ TEST_F(TestSFDP, TestSFDPReadFailure)
*/
TEST_F(TestSFDP, TestMoreRegionsThanSupported)
{
/**
* Based on Cypress S25FS512S, modified to have one descriptor,
* twelve regions for test purpose.
*/
const uint8_t sector_map_single_descriptor_twelve_regions[] {
0xFF, 0x00, 0x0B, 0xFF, // header, highest region = 0x0B
0xF1, 0x7F, 0x00, 0x00, // region 0
0xF4, 0x7F, 0x03, 0x00, // region 1
0xF4, 0xFF, 0xFB, 0x03, // region 2
0xF1, 0x7F, 0x00, 0x00, // region 3
0xF4, 0x7F, 0x03, 0x00, // region 4
0xF4, 0xFF, 0xFB, 0x03, // region 5
0xF1, 0x7F, 0x00, 0x00, // region 6
0xF4, 0x7F, 0x03, 0x00, // region 7
0xF4, 0xFF, 0xFB, 0x03, // region 8
0xF1, 0x7F, 0x00, 0x00, // region 9
0xF4, 0x7F, 0x03, 0x00, // region 10
0xF4, 0xFF, 0xFB, 0x03, // region 11
};
mbed::sfdp_hdr_info header_info;
set_sector_map_param_table(
header_info.smptbl,
@ -261,9 +325,305 @@ TEST_F(TestSFDP, TestMoreRegionsThanSupported)
sizeof(sector_map_single_descriptor_twelve_regions)
);
EXPECT_CALL(sfdp_reader_mock, Call(sector_map_start_addr, _, sizeof(sector_map_single_descriptor_twelve_regions)))
.Times(1)
.WillOnce(Return(0));
EXPECT_CALL(
sfdp_reader_mock,
Call(
sector_map_start_addr,
mbed::SFDP_READ_CMD_ADDR_TYPE,
mbed::SFDP_READ_CMD_INST,
mbed::SFDP_READ_CMD_DUMMY_CYCLES,
_,
sizeof(sector_map_single_descriptor_twelve_regions)
)
).Times(1).WillOnce(Return(0));
EXPECT_EQ(-1, sfdp_parse_sector_map_table(sfdp_reader_callback, header_info));
}
/**
* When a Sector Map Parameter Table has multiple configuration detection
* commands and sector maps, sfdp_parse_sector_map_table() runs all commands
* to find the active configuration and selects the matching sector map.
*/
TEST_F(TestSFDP, TestMultipleSectorConfigs)
{
mbed::sfdp_hdr_info header_info;
set_sector_map_param_table(
header_info.smptbl,
sector_map_multiple_descriptors,
sizeof(sector_map_multiple_descriptors)
);
// First call: get all detection command and sector map descriptors
Expectation call_1 = EXPECT_CALL(
sfdp_reader_mock,
Call(
sector_map_start_addr,
mbed::SFDP_READ_CMD_ADDR_TYPE,
mbed::SFDP_READ_CMD_INST,
mbed::SFDP_READ_CMD_DUMMY_CYCLES,
_,
sizeof(sector_map_multiple_descriptors)
)
).Times(1).WillOnce(Return(0));
// Second call: detect bit-0 of configuration
Expectation call_2 = EXPECT_CALL(
sfdp_reader_mock,
Call(register_CR3NV, mbed::SFDP_CMD_ADDR_SIZE_VARIABLE, 0x65, mbed::SFDP_CMD_DUMMY_CYCLES_VARIABLE, _, 1)
).Times(1).After(call_1).WillOnce(Return(0));
// Third call: detect bit-1 of configuration
Expectation call_3 = EXPECT_CALL(
sfdp_reader_mock,
Call(register_CR1NV, mbed::SFDP_CMD_ADDR_SIZE_VARIABLE, 0x65, mbed::SFDP_CMD_DUMMY_CYCLES_VARIABLE, _, 1)
).Times(1).After(call_2).WillOnce(Return(0));
// Fourth call: detect bit-2 of configuration
Expectation call_4 = EXPECT_CALL(
sfdp_reader_mock,
Call(register_CR3NV, mbed::SFDP_CMD_ADDR_SIZE_VARIABLE, 0x65, mbed::SFDP_CMD_DUMMY_CYCLES_VARIABLE, _, 1)
).Times(1).After(call_3).WillOnce(Return(0));
EXPECT_EQ(0, sfdp_parse_sector_map_table(sfdp_reader_callback, header_info));
// Expecting sector map for Configuration ID = 0x03:
// Three regions
EXPECT_EQ(3, header_info.smptbl.region_cnt);
// Region 0: erase type 3 (256KB erase)
// Range: first 64 MB minus 256 KB
EXPECT_EQ(64_MB - 256_KB, header_info.smptbl.region_size[0]);
EXPECT_EQ(64_MB - 256_KB - 1_B, header_info.smptbl.region_high_boundary[0]);
EXPECT_EQ(1 << (3 - 1), header_info.smptbl.region_erase_types_bitfld[0]);
// Region 1: erase type 3 (256KB erase, which also covers 32KB from Region 2)
// Range: between Region 0 and Region 2
EXPECT_EQ(256_KB - 32_KB, header_info.smptbl.region_size[1]);
EXPECT_EQ(64_MB - 32_KB - 1_B, header_info.smptbl.region_high_boundary[1]);
EXPECT_EQ(1 << (3 - 1), header_info.smptbl.region_erase_types_bitfld[1]);
// Region 2: erase type 1 (4KB erase)
// Range: last 32 KB
EXPECT_EQ(32_KB, header_info.smptbl.region_size[2]);
EXPECT_EQ(64_MB - 1_B, header_info.smptbl.region_high_boundary[2]);
EXPECT_EQ(1 << (1 - 1), header_info.smptbl.region_erase_types_bitfld[2]);
}
/**
* When a Sector Map Parameter Table has multiple configuration detection
* commands and sector maps, but one of the detection commands returns
* an error (e.g. due to a bus fault).
*/
TEST_F(TestSFDP, TestConfigDetectCmdFailure)
{
mbed::sfdp_hdr_info header_info;
set_sector_map_param_table(
header_info.smptbl,
sector_map_multiple_descriptors,
sizeof(sector_map_multiple_descriptors)
);
// First call: get all detection command and sector map descriptors
Expectation call_1 = EXPECT_CALL(
sfdp_reader_mock,
Call(
sector_map_start_addr,
mbed::SFDP_READ_CMD_ADDR_TYPE,
mbed::SFDP_READ_CMD_INST,
mbed::SFDP_READ_CMD_DUMMY_CYCLES,
_,
sizeof(sector_map_multiple_descriptors)
)
).Times(1).WillOnce(Return(0));
// Second call: detect bit-0 of configuration
Expectation call_2 = EXPECT_CALL(
sfdp_reader_mock,
Call(register_CR3NV, mbed::SFDP_CMD_ADDR_SIZE_VARIABLE, 0x65, mbed::SFDP_CMD_DUMMY_CYCLES_VARIABLE, _, 1)
).Times(1).After(call_1).WillOnce(Return(0));
// Third call: detect bit-1 of configuration (failed)
Expectation call_3 = EXPECT_CALL(
sfdp_reader_mock,
Call(register_CR1NV, mbed::SFDP_CMD_ADDR_SIZE_VARIABLE, 0x65, mbed::SFDP_CMD_DUMMY_CYCLES_VARIABLE, _, 1)
).Times(1).After(call_2).WillOnce(Return(-1)); // Emulate command failure
// No further calls after failure
Expectation call_4 = EXPECT_CALL(
sfdp_reader_mock,
Call(_, _, _, _, _, _)
).Times(0).After(call_3);
EXPECT_EQ(-1, sfdp_parse_sector_map_table(sfdp_reader_callback, header_info));
}
/**
* When a Sector Map Parameter Table has multiple configuration detection
* commands and sector maps, but no detection command is declared as the
* last command.
* Note: This means either reading went wrong, or the SFDP data is inconsistent
* possibly due to hardware manufactured with wrong data. When the latter happens in
* practice, the solution is to let the block device apply a device-specific quirk
* and supply "corrected" SFDP data in its callback.
*/
TEST_F(TestSFDP, TestConfigIncompleteDetectCommands)
{
const uint8_t table_incomplete_detect_commands[] = {
// Detect 1
0xFC, 0x65, 0xFF, 0x08,
0x04, 0x00, 0x00, 0x00,
// Detect 2
0xFC, 0x65, 0xFF, 0x04,
0x02, 0x00, 0x00, 0x00,
// Detect 3
// Removed to trigger a parsing error
// Config 1
0xFE, 0x01, 0x02, 0xFF, // header
0xF1, 0x7F, 0x00, 0x00, // region 0
0xF4, 0x7F, 0x03, 0x00, // region 1
0xF4, 0xFF, 0xFB, 0x03, // region 2
// No Config 2
// Config 3
0xFE, 0x03, 0x02, 0xFF, // header
0xF4, 0xFF, 0xFB, 0x03, // region 0
0xF4, 0x7F, 0x03, 0x00, // region 1
0xF1, 0x7F, 0x00, 0x00, // region 2
// Config 4
0xFF, 0x05, 0x00, 0xFF, // header
0xF4, 0xFF, 0xFF, 0x03 // region 0
};
mbed::sfdp_hdr_info header_info;
set_sector_map_param_table(
header_info.smptbl,
table_incomplete_detect_commands,
sizeof(table_incomplete_detect_commands)
);
// First call: get all detection command and sector map descriptors
Expectation call_1 = EXPECT_CALL(
sfdp_reader_mock,
Call(
sector_map_start_addr,
mbed::SFDP_READ_CMD_ADDR_TYPE,
mbed::SFDP_READ_CMD_INST,
mbed::SFDP_READ_CMD_DUMMY_CYCLES,
_,
sizeof(table_incomplete_detect_commands)
)
).Times(1).WillOnce(Return(0));
// Second call: detect bit-0 of configuration
Expectation call_2 = EXPECT_CALL(
sfdp_reader_mock,
Call(register_CR3NV, mbed::SFDP_CMD_ADDR_SIZE_VARIABLE, 0x65, mbed::SFDP_CMD_DUMMY_CYCLES_VARIABLE, _, 1)
).Times(1).After(call_1).WillOnce(Return(0));
// Third call: detect bit-1 of configuration
Expectation call_3 = EXPECT_CALL(
sfdp_reader_mock,
Call(register_CR1NV, mbed::SFDP_CMD_ADDR_SIZE_VARIABLE, 0x65, mbed::SFDP_CMD_DUMMY_CYCLES_VARIABLE, _, 1)
).Times(1).After(call_2).WillOnce(Return(0));
// No further calls - incomplete detect command
Expectation call_4 = EXPECT_CALL(
sfdp_reader_mock,
Call(_, _, _, _, _, _)
).Times(0).After(call_3);
EXPECT_EQ(-1, sfdp_parse_sector_map_table(sfdp_reader_callback, header_info));
}
/**
* When a Sector Map Parameter Table has multiple configuration detection
* commands and sector maps, but no sector map matches the active
* configuration.
* Note: This means either detection went wrong, or the SFDP data is inconsistent
* possibly due to hardware manufactured with wrong data. When the latter happens in
* practice, the solution is to let the block device apply a device-specific quirk
* and supply "corrected" SFDP data in its callback.
*/
TEST_F(TestSFDP, TestConfigNoMatchingSectorMap)
{
const uint8_t table_no_matching_sector_map[] = {
// Detect 1
0xFC, 0x65, 0xFF, 0x08,
0x04, 0x00, 0x00, 0x00,
// Detect 2
0xFC, 0x65, 0xFF, 0x04,
0x02, 0x00, 0x00, 0x00,
// Detect 3
0xFD, 0x65, 0xFF, 0x02,
0x04, 0x00, 0x00, 0x00,
// Config 1
0xFE, 0x01, 0x02, 0xFF, // header
0xF1, 0x7F, 0x00, 0x00, // region 0
0xF4, 0x7F, 0x03, 0x00, // region 1
0xF4, 0xFF, 0xFB, 0x03, // region 2
// No Config 2
// Config 3
// The active configuration (for test purpose) is 0x03 which should match header[1],
// but we change the latter to 0x02 to trigger a parsing error.
0xFE, 0x02, 0x02, 0xFF, // header
0xF4, 0xFF, 0xFB, 0x03, // region 0
0xF4, 0x7F, 0x03, 0x00, // region 1
0xF1, 0x7F, 0x00, 0x00, // region 2
// Config 4
0xFF, 0x05, 0x00, 0xFF, // header
0xF4, 0xFF, 0xFF, 0x03 // region 0
};
mbed::sfdp_hdr_info header_info;
set_sector_map_param_table(
header_info.smptbl,
table_no_matching_sector_map,
sizeof(table_no_matching_sector_map)
);
// First call: get all detection command and sector map descriptors
Expectation call_1 = EXPECT_CALL(
sfdp_reader_mock,
Call(
sector_map_start_addr,
mbed::SFDP_READ_CMD_ADDR_TYPE,
mbed::SFDP_READ_CMD_INST,
mbed::SFDP_READ_CMD_DUMMY_CYCLES,
_,
sizeof(table_no_matching_sector_map)
)
).Times(1).WillOnce(Return(0));
// Second call: detect bit-0 of configuration
Expectation call_2 = EXPECT_CALL(
sfdp_reader_mock,
Call(register_CR3NV, mbed::SFDP_CMD_ADDR_SIZE_VARIABLE, 0x65, mbed::SFDP_CMD_DUMMY_CYCLES_VARIABLE, _, 1)
).Times(1).After(call_1).WillOnce(Return(0));
// Third call: detect bit-1 of configuration
Expectation call_3 = EXPECT_CALL(
sfdp_reader_mock,
Call(register_CR1NV, mbed::SFDP_CMD_ADDR_SIZE_VARIABLE, 0x65, mbed::SFDP_CMD_DUMMY_CYCLES_VARIABLE, _, 1)
).Times(1).After(call_2).WillOnce(Return(0));
// Fourth call: detect bit-2 of configuration
Expectation call_4 = EXPECT_CALL(
sfdp_reader_mock,
Call(register_CR3NV, mbed::SFDP_CMD_ADDR_SIZE_VARIABLE, 0x65, mbed::SFDP_CMD_DUMMY_CYCLES_VARIABLE, _, 1)
).Times(1).After(call_3).WillOnce(Return(0));
// Failed to find a sector map for the active configuration.
EXPECT_EQ(-1, sfdp_parse_sector_map_table(sfdp_reader_callback, header_info));
}

View File

@ -7849,12 +7849,8 @@
"CYW43XXX",
"UDB_SDIO_P12"
],
"components_remove": [
"QSPIF"
],
"device_has_remove": [
"ANALOGOUT",
"QSPI"
"ANALOGOUT"
],
"extra_labels_add": [
"PSOC6_01",