chore: Initial multi-core compatibility (#2294)

This commit is contained in:
Uri Tauber
2026-06-29 21:57:21 +03:00
committed by GitHub
parent 28255061fb
commit fd43ca2fe1
6 changed files with 40 additions and 13 deletions
+8 -1
View File
@@ -6,6 +6,7 @@
#include <Serialization.h> #include <Serialization.h>
#include <cstring> #include <cstring>
#include <mutex>
#include <string> #include <string>
#include "I18nKeys.h" #include "I18nKeys.h"
@@ -96,6 +97,7 @@ uint8_t CrossPointSettings::sleepTimeoutEnumToMinutes(const uint8_t legacyValue)
} }
bool CrossPointSettings::saveToFile() const { bool CrossPointSettings::saveToFile() const {
std::lock_guard<std::mutex> lock(_mutex);
Storage.mkdir("/.crosspoint"); Storage.mkdir("/.crosspoint");
return JsonSettingsIO::saveSettings(*this, SETTINGS_FILE_JSON); return JsonSettingsIO::saveSettings(*this, SETTINGS_FILE_JSON);
} }
@@ -106,7 +108,11 @@ bool CrossPointSettings::loadFromFile() {
String json = Storage.readFile(SETTINGS_FILE_JSON); String json = Storage.readFile(SETTINGS_FILE_JSON);
if (!json.isEmpty()) { if (!json.isEmpty()) {
bool resave = false; bool resave = false;
bool result = JsonSettingsIO::loadSettings(*this, json.c_str(), &resave); bool result;
{
std::lock_guard<std::mutex> lock(_mutex);
result = JsonSettingsIO::loadSettings(*this, json.c_str(), &resave);
}
if (result && resave) { if (result && resave) {
if (saveToFile()) { if (saveToFile()) {
LOG_DBG("CPS", "Resaved settings to update format"); LOG_DBG("CPS", "Resaved settings to update format");
@@ -166,6 +172,7 @@ bool CrossPointSettings::loadFromBinaryFile() {
if (!Storage.openFileForRead("CPS", SETTINGS_FILE_BIN, inputFile)) { if (!Storage.openFileForRead("CPS", SETTINGS_FILE_BIN, inputFile)) {
return false; return false;
} }
std::lock_guard<std::mutex> lock(_mutex);
uint8_t version; uint8_t version;
serialization::readPod(inputFile, version); serialization::readPod(inputFile, version);
+7
View File
@@ -3,9 +3,12 @@
#include <cstdint> #include <cstdint>
#include <iosfwd> #include <iosfwd>
#include <mutex>
class CrossPointSettings { class CrossPointSettings {
private: private:
mutable std::mutex _mutex;
// Private constructor for singleton // Private constructor for singleton
CrossPointSettings() = default; CrossPointSettings() = default;
@@ -17,6 +20,10 @@ class CrossPointSettings {
CrossPointSettings(const CrossPointSettings&) = delete; CrossPointSettings(const CrossPointSettings&) = delete;
CrossPointSettings& operator=(const CrossPointSettings&) = delete; CrossPointSettings& operator=(const CrossPointSettings&) = delete;
// Access the settings mutex for protecting multi-field reads/writes from other cores.
// Callers must not re-enter SETTINGS methods that lock _mutex while holding it.
std::mutex& getMutex() const { return _mutex; }
enum SLEEP_SCREEN_MODE { enum SLEEP_SCREEN_MODE {
DARK = 0, DARK = 0,
LIGHT = 1, LIGHT = 1,
+4
View File
@@ -6,6 +6,7 @@
#include <Serialization.h> #include <Serialization.h>
#include <algorithm> #include <algorithm>
#include <mutex>
namespace { namespace {
constexpr uint8_t STATE_FILE_VERSION = 4; constexpr uint8_t STATE_FILE_VERSION = 4;
@@ -32,6 +33,7 @@ void CrossPointState::pushRecentSleep(uint16_t idx) {
} }
bool CrossPointState::saveToFile() const { bool CrossPointState::saveToFile() const {
std::lock_guard<std::mutex> lock(_mutex);
Storage.mkdir("/.crosspoint"); Storage.mkdir("/.crosspoint");
return JsonSettingsIO::saveState(*this, STATE_FILE_JSON); return JsonSettingsIO::saveState(*this, STATE_FILE_JSON);
} }
@@ -41,6 +43,7 @@ bool CrossPointState::loadFromFile() {
if (Storage.exists(STATE_FILE_JSON)) { if (Storage.exists(STATE_FILE_JSON)) {
String json = Storage.readFile(STATE_FILE_JSON); String json = Storage.readFile(STATE_FILE_JSON);
if (!json.isEmpty()) { if (!json.isEmpty()) {
std::lock_guard<std::mutex> lock(_mutex);
return JsonSettingsIO::loadState(*this, json.c_str()); return JsonSettingsIO::loadState(*this, json.c_str());
} }
} }
@@ -67,6 +70,7 @@ bool CrossPointState::loadFromBinaryFile() {
if (!Storage.openFileForRead("CPS", STATE_FILE_BIN, inputFile)) { if (!Storage.openFileForRead("CPS", STATE_FILE_BIN, inputFile)) {
return false; return false;
} }
std::lock_guard<std::mutex> lock(_mutex);
uint8_t version; uint8_t version;
serialization::readPod(inputFile, version); serialization::readPod(inputFile, version);
+6
View File
@@ -1,12 +1,18 @@
#pragma once #pragma once
#include <cstdint> #include <cstdint>
#include <mutex>
#include <string> #include <string>
class CrossPointState { class CrossPointState {
mutable std::mutex _mutex;
// Static instance // Static instance
static CrossPointState instance; static CrossPointState instance;
public: public:
// Access the state mutex for protecting multi-field reads/writes from other cores.
std::mutex& getMutex() const { return _mutex; }
static constexpr uint8_t SLEEP_RECENT_COUNT = 16; static constexpr uint8_t SLEEP_RECENT_COUNT = 16;
std::string openEpubPath; std::string openEpubPath;
+13 -11
View File
@@ -19,12 +19,15 @@
#include "settings/SettingsActivity.h" #include "settings/SettingsActivity.h"
#include "util/FullScreenMessageActivity.h" #include "util/FullScreenMessageActivity.h"
static portMUX_TYPE activityManagerSpinlock = portMUX_INITIALIZER_UNLOCKED;
void ActivityManager::begin() { void ActivityManager::begin() {
xTaskCreate(&renderTaskTrampoline, "ActivityManagerRender", xTaskCreatePinnedToCore(&renderTaskTrampoline, "ActivityManagerRender",
8192, // Stack size 8192, // Stack size
this, // Parameters this, // Parameters
1, // Priority 1, // Priority
&renderTaskHandle // Task handle &renderTaskHandle, // Task handle
0 // Pin to core 0 (PRO_CPU)
); );
assert(renderTaskHandle != nullptr && "Failed to create render task"); assert(renderTaskHandle != nullptr && "Failed to create render task");
} }
@@ -46,10 +49,10 @@ void ActivityManager::renderTaskLoop() {
} }
// Notify any task blocked in requestUpdateAndWait() that the render is done. // Notify any task blocked in requestUpdateAndWait() that the render is done.
TaskHandle_t waiter = nullptr; TaskHandle_t waiter = nullptr;
taskENTER_CRITICAL(nullptr); taskENTER_CRITICAL(&activityManagerSpinlock);
waiter = waitingTaskHandle; waiter = waitingTaskHandle;
waitingTaskHandle = nullptr; waitingTaskHandle = nullptr;
taskEXIT_CRITICAL(nullptr); taskEXIT_CRITICAL(&activityManagerSpinlock);
if (waiter) { if (waiter) {
xTaskNotify(waiter, 1, eIncrement); xTaskNotify(waiter, 1, eIncrement);
} }
@@ -137,8 +140,7 @@ void ActivityManager::loop() {
} }
} }
if (requestedUpdate) { if (requestedUpdate.exchange(false)) {
requestedUpdate = false;
// Using direct notification to signal the render task to update // Using direct notification to signal the render task to update
// Increment counter so multiple rapid calls won't be lost // Increment counter so multiple rapid calls won't be lost
if (renderTaskHandle) { if (renderTaskHandle) {
@@ -280,7 +282,7 @@ void ActivityManager::requestUpdateAndWait() {
} }
// Atomic section to perform checks // Atomic section to perform checks
taskENTER_CRITICAL(nullptr); taskENTER_CRITICAL(&activityManagerSpinlock);
auto currTaskHandler = xTaskGetCurrentTaskHandle(); auto currTaskHandler = xTaskGetCurrentTaskHandle();
auto mutexHolder = xSemaphoreGetMutexHolder(renderingMutex); auto mutexHolder = xSemaphoreGetMutexHolder(renderingMutex);
bool isRenderTask = (currTaskHandler == renderTaskHandle); bool isRenderTask = (currTaskHandler == renderTaskHandle);
@@ -289,7 +291,7 @@ void ActivityManager::requestUpdateAndWait() {
if (!alreadyWaiting && !isRenderTask && !holdingRenderLock) { if (!alreadyWaiting && !isRenderTask && !holdingRenderLock) {
waitingTaskHandle = currTaskHandler; waitingTaskHandle = currTaskHandler;
} }
taskEXIT_CRITICAL(nullptr); taskEXIT_CRITICAL(&activityManagerSpinlock);
// Render task cannot call requestUpdateAndWait() or it will cause a deadlock // Render task cannot call requestUpdateAndWait() or it will cause a deadlock
assert(!isRenderTask && "Render task cannot call requestUpdateAndWait()"); assert(!isRenderTask && "Render task cannot call requestUpdateAndWait()");
+2 -1
View File
@@ -4,6 +4,7 @@
#include <freertos/semphr.h> #include <freertos/semphr.h>
#include <freertos/task.h> #include <freertos/task.h>
#include <atomic>
#include <cassert> #include <cassert>
#include <memory> #include <memory>
#include <string> #include <string>
@@ -63,7 +64,7 @@ class ActivityManager {
// Whether to trigger a render after the current loop() // Whether to trigger a render after the current loop()
// This variable must only be set by the main loop, to avoid race conditions // This variable must only be set by the main loop, to avoid race conditions
bool requestedUpdate = false; std::atomic<bool> requestedUpdate{false};
public: public:
explicit ActivityManager(GfxRenderer& renderer, MappedInputManager& mappedInput) explicit ActivityManager(GfxRenderer& renderer, MappedInputManager& mappedInput)