Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generic wear-leveling algorithm #16996

Merged
merged 11 commits into from
Jun 26, 2022
Merged
65 changes: 51 additions & 14 deletions quantum/wear_leveling/tests/backing_mocks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,30 +11,56 @@
void MockBackingStore::reset_instance() {
for (auto&& e : backing_storage)
e.reset();

locked = true;

backing_erasure_count = 0;
backing_max_write_count = 0;
backing_total_write_count = 0;
init_fail = false;
unlock_fail = false;
write_fail = false;
lock_fail = false;
erase_fail_index = BACKING_STORE_ELEMENT_COUNT::value;

backing_init_invoke_count = 0;
backing_unlock_invoke_count = 0;
backing_erase_invoke_count = 0;
backing_write_invoke_count = 0;
backing_lock_invoke_count = 0;

init_success_callback = [](std::uint64_t) { return true; };
erase_success_callback = [](std::uint64_t) { return true; };
unlock_success_callback = [](std::uint64_t) { return true; };
write_success_callback = [](std::uint64_t, std::uint32_t) { return true; };
lock_success_callback = [](std::uint64_t) { return true; };

write_log.clear();
}

bool MockBackingStore::init(void) {
return !init_fail;
++backing_init_invoke_count;

if (init_success_callback) {
return init_success_callback(backing_init_invoke_count);
}
return true;
}

bool MockBackingStore::unlock(void) {
return !unlock_fail;
++backing_unlock_invoke_count;

EXPECT_TRUE(is_locked()) << "Attempted to unlock but was not locked";
locked = false;

if (unlock_success_callback) {
return unlock_success_callback(backing_unlock_invoke_count);
}
return true;
}

bool MockBackingStore::erase(void) {
++backing_erase_invoke_count;

// Erase each slot
for (std::size_t i = 0; i < backing_storage.size(); ++i) {
// Drop out of erase early with failure if we need to
if (i >= erase_fail_index) {
if (erase_success_callback && !erase_success_callback(backing_erase_invoke_count)) {
append_log(true);
return false;
}
Expand All @@ -50,12 +76,15 @@ bool MockBackingStore::erase(void) {
}

bool MockBackingStore::write(uint32_t address, backing_store_int_t value) {
++backing_write_invoke_count;

// precondition: value's buffer size already matches BACKING_STORE_WRITE_SIZE
EXPECT_TRUE(address % BACKING_STORE_WRITE_SIZE == 0) << "Supplied address was not aligned with the backing store integral size.";
EXPECT_TRUE(address + BACKING_STORE_WRITE_SIZE <= WEAR_LEVELING_BACKING_SIZE) << "Address would result of out-of-bounds access.";
EXPECT_TRUE(address % BACKING_STORE_WRITE_SIZE == 0) << "Supplied address was not aligned with the backing store integral size";
EXPECT_TRUE(address + BACKING_STORE_WRITE_SIZE <= WEAR_LEVELING_BACKING_SIZE) << "Address would result of out-of-bounds access";
EXPECT_FALSE(is_locked()) << "Write was attempted without being unlocked first";

// Drop out of write early with failure if we need to
if (write_fail) {
if (write_success_callback && !write_success_callback(backing_write_invoke_count, address)) {
return false;
}

Expand All @@ -73,13 +102,21 @@ bool MockBackingStore::write(uint32_t address, backing_store_int_t value) {
}

bool MockBackingStore::lock(void) {
return !lock_fail;
++backing_lock_invoke_count;

EXPECT_FALSE(is_locked()) << "Attempted to lock but was not unlocked";
locked = true;

if (lock_success_callback) {
return lock_success_callback(backing_lock_invoke_count);
}
return true;
}

bool MockBackingStore::read(uint32_t address, backing_store_int_t& value) const {
// precondition: value's buffer size already matches BACKING_STORE_WRITE_SIZE
EXPECT_TRUE(address % BACKING_STORE_WRITE_SIZE == 0) << "Supplied address was not aligned with the backing store integral size.";
EXPECT_TRUE(address + BACKING_STORE_WRITE_SIZE <= WEAR_LEVELING_BACKING_SIZE) << "Address would result of out-of-bounds access.";
EXPECT_TRUE(address % BACKING_STORE_WRITE_SIZE == 0) << "Supplied address was not aligned with the backing store integral size";
EXPECT_TRUE(address + BACKING_STORE_WRITE_SIZE <= WEAR_LEVELING_BACKING_SIZE) << "Address would result of out-of-bounds access";

// Read and take the complement as we're simulating flash memory -- 0xFF means 0x00
std::size_t index = address / BACKING_STORE_WRITE_SIZE;
Expand Down
102 changes: 80 additions & 22 deletions quantum/wear_leveling/tests/backing_mocks.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,13 @@ class MockBackingStore {
reset_instance();
}

// Type containing each of the entries and the write counts
using storage_t = std::array<MockBackingStoreElement, BACKING_STORE_ELEMENT_COUNT::value>;

// Whether the backing store is locked
bool locked;
// The actual data stored in the emulated flash
std::array<MockBackingStoreElement, BACKING_STORE_ELEMENT_COUNT::value> backing_storage;
storage_t backing_storage;
// The number of erase cycles that have occurred
std::uint64_t backing_erasure_count;
// The max number of writes to an element of the backing store
Expand All @@ -83,16 +88,24 @@ class MockBackingStore {
std::uint64_t backing_total_write_count;
// The write log for the backing store
std::vector<MockBackingStoreLogEntry> write_log;
// Whether init should fail
bool init_fail;
// Whether unlocks should fail
bool unlock_fail;
// Whether writes should fail
bool write_fail;
// Whether locks should fail
bool lock_fail;
// The element index at which an erase should fail
std::size_t erase_fail_index;

// The number of times each API was invoked
std::uint64_t backing_init_invoke_count;
std::uint64_t backing_unlock_invoke_count;
std::uint64_t backing_erase_invoke_count;
std::uint64_t backing_write_invoke_count;
std::uint64_t backing_lock_invoke_count;

// Whether init should succeed
std::function<bool(std::uint64_t)> init_success_callback;
// Whether erase should succeed
std::function<bool(std::uint64_t)> erase_success_callback;
// Whether unlocks should succeed
std::function<bool(std::uint64_t)> unlock_success_callback;
// Whether writes should succeed
std::function<bool(std::uint64_t, std::uint32_t)> write_success_callback;
// Whether locks should succeed
std::function<bool(std::uint64_t)> lock_success_callback;

template <typename... Args>
void append_log(Args&&... args) {
Expand All @@ -107,9 +120,40 @@ class MockBackingStore {
return instance;
}

std::uint64_t erasure_count() const {
return backing_erasure_count;
}
std::uint64_t max_write_count() const {
return backing_max_write_count;
}
std::uint64_t total_write_count() const {
return backing_total_write_count;
}

// The number of times each API was invoked
std::uint64_t init_invoke_count() const {
return backing_init_invoke_count;
}
std::uint64_t unlock_invoke_count() const {
return backing_unlock_invoke_count;
}
std::uint64_t erase_invoke_count() const {
return backing_erase_invoke_count;
}
std::uint64_t write_invoke_count() const {
return backing_write_invoke_count;
}
std::uint64_t lock_invoke_count() const {
return backing_lock_invoke_count;
}

// Clear out the internal data for the next run
void reset_instance();

bool is_locked() const {
return locked;
}

// APIs for the backing store
bool init();
bool unlock();
Expand All @@ -118,21 +162,21 @@ class MockBackingStore {
bool lock();
bool read(std::uint32_t address, backing_store_int_t& value) const;

// Control over when init/writes/erases should fail
void set_init_fail(bool fail) {
init_fail = fail;
// Control over when init/writes/erases should succeed
void set_init_callback(std::function<bool(std::uint64_t)> callback) {
init_success_callback = callback;
}
void set_unlock_fail(bool fail) {
unlock_fail = fail;
void set_erase_callback(std::function<bool(std::uint64_t)> callback) {
erase_success_callback = callback;
}
void set_erase_fail(std::size_t fail_index) {
erase_fail_index = fail_index;
void set_unlock_callback(std::function<bool(std::uint64_t)> callback) {
unlock_success_callback = callback;
}
void set_write_fail(bool fail) {
write_fail = fail;
void set_write_callback(std::function<bool(std::uint64_t, std::uint32_t)> callback) {
write_success_callback = callback;
}
void set_lock_fail(bool fail) {
lock_fail = fail;
void set_lock_callback(std::function<bool(std::uint64_t)> callback) {
lock_success_callback = callback;
}

auto storage_begin() const -> decltype(backing_storage.begin()) {
Expand All @@ -142,6 +186,20 @@ class MockBackingStore {
return backing_storage.end();
}

auto storage_begin() -> decltype(backing_storage.begin()) {
return backing_storage.begin();
}
auto storage_end() -> decltype(backing_storage.end()) {
return backing_storage.end();
}

auto log_begin() -> decltype(write_log.begin()) {
return write_log.begin();
}
auto log_end() -> decltype(write_log.end()) {
return write_log.end();
}

auto log_begin() const -> decltype(write_log.begin()) {
return write_log.begin();
}
Expand Down
50 changes: 30 additions & 20 deletions quantum/wear_leveling/tests/rules.mk
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
wear_leveling_common_DEFS := \
-DWEAR_LEVELING_TESTS \
-DWEAR_LEVELING_ASSERTS
-DWEAR_LEVELING_TESTS
wear_leveling_common_SRC := \
$(QUANTUM_PATH)/wear_leveling/wear_leveling.c \
$(QUANTUM_PATH)/wear_leveling/tests/backing_mocks.cpp
wear_leveling_common_INC := \
$(QUANTUM_PATH)/wear_leveling

wear_leveling_general_DEFS := \
$(wear_leveling_common_DEFS) \
-DBACKING_STORE_WRITE_SIZE=2 \
-DWEAR_LEVELING_BACKING_SIZE=48 \
-DWEAR_LEVELING_LOGICAL_SIZE=24
wear_leveling_general_SRC := \
$(wear_leveling_common_SRC) \
$(QUANTUM_PATH)/wear_leveling/tests/wear_leveling_general.cpp
wear_leveling_general_INC := \
$(wear_leveling_common_INC)

wear_leveling_2byte_optimized_writes_DEFS := \
$(wear_leveling_common_DEFS) \
-DBACKING_STORE_WRITE_SIZE=2 \
Expand All @@ -18,35 +28,35 @@ wear_leveling_2byte_optimized_writes_SRC := \
wear_leveling_2byte_optimized_writes_INC := \
$(wear_leveling_common_INC)

wear_leveling_2byte_consolidation_DEFS := \
wear_leveling_2byte_DEFS := \
$(wear_leveling_common_DEFS) \
-DBACKING_STORE_WRITE_SIZE=2 \
-DWEAR_LEVELING_BACKING_SIZE=64 \
-DWEAR_LEVELING_LOGICAL_SIZE=32
wear_leveling_2byte_consolidation_SRC := \
-DWEAR_LEVELING_BACKING_SIZE=48 \
-DWEAR_LEVELING_LOGICAL_SIZE=24
wear_leveling_2byte_SRC := \
$(wear_leveling_common_SRC) \
$(QUANTUM_PATH)/wear_leveling/tests/wear_leveling_2byte_consolidation.cpp
wear_leveling_2byte_consolidation_INC := \
$(QUANTUM_PATH)/wear_leveling/tests/wear_leveling_2byte.cpp
wear_leveling_2byte_INC := \
$(wear_leveling_common_INC)

wear_leveling_4byte_consolidation_DEFS := \
wear_leveling_4byte_DEFS := \
$(wear_leveling_common_DEFS) \
-DBACKING_STORE_WRITE_SIZE=4 \
-DWEAR_LEVELING_BACKING_SIZE=64 \
-DWEAR_LEVELING_LOGICAL_SIZE=32
wear_leveling_4byte_consolidation_SRC := \
-DWEAR_LEVELING_BACKING_SIZE=48 \
-DWEAR_LEVELING_LOGICAL_SIZE=24
wear_leveling_4byte_SRC := \
$(wear_leveling_common_SRC) \
$(QUANTUM_PATH)/wear_leveling/tests/wear_leveling_4byte_consolidation.cpp
wear_leveling_4byte_consolidation_INC := \
$(QUANTUM_PATH)/wear_leveling/tests/wear_leveling_4byte.cpp
wear_leveling_4byte_INC := \
$(wear_leveling_common_INC)

wear_leveling_8byte_consolidation_DEFS := \
wear_leveling_8byte_DEFS := \
$(wear_leveling_common_DEFS) \
-DBACKING_STORE_WRITE_SIZE=8 \
-DWEAR_LEVELING_BACKING_SIZE=64 \
-DWEAR_LEVELING_LOGICAL_SIZE=32
wear_leveling_8byte_consolidation_SRC := \
-DWEAR_LEVELING_BACKING_SIZE=48 \
-DWEAR_LEVELING_LOGICAL_SIZE=24
wear_leveling_8byte_SRC := \
$(wear_leveling_common_SRC) \
$(QUANTUM_PATH)/wear_leveling/tests/wear_leveling_8byte_consolidation.cpp
wear_leveling_8byte_consolidation_INC := \
$(QUANTUM_PATH)/wear_leveling/tests/wear_leveling_8byte.cpp
wear_leveling_8byte_INC := \
$(wear_leveling_common_INC)
7 changes: 4 additions & 3 deletions quantum/wear_leveling/tests/testlist.mk
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
TEST_LIST += \
wear_leveling_general \
wear_leveling_2byte_optimized_writes \
wear_leveling_2byte_consolidation \
wear_leveling_4byte_consolidation \
wear_leveling_8byte_consolidation
wear_leveling_2byte \
wear_leveling_4byte \
wear_leveling_8byte
Loading