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
1 change: 1 addition & 0 deletions builddefs/build_test.mk
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ include $(TMK_PATH)/protocol.mk
include $(QUANTUM_PATH)/debounce/tests/rules.mk
include $(QUANTUM_PATH)/encoder/tests/rules.mk
include $(QUANTUM_PATH)/sequencer/tests/rules.mk
include $(QUANTUM_PATH)/wear_leveling/tests/rules.mk
include $(PLATFORM_PATH)/test/rules.mk
ifneq ($(filter $(FULL_TESTS),$(TEST)),)
include $(BUILDDEFS_PATH)/build_full_test.mk
Expand Down
1 change: 1 addition & 0 deletions builddefs/testlist.mk
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ FULL_TESTS := $(notdir $(TEST_LIST))
include $(QUANTUM_PATH)/debounce/tests/testlist.mk
include $(QUANTUM_PATH)/encoder/tests/testlist.mk
include $(QUANTUM_PATH)/sequencer/tests/testlist.mk
include $(QUANTUM_PATH)/wear_leveling/tests/testlist.mk
include $(PLATFORM_PATH)/test/testlist.mk

define VALIDATE_TEST_LIST
Expand Down
154 changes: 154 additions & 0 deletions quantum/wear_leveling/tests/backing_mocks.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// Copyright 2022 Nick Brassel (@tzarc)
// SPDX-License-Identifier: GPL-2.0-or-later
#include "gtest/gtest.h"
#include "gmock/gmock.h"
#include "backing_mocks.hpp"

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Backing Store Mock implementation
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

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;

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) {
++backing_init_invoke_count;

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

bool MockBackingStore::unlock(void) {
++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 (erase_success_callback && !erase_success_callback(backing_erase_invoke_count)) {
append_log(true);
return false;
}

backing_storage[i].erase();
}

// Keep track of the erase in the write log so that we can verify during tests
append_log(true);

++backing_erasure_count;
return true;
}

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_FALSE(is_locked()) << "Write was attempted without being unlocked first";

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

// Write the complement as we're simulating flash memory -- 0xFF means 0x00
std::size_t index = address / BACKING_STORE_WRITE_SIZE;
backing_storage[index].set(~value);

// Keep track of the write log so that we can verify during tests
append_log(address, value);

// Keep track of the total number of writes into the backing store
++backing_total_write_count;

return true;
}

bool MockBackingStore::lock(void) {
++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";

// Read and take the complement as we're simulating flash memory -- 0xFF means 0x00
std::size_t index = address / BACKING_STORE_WRITE_SIZE;
value = ~backing_storage[index].get();

return true;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Backing Implementation
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

extern "C" bool backing_store_init(void) {
return MockBackingStore::Instance().init();
}

extern "C" bool backing_store_unlock(void) {
return MockBackingStore::Instance().unlock();
}

extern "C" bool backing_store_erase(void) {
return MockBackingStore::Instance().erase();
}

extern "C" bool backing_store_write(uint32_t address, backing_store_int_t value) {
return MockBackingStore::Instance().write(address, value);
}

extern "C" bool backing_store_lock(void) {
return MockBackingStore::Instance().lock();
}

extern "C" bool backing_store_read(uint32_t address, backing_store_int_t* value) {
return MockBackingStore::Instance().read(address, *value);
}
209 changes: 209 additions & 0 deletions quantum/wear_leveling/tests/backing_mocks.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
// Copyright 2022 Nick Brassel (@tzarc)
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <algorithm>
#include <array>
#include <cstdint>
#include <cstdlib>
#include <functional>
#include <type_traits>
#include <vector>

extern "C" {
#include "wear_leveling.h"
#include "wear_leveling_internal.h"
};

// Maximum number of mock write log entries to keep
using MOCK_WRITE_LOG_MAX_ENTRIES = std::integral_constant<std::size_t, 1024>;
// Complement to the backing store integral, for emulating flash erases of all bytes=0xFF
using BACKING_STORE_INTEGRAL_COMPLEMENT = std::integral_constant<backing_store_int_t, ((backing_store_int_t)(~(backing_store_int_t)0))>;
// Total number of elements stored in the backing arrays
using BACKING_STORE_ELEMENT_COUNT = std::integral_constant<std::size_t, (WEAR_LEVELING_BACKING_SIZE / sizeof(backing_store_int_t))>;

class MockBackingStoreElement {
private:
backing_store_int_t value;
std::size_t writes;
std::size_t erases;

public:
MockBackingStoreElement() : value(BACKING_STORE_INTEGRAL_COMPLEMENT::value), writes(0), erases(0) {}
void reset() {
erase();
writes = 0;
erases = 0;
}
void erase() {
if (!is_erased()) {
++erases;
}
value = BACKING_STORE_INTEGRAL_COMPLEMENT::value;
}
backing_store_int_t get() const {
return value;
}
void set(const backing_store_int_t& v) {
EXPECT_TRUE(is_erased()) << "Attempted write at index which isn't empty.";
value = v;
++writes;
}
std::size_t num_writes() const {
return writes;
}
std::size_t num_erases() const {
return erases;
}
bool is_erased() const {
return value == BACKING_STORE_INTEGRAL_COMPLEMENT::value;
}
};

struct MockBackingStoreLogEntry {
MockBackingStoreLogEntry(uint32_t address, backing_store_int_t value) : address(address), value(value), erased(false) {}
MockBackingStoreLogEntry(bool erased) : address(0), value(0), erased(erased) {}
uint32_t address = 0; // The address of the operation
backing_store_int_t value = 0; // The value of the operation
bool erased = false; // Whether the entire backing store was erased
};

class MockBackingStore {
private:
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
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
std::uint64_t backing_max_write_count;
// The total number of writes to all elements of the backing store
std::uint64_t backing_total_write_count;
// The write log for the backing store
std::vector<MockBackingStoreLogEntry> write_log;

// 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) {
if (write_log.size() < MOCK_WRITE_LOG_MAX_ENTRIES::value) {
write_log.emplace_back(std::forward<Args>(args)...);
}
}

public:
static MockBackingStore& Instance() {
static MockBackingStore instance;
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();
bool erase();
bool write(std::uint32_t address, backing_store_int_t value);
bool lock();
bool read(std::uint32_t address, backing_store_int_t& value) const;

// 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_erase_callback(std::function<bool(std::uint64_t)> callback) {
erase_success_callback = callback;
}
void set_unlock_callback(std::function<bool(std::uint64_t)> callback) {
unlock_success_callback = callback;
}
void set_write_callback(std::function<bool(std::uint64_t, std::uint32_t)> callback) {
write_success_callback = callback;
}
void set_lock_callback(std::function<bool(std::uint64_t)> callback) {
lock_success_callback = callback;
}

auto storage_begin() const -> decltype(backing_storage.begin()) {
return backing_storage.begin();
}
auto storage_end() const -> decltype(backing_storage.end()) {
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();
}
auto log_end() const -> decltype(write_log.end()) {
return write_log.end();
}
};
Loading