Skip to content

Commit f88bf82

Browse files
committed
QSPIFBlockDevice: Add unit test for the S25FS512S quirk
Add a test case to verify that the quirk on `CR1NV` and `CR3NV` registers get applied by `QSPIFBlockDevice` when the flash device is S25FS512S. `QSPIFBlockDevice` depends on the SFDP functions and the `QSPI` class, but we can't use gMock on them because: * SFDP functions are global functions, whereas gMock only supports mocking class member functions. * A mocked class is injected into the test subject, but passing a preexisting `QSPI` instance into `QSPIFBlockDevice` is not possible (unless we resort to pointers). For details, see comments in test_QSPIFBlockDevice.cpp. So fakes of the `QSPI` class and any SFDP functions involved are defined in test_QSPIFBlockDevice.cpp.
1 parent 23a79ef commit f88bf82

File tree

5 files changed

+381
-0
lines changed

5 files changed

+381
-0
lines changed

storage/blockdevice/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME AND BUILD_TESTING)
55
if(BUILD_GREENTEA_TESTS)
66
# add greentea test
77
else()
8+
add_subdirectory(COMPONENT_QSPIF)
89
add_subdirectory(tests/UNITTESTS)
910
endif()
1011
endif()

storage/blockdevice/COMPONENT_QSPIF/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,7 @@ target_sources(mbed-storage-qspif
1111
INTERFACE
1212
source/QSPIFBlockDevice.cpp
1313
)
14+
15+
if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME AND BUILD_TESTING)
16+
add_subdirectory(UNITTESTS)
17+
endif()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
*
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Copyright (c) 2021 Arm Limited. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
include(GoogleTest)
5+
6+
add_executable(qspif-unittest)
7+
8+
target_compile_definitions(qspif-unittest
9+
PRIVATE
10+
DEVICE_QSPI=1
11+
MBED_CONF_QSPIF_QSPI_MIN_READ_SIZE=1
12+
MBED_CONF_QSPIF_QSPI_MIN_PROG_SIZE=1
13+
)
14+
15+
target_include_directories(qspif-unittest
16+
PRIVATE
17+
${mbed-os_SOURCE_DIR}/storage/blockdevice/COMPONENT_QSPIF/include
18+
)
19+
20+
target_sources(qspif-unittest
21+
PRIVATE
22+
${mbed-os_SOURCE_DIR}/storage/blockdevice/COMPONENT_QSPIF/source/QSPIFBlockDevice.cpp
23+
test_QSPIFBlockDevice.cpp
24+
)
25+
26+
target_link_libraries(qspif-unittest
27+
PRIVATE
28+
mbed-headers-blockdevice
29+
mbed-headers-drivers
30+
mbed-headers-platform
31+
mbed-headers-rtos
32+
mbed-stubs-blockdevice
33+
mbed-stubs-platform
34+
mbed-stubs-drivers
35+
mbed-stubs-rtos
36+
gmock_main
37+
)
38+
39+
gtest_discover_tests(qspif-unittest PROPERTIES LABELS "storage")
Lines changed: 336 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,336 @@
1+
/* Copyright (c) 2021 Arm Limited
2+
* SPDX-License-Identifier: Apache-2.0
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#include <cassert>
18+
19+
#include "gtest/gtest.h"
20+
#include "gmock/gmock.h"
21+
22+
#include "QSPIF/QSPIFBlockDevice.h"
23+
24+
// S25FS512S's CR3NV register needs a quirk
25+
static const int S25FS512S_CR1NV = 0x2;
26+
static const int S25FS512S_CR3NV = 0x4;
27+
28+
// JEDEC Manufacturer and Device ID, set by the test cases
29+
static uint8_t const *device_id;
30+
31+
namespace mbed {
32+
33+
/**
34+
* Fake implementation of mbed::QSPI class for QSPIFBlockDevice unit tests.
35+
*
36+
* Note: Data output by this is either dummy or based on Cypress S25FS512S.
37+
* We can't use gMock for this because
38+
* - The entire mbed::QSPI is non-virtual. Mocks are derived classes, and
39+
* when a derived class instance is used as an instance of its base class,
40+
* non-virtual members of the base class get used.
41+
* - QSPIFBlockDevice's member _qspi (an instance of mbed::QSPI) is a value
42+
* instead of a reference, in order to support initializing from pins directly.
43+
* Also, mbed::QSPI is declared as NonCopyable whose copy-constructor is deleted,
44+
* so we can't copy a preexisting mbed::QSPI instance into QSPIFBlockDevice.
45+
* There's no way to dependency inject a mock instance.
46+
*/
47+
QSPI::QSPI(PinName io0, PinName io1, PinName io2, PinName io3, PinName sclk, PinName ssel, int mode) : _qspi()
48+
{
49+
}
50+
51+
QSPI::~QSPI()
52+
{
53+
}
54+
55+
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)
56+
{
57+
return QSPI_STATUS_OK;
58+
}
59+
60+
qspi_status_t QSPI::set_frequency(int hz)
61+
{
62+
return QSPI_STATUS_OK;
63+
}
64+
65+
qspi_status_t QSPI::read(int address, char *rx_buffer, size_t *rx_length)
66+
{
67+
return QSPI_STATUS_OK;
68+
}
69+
70+
qspi_status_t QSPI::write(int address, const char *tx_buffer, size_t *tx_length)
71+
{
72+
return QSPI_STATUS_OK;
73+
}
74+
75+
qspi_status_t QSPI::read(qspi_inst_t instruction, int alt, int address, char *rx_buffer, size_t *rx_length)
76+
{
77+
if (!rx_buffer) {
78+
return QSPI_STATUS_INVALID_PARAMETER;
79+
}
80+
81+
switch (address) {
82+
// CR1NV and CR3NV registers' factory default values are both 0x0
83+
case S25FS512S_CR1NV:
84+
case S25FS512S_CR3NV:
85+
if (!rx_length || *rx_length < 1) {
86+
return QSPI_STATUS_INVALID_PARAMETER;
87+
}
88+
rx_buffer[0] = 0x00;
89+
break;
90+
}
91+
92+
switch (instruction) {
93+
case mbed::SFDP_READ_CMD_INST: // read SFDP table
94+
if (!rx_length || *rx_length < SFDP_BASIC_PARAMS_TBL_SIZE) {
95+
return QSPI_STATUS_INVALID_PARAMETER;
96+
}
97+
// set dummy data (zeros), to avoid warning of uninitialized
98+
// data from Valgrind and inconsistent test behavior.
99+
memset(rx_buffer, 0x00, *rx_length);
100+
// soft reset needs one instruction to enable reset,
101+
// another instruction to perform reset
102+
rx_buffer[61] = 0x30;
103+
break;
104+
}
105+
106+
return QSPI_STATUS_OK;
107+
}
108+
109+
qspi_status_t QSPI::write(qspi_inst_t instruction, int alt, int address, const char *tx_buffer, size_t *tx_length)
110+
{
111+
return QSPI_STATUS_OK;
112+
}
113+
114+
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)
115+
{
116+
static char status_registers[2];
117+
switch (instruction) {
118+
case 0x01: // write status registers
119+
if (!tx_buffer || tx_length < 2) {
120+
return QSPI_STATUS_INVALID_PARAMETER;
121+
}
122+
memcpy(status_registers, tx_buffer, tx_length);
123+
break;
124+
case 0x05: // read status register 1
125+
if (!rx_buffer || rx_length < 1) {
126+
return QSPI_STATUS_INVALID_PARAMETER;
127+
}
128+
const_cast<char *>(rx_buffer)[0] = status_registers[0];
129+
break;
130+
case 0x35: // read status register 2
131+
if (!rx_buffer || rx_length < 1) {
132+
return QSPI_STATUS_INVALID_PARAMETER;
133+
}
134+
const_cast<char *>(rx_buffer)[0] = status_registers[1];
135+
break;
136+
case 0x06: // set write enabled bit
137+
status_registers[0] |= 0x2;
138+
break;
139+
case 0x9F: // read Manufacturer and JDEC Device ID
140+
assert(nullptr != device_id); // Test cases should set device_id
141+
if (!rx_buffer || rx_length < 3) {
142+
return QSPI_STATUS_INVALID_PARAMETER;
143+
}
144+
memcpy(const_cast<char *>(rx_buffer), device_id, 3);
145+
break;
146+
}
147+
return QSPI_STATUS_OK;
148+
}
149+
150+
void QSPI::lock()
151+
{
152+
}
153+
154+
void QSPI::unlock()
155+
{
156+
}
157+
158+
bool QSPI::_initialize()
159+
{
160+
return true;
161+
}
162+
163+
bool QSPI::_initialize_direct()
164+
{
165+
return true;
166+
}
167+
168+
void QSPI::_build_qspi_command(qspi_inst_t instruction, int address, int alt)
169+
{
170+
}
171+
172+
/**
173+
* Fake implementation of SFDP functions.
174+
* They can't be mocked with gMock which supports only class member functions,
175+
* not free/global functions.
176+
*/
177+
static Callback<int(bd_addr_t, sfdp_cmd_addr_size_t, uint8_t, uint8_t, void *, bd_size_t)> test_sfdp_reader;
178+
179+
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)
180+
{
181+
// The SFDP Basic Parameters Table size is used as a QSPI buffer
182+
// size, so it must be set.
183+
sfdp_info.bptbl.size = SFDP_BASIC_PARAMS_TBL_SIZE;
184+
return 0;
185+
}
186+
187+
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)
188+
{
189+
// The real implementation of this function queries
190+
// the CR3NV register on S25FS512S.
191+
test_sfdp_reader = sfdp_reader;
192+
return 0;
193+
}
194+
195+
size_t sfdp_detect_page_size(uint8_t *bptbl_ptr, size_t bptbl_size)
196+
{
197+
return 0;
198+
}
199+
200+
int sfdp_detect_erase_types_inst_and_size(uint8_t *bptbl_ptr, sfdp_hdr_info &sfdp_info)
201+
{
202+
return 0;
203+
}
204+
205+
int sfdp_find_addr_region(bd_addr_t offset, const sfdp_hdr_info &sfdp_info)
206+
{
207+
return 0;
208+
}
209+
210+
int sfdp_iterate_next_largest_erase_type(uint8_t bitfield,
211+
bd_size_t size,
212+
bd_addr_t offset,
213+
int region,
214+
const sfdp_smptbl_info &smptbl)
215+
{
216+
return 0;
217+
}
218+
219+
int sfdp_detect_device_density(uint8_t *bptbl_ptr, sfdp_bptbl_info &bptbl_info)
220+
{
221+
return 0;
222+
}
223+
224+
int sfdp_detect_addressability(uint8_t *bptbl_ptr, sfdp_bptbl_info &bptbl_info)
225+
{
226+
return 0;
227+
}
228+
229+
} // namespace mbed
230+
231+
232+
class TestQSPIFBlockDevice : public testing::Test {
233+
protected:
234+
TestQSPIFBlockDevice()
235+
: bd(PinName(0), PinName(1), PinName(2), PinName(3), PinName(4), PinName(5)) // fake pins
236+
{
237+
}
238+
239+
virtual void TearDown()
240+
{
241+
device_id = nullptr;
242+
}
243+
244+
QSPIFBlockDevice bd;
245+
};
246+
247+
/**
248+
* Test that the quirk on the reported values of the CR1NV and CR3NV
249+
* registers is applied when the flash chip is S25FS512S.
250+
*/
251+
TEST_F(TestQSPIFBlockDevice, TestS25FS512SQuirkApplied)
252+
{
253+
// Use flash chip S25FS512S
254+
const uint8_t id_S25FS512S[] { 0x01, 0x02, 0x20 };
255+
device_id = id_S25FS512S;
256+
EXPECT_EQ(QSPIF_BD_ERROR_OK, bd.init());
257+
258+
const uint8_t id_N25Q128A[] { 0x20, 0xBB, 0x18 };
259+
260+
// Read the CR1NV register
261+
uint8_t CR1NV;
262+
EXPECT_EQ(QSPIF_BD_ERROR_OK, mbed::test_sfdp_reader(
263+
S25FS512S_CR1NV,
264+
mbed::SFDP_CMD_ADDR_SIZE_VARIABLE,
265+
0x65,
266+
mbed::SFDP_CMD_DUMMY_CYCLES_VARIABLE,
267+
&CR1NV,
268+
sizeof(CR1NV)));
269+
270+
// Read the CR3NV register
271+
uint8_t CR3NV;
272+
EXPECT_EQ(QSPIF_BD_ERROR_OK, mbed::test_sfdp_reader(
273+
S25FS512S_CR3NV,
274+
mbed::SFDP_CMD_ADDR_SIZE_VARIABLE,
275+
0x65,
276+
mbed::SFDP_CMD_DUMMY_CYCLES_VARIABLE,
277+
&CR3NV,
278+
sizeof(CR3NV)));
279+
280+
// The hardware value of CR3NV[1] is 0 but S25FS512S's SFDP table
281+
// expects it to be 1.
282+
EXPECT_EQ(0x2, CR3NV & 0x02);
283+
284+
// The factory default configuration is CR1NV[2] == 0 and CR3NV[3] == 0
285+
// (eight 4KB sectors overlaying the start of the flash) but we treat
286+
// the flash chip as if CR1NV[2] == 0 and CR3NV[3] == 1 (no overlaying 4KB
287+
// sectors), as Mbed OS does not support this type of flash layout.
288+
EXPECT_EQ(0x0, CR1NV & 0x4);
289+
EXPECT_EQ(0x8, CR3NV & 0x8);
290+
291+
// Deinitialization
292+
EXPECT_EQ(QSPIF_BD_ERROR_OK, bd.deinit());
293+
}
294+
295+
/**
296+
* Test that the quirk on the reported values of the CR1NV and CR3NV
297+
* registers is not applied when the flash chip is not S25FS512S.
298+
* Note: Other flash chips most likely do not have CR1NV or CR3NV, but
299+
* they might have something else readable at the same addresses.
300+
*/
301+
TEST_F(TestQSPIFBlockDevice, TestS25FS512SQuirkNotApplied)
302+
{
303+
// Use flash chip N25Q128A
304+
const uint8_t id_N25Q128A[] { 0x20, 0xBB, 0x18 };
305+
device_id = id_N25Q128A;
306+
EXPECT_EQ(QSPIF_BD_ERROR_OK, bd.init());
307+
308+
// Read the value at the address of S25FS512S's CR1NV register,
309+
// assuming this address is readable.
310+
uint8_t CR1NV;
311+
EXPECT_EQ(QSPIF_BD_ERROR_OK, mbed::test_sfdp_reader(
312+
S25FS512S_CR1NV,
313+
mbed::SFDP_CMD_ADDR_SIZE_VARIABLE,
314+
0x65,
315+
mbed::SFDP_CMD_DUMMY_CYCLES_VARIABLE,
316+
&CR1NV,
317+
sizeof(CR1NV)));
318+
319+
// Read the value at the address of S25FS512S's CR3NV register,
320+
// assuming this address is readable.
321+
uint8_t CR3NV;
322+
EXPECT_EQ(QSPIF_BD_ERROR_OK, mbed::test_sfdp_reader(
323+
S25FS512S_CR3NV,
324+
mbed::SFDP_CMD_ADDR_SIZE_VARIABLE,
325+
0x65,
326+
mbed::SFDP_CMD_DUMMY_CYCLES_VARIABLE,
327+
&CR3NV,
328+
sizeof(CR3NV)));
329+
330+
// Values (0) reported by the fake QSPI::read() should be unmodifed
331+
EXPECT_EQ(0, CR1NV);
332+
EXPECT_EQ(0, CR3NV);
333+
334+
// Deinitialization
335+
EXPECT_EQ(QSPIF_BD_ERROR_OK, bd.deinit());
336+
}

0 commit comments

Comments
 (0)