diff --git a/drivers/CMakeLists.txt b/drivers/CMakeLists.txt index 256ff5b558..a0b5feab06 100644 --- a/drivers/CMakeLists.txt +++ b/drivers/CMakeLists.txt @@ -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 diff --git a/drivers/include/drivers/QSPI.h b/drivers/include/drivers/QSPI.h index 039090043f..86beb2b98f 100644 --- a/drivers/include/drivers/QSPI.h +++ b/drivers/include/drivers/QSPI.h @@ -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 * diff --git a/drivers/source/QSPI.cpp b/drivers/source/QSPI.cpp index 6472a8905f..aa430e1b59 100644 --- a/drivers/source/QSPI.cpp +++ b/drivers/source/QSPI.cpp @@ -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 diff --git a/storage/blockdevice/CMakeLists.txt b/storage/blockdevice/CMakeLists.txt index 6b03244aeb..8b77f0fbc0 100644 --- a/storage/blockdevice/CMakeLists.txt +++ b/storage/blockdevice/CMakeLists.txt @@ -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() diff --git a/storage/blockdevice/COMPONENT_OSPIF/include/OSPIF/OSPIFBlockDevice.h b/storage/blockdevice/COMPONENT_OSPIF/include/OSPIF/OSPIFBlockDevice.h index 2b0799dd8e..801fe74a02 100644 --- a/storage/blockdevice/COMPONENT_OSPIF/include/OSPIF/OSPIFBlockDevice.h +++ b/storage/blockdevice/COMPONENT_OSPIF/include/OSPIF/OSPIFBlockDevice.h @@ -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 sfdp_reader, + int _sfdp_parse_basic_param_table(mbed::Callback 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 sfdp_reader, + int _sfdp_parse_4_byte_inst_table(mbed::Callback sfdp_reader, mbed::sfdp_hdr_info &sfdp_info); // Detect the soft reset protocol and reset - returns error if soft reset is not supported diff --git a/storage/blockdevice/COMPONENT_OSPIF/source/OSPIFBlockDevice.cpp b/storage/blockdevice/COMPONENT_OSPIF/source/OSPIFBlockDevice.cpp index f6857e6ca7..a0954b394c 100644 --- a/storage/blockdevice/COMPONENT_OSPIF/source/OSPIFBlockDevice.cpp +++ b/storage/blockdevice/COMPONENT_OSPIF/source/OSPIFBlockDevice.cpp @@ -921,12 +921,19 @@ int OSPIFBlockDevice::remove_csel_instance(PinName csel) /*********************************************************/ /********** SFDP Parsing and Detection Functions *********/ /*********************************************************/ -int OSPIFBlockDevice::_sfdp_parse_basic_param_table(Callback sfdp_reader, +int OSPIFBlockDevice::_sfdp_parse_basic_param_table(Callback 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 sfdp_reader, +int OSPIFBlockDevice::_sfdp_parse_4_byte_inst_table(Callback 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(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(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) diff --git a/storage/blockdevice/COMPONENT_QSPIF/CMakeLists.txt b/storage/blockdevice/COMPONENT_QSPIF/CMakeLists.txt index 17ee0e28c7..f92553fe98 100644 --- a/storage/blockdevice/COMPONENT_QSPIF/CMakeLists.txt +++ b/storage/blockdevice/COMPONENT_QSPIF/CMakeLists.txt @@ -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() diff --git a/storage/blockdevice/COMPONENT_QSPIF/UNITTESTS/.mbedignore b/storage/blockdevice/COMPONENT_QSPIF/UNITTESTS/.mbedignore new file mode 100644 index 0000000000..72e8ffc0db --- /dev/null +++ b/storage/blockdevice/COMPONENT_QSPIF/UNITTESTS/.mbedignore @@ -0,0 +1 @@ +* diff --git a/storage/blockdevice/COMPONENT_QSPIF/UNITTESTS/CMakeLists.txt b/storage/blockdevice/COMPONENT_QSPIF/UNITTESTS/CMakeLists.txt new file mode 100644 index 0000000000..1958cefa90 --- /dev/null +++ b/storage/blockdevice/COMPONENT_QSPIF/UNITTESTS/CMakeLists.txt @@ -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") diff --git a/storage/blockdevice/COMPONENT_QSPIF/UNITTESTS/test_QSPIFBlockDevice.cpp b/storage/blockdevice/COMPONENT_QSPIF/UNITTESTS/test_QSPIFBlockDevice.cpp new file mode 100644 index 0000000000..0852368028 --- /dev/null +++ b/storage/blockdevice/COMPONENT_QSPIF/UNITTESTS/test_QSPIFBlockDevice.cpp @@ -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 + +#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(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(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(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 test_sfdp_reader; + +int sfdp_parse_headers(Callback 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 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()); +} diff --git a/storage/blockdevice/COMPONENT_QSPIF/include/QSPIF/QSPIFBlockDevice.h b/storage/blockdevice/COMPONENT_QSPIF/include/QSPIF/QSPIFBlockDevice.h index 1aae02bf4c..3ebd0e504c 100644 --- a/storage/blockdevice/COMPONENT_QSPIF/include/QSPIF/QSPIFBlockDevice.h +++ b/storage/blockdevice/COMPONENT_QSPIF/include/QSPIF/QSPIFBlockDevice.h @@ -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 sfdp_reader, + int _sfdp_parse_basic_param_table(mbed::Callback 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; diff --git a/storage/blockdevice/COMPONENT_QSPIF/source/QSPIFBlockDevice.cpp b/storage/blockdevice/COMPONENT_QSPIF/source/QSPIFBlockDevice.cpp index e07a5217db..39b8c1c43c 100644 --- a/storage/blockdevice/COMPONENT_QSPIF/source/QSPIFBlockDevice.cpp +++ b/storage/blockdevice/COMPONENT_QSPIF/source/QSPIFBlockDevice.cpp @@ -17,7 +17,7 @@ #include "blockdevice/internal/SFDP.h" #include "platform/Callback.h" -#include "QSPIFBlockDevice.h" +#include "QSPIF/QSPIFBlockDevice.h" #include #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 sfdp_reader, +int QSPIFBlockDevice::_sfdp_parse_basic_param_table(Callback 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(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(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(rx_buffer)[0] |= (1 << 1); + static_cast(rx_buffer)[0] |= (1 << 3); + } else if (addr == S25FS512S_CR1NV) { + static_cast(rx_buffer)[0] &= ~(1 << 2); + } + } + return QSPI_STATUS_OK; } diff --git a/storage/blockdevice/COMPONENT_SPIF/include/SPIF/SPIFBlockDevice.h b/storage/blockdevice/COMPONENT_SPIF/include/SPIF/SPIFBlockDevice.h index c68c457536..a0a87ab78b 100644 --- a/storage/blockdevice/COMPONENT_SPIF/include/SPIF/SPIFBlockDevice.h +++ b/storage/blockdevice/COMPONENT_SPIF/include/SPIF/SPIFBlockDevice.h @@ -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 sfdp_reader, + int _sfdp_parse_basic_param_table(mbed::Callback sfdp_reader, mbed::sfdp_hdr_info &hdr_info); // Detect fastest read Bus mode supported by device diff --git a/storage/blockdevice/COMPONENT_SPIF/source/SPIFBlockDevice.cpp b/storage/blockdevice/COMPONENT_SPIF/source/SPIFBlockDevice.cpp index 823ed2ae3d..bdbcae9e38 100644 --- a/storage/blockdevice/COMPONENT_SPIF/source/SPIFBlockDevice.cpp +++ b/storage/blockdevice/COMPONENT_SPIF/source/SPIFBlockDevice.cpp @@ -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(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(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 sfdp_reader, - mbed::sfdp_hdr_info &sfdp_info) +int SPIFBlockDevice::_sfdp_parse_basic_param_table(Callback 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; diff --git a/storage/blockdevice/include/blockdevice/internal/SFDP.h b/storage/blockdevice/include/blockdevice/internal/SFDP.h index 96f7518e01..b1b51a1a60 100644 --- a/storage/blockdevice/include/blockdevice/internal/SFDP.h +++ b/storage/blockdevice/include/blockdevice/internal/SFDP.h @@ -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 sfdp_reader, sfdp_hdr_info &sfdp_info); +int sfdp_parse_headers(Callback 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 sfdp_reader, * * @return MBED_SUCCESS on success, negative error code on failure */ -int sfdp_parse_sector_map_table(Callback sfdp_reader, sfdp_hdr_info &sfdp_info); +int sfdp_parse_sector_map_table(Callback sfdp_reader, sfdp_hdr_info &sfdp_info); /** Detect page size used for writing on flash * diff --git a/storage/blockdevice/source/SFDP.cpp b/storage/blockdevice/source/SFDP.cpp index 3b5ac248f9..e523c3449c 100644 --- a/storage/blockdevice/source/SFDP.cpp +++ b/storage/blockdevice/source/SFDP.cpp @@ -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 sfdp_reader, sfdp_hdr_info &sfdp_info) +int sfdp_parse_headers(Callback 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 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 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 sfdp_reader, return 0; } -int sfdp_parse_sector_map_table(Callback 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 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(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 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 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(sfdp_info.smptbl.size); + auto smptbl_buff = std::unique_ptr(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 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 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]; diff --git a/storage/blockdevice/tests/UNITTESTS/SFDP/test_sfdp.cpp b/storage/blockdevice/tests/UNITTESTS/SFDP/test_sfdp.cpp index 3bb3ea6df9..8bd3a0eb8c 100644 --- a/storage/blockdevice/tests/UNITTESTS/SFDP/test_sfdp.cpp +++ b/storage/blockdevice/tests/UNITTESTS/SFDP/test_sfdp.cpp @@ -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 sfdp_reader_callback; + mbed::Callback 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(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 sfdp_reader_mock; + MockFunction 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)); +} diff --git a/targets/targets.json b/targets/targets.json index 9efd06678e..81ddc62a0e 100644 --- a/targets/targets.json +++ b/targets/targets.json @@ -7849,12 +7849,8 @@ "CYW43XXX", "UDB_SDIO_P12" ], - "components_remove": [ - "QSPIF" - ], "device_has_remove": [ - "ANALOGOUT", - "QSPI" + "ANALOGOUT" ], "extra_labels_add": [ "PSOC6_01",