diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b770254f96..8203b07eb42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,6 +57,7 @@ ### Fixed +- On the X4 Pro, the e-ink panel is powered off before every restart (after an OTA or SD card update, or a silent restart when leaving Wi-Fi) instead of staying powered until the reset. - File Transfer choices no longer appear preselected when opened on a touch device. - On the X4 Pro and Sticky, the Home screen no longer stalls waiting for the SD card while the Library indexes in the background. - Small images that are scaled up, such as a short progressive JPEG, are now cached after the first draw instead of being decoded again on every screen refresh. diff --git a/src/activities/ActivityManager.cpp b/src/activities/ActivityManager.cpp index 93be0fdd3a4..c968a651d7c 100644 --- a/src/activities/ActivityManager.cpp +++ b/src/activities/ActivityManager.cpp @@ -1351,6 +1351,10 @@ RenderLock::RenderLock(const Mode mode) { RenderLock::RenderLock([[maybe_unused]] Activity&, const Mode mode) : RenderLock(mode) {} +RenderLock::RenderLock(const unsigned long timeoutMs) { + isLocked = xSemaphoreTake(activityManager.renderingMutex, pdMS_TO_TICKS(timeoutMs)) == pdTRUE; +} + RenderLock::~RenderLock() { if (isLocked) { xSemaphoreGive(activityManager.renderingMutex); @@ -1373,3 +1377,7 @@ void RenderLock::unlock() { * @note Must not be called from ISR context — xSemaphoreGetMutexHolder is not ISR-safe. */ bool RenderLock::peek() { return xSemaphoreGetMutexHolder(activityManager.renderingMutex) != nullptr; } + +bool RenderLock::heldByCaller() { + return xSemaphoreGetMutexHolder(activityManager.renderingMutex) == xTaskGetCurrentTaskHandle(); +} diff --git a/src/activities/RenderLock.h b/src/activities/RenderLock.h index 55f2227ae9b..db2175853d6 100644 --- a/src/activities/RenderLock.h +++ b/src/activities/RenderLock.h @@ -10,10 +10,12 @@ class RenderLock { enum class Mode { Blocking, Try }; explicit RenderLock(Mode mode = Mode::Blocking); explicit RenderLock(Activity&, Mode mode = Mode::Blocking); // Compatibility overload used by activity call sites. + explicit RenderLock(unsigned long timeoutMs); // bounded take; check ownsLock() RenderLock(const RenderLock&) = delete; RenderLock& operator=(const RenderLock&) = delete; ~RenderLock(); void unlock(); bool ownsLock() const { return isLocked; } static bool peek(); + static bool heldByCaller(); }; diff --git a/src/main.cpp b/src/main.cpp index 482a8701bb1..603bba13b2a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -34,6 +34,7 @@ #include "util/BootReason.h" #ifndef SIMULATOR +#include #include #endif @@ -49,6 +50,7 @@ #include "SilentRestart.h" #include "activities/Activity.h" #include "activities/ActivityManager.h" +#include "activities/RenderLock.h" #include "activities/boot_sleep/ImageFolderIndex.h" #include "activities/home/BookActions.h" #include "activities/reader/KOReaderSyncActivity.h" @@ -1101,6 +1103,28 @@ void enterDeepSleep(bool fromTimeout) { powerManager.startDeepSleep(gpio); } +#if !defined(SIMULATOR) && FREEINK_DEVICE_X4PRO +// Every esp_restart() (silent restart, OTA, SD update) runs this: the panel +// stays powered between refreshes, so power it off (POF + deep sleep) instead +// of leaving the booster on until the reset. begin() re-inits the controller +// and the first paint after boot is non-differential, so lost RAM is harmless. +// X4 Pro only: SDMMC keeps the SD card off the display's SPI bus, so the bus +// lock cannot block here behind an SD transfer. +static void powerOffPanelOnRestart() { + // ponytail: 3 s covers the longest refresh and stays under the 5 s task + // watchdog; a longer hold leaves the panel on. + constexpr unsigned long RENDER_LOCK_WAIT_MS = 3000; + const bool ownLock = RenderLock::heldByCaller(); + RenderLock lock(ownLock ? 0UL : RENDER_LOCK_WAIT_MS); + if (!ownLock && !lock.ownsLock()) { + LOG_ERR("MAIN", "Render busy at restart; panel left powered"); + return; + } + display.deepSleep(); + LOG_INF("MAIN", "Panel powered off before restart"); +} +#endif + void setupDisplayAndFonts(const bool seamless, const bool loadReaderResources, const bool useReaderRenderStack) { #if !defined(SIMULATOR) && !FREEINK_MCU_C3 // C3 X3/X4 detection already runs in HalGPIO::begin() before SPI owns the @@ -1120,6 +1144,9 @@ void setupDisplayAndFonts(const bool seamless, const bool loadReaderResources, c display.begin(); #else display.begin(seamless); +#if FREEINK_DEVICE_X4PRO + esp_register_shutdown_handler(powerOffPanelOnRestart); +#endif #endif renderer.begin(); display.setInverted(SETTINGS.screenInverted != 0);