mirror of
https://github.com/liquidraver/ZephCore.git
synced 2026-09-02 04:08:22 +00:00
107 lines
4.0 KiB
C
107 lines
4.0 KiB
C
/*
|
|
* SPDX-License-Identifier: MIT
|
|
* ESP32 light-sleep console guards.
|
|
*
|
|
* Two independent problems sit between ESP32 light sleep and a usable serial
|
|
* console. This file solves both; neither fix substitutes for the other.
|
|
*
|
|
* 1. TRUNCATED OUTPUT. soc/espressif/common/power.c calls
|
|
* esp_light_sleep_start() with no wait for the console UART to drain — the
|
|
* TX-idle flush that ESP-IDF performs lives behind its own console Kconfig,
|
|
* which is not part of a Zephyr build. Sleeping with bytes still in the
|
|
* FIFO cuts the line mid-character. A pm_notifier's state_entry callback
|
|
* runs immediately before pm_state_set() (subsys/pm/pm.c), which is exactly
|
|
* the right moment to poll the UART to idle.
|
|
*
|
|
* 2. UNREACHABLE INPUT. Nothing arms a UART wake source — Zephyr's PM path
|
|
* never calls esp_sleep_enable_uart_wakeup(), and on the boards this runs on
|
|
* the console RX pad (GPIO44 on an S3 uart0) is outside the RTC range that
|
|
* could carry a GPIO wake instead. Characters typed at a sleeping node are
|
|
* therefore dropped, and no amount of TX handling changes that.
|
|
*
|
|
* The answer here is a boot window rather than a wake source: sleep is
|
|
* blocked outright for ZEPHCORE_PM_BOOT_AWAKE_MS after boot, so a node is
|
|
* always reachable for that long. This works better in practice than it
|
|
* sounds, because the USB bridge on these boards drives EN from DTR — a
|
|
* terminal that asserts DTR on open resets the board, which re-arms the
|
|
* window at the moment someone connects. A terminal that does not toggle
|
|
* DTR gets no window, and the node has to be power-cycled to answer over
|
|
* USB; that is the known limitation of this approach. Arming
|
|
* esp_sleep_enable_uart_wakeup() and re-taking the lock on console activity
|
|
* would remove it, at the cost of the first keystroke (the hardware
|
|
* consumes it as the wake trigger).
|
|
*
|
|
* Remote admin over LoRa is unaffected by any of this: DIO1 is armed as a wake
|
|
* source by patches/zephyr/0012 and wakes the SoC on a received packet.
|
|
*/
|
|
|
|
#include <zephyr/kernel.h>
|
|
#include <zephyr/init.h>
|
|
#include <zephyr/pm/pm.h>
|
|
|
|
#include <esp_rom_uart.h>
|
|
|
|
#include "pm_sleep_guard.h"
|
|
|
|
#include <zephyr/logging/log.h>
|
|
LOG_MODULE_REGISTER(zephcore_pm, CONFIG_ZEPHCORE_MAIN_LOG_LEVEL);
|
|
|
|
/* ========== 1. Flush the console before every sleep ========== */
|
|
|
|
static void pm_console_flush(enum pm_state state)
|
|
{
|
|
if (state != PM_STATE_STANDBY) {
|
|
return;
|
|
}
|
|
|
|
/* Polls a status register until the shifter empties — no locking, so it
|
|
* is safe in this context (called with the scheduler locked). Bounded
|
|
* by the FIFO depth at the configured baud: ~11 ms worst case for a full
|
|
* 128-byte FIFO at 115200, and normally microseconds. */
|
|
esp_rom_uart_tx_wait_idle(CONFIG_ZEPHCORE_PM_CONSOLE_UART_NUM);
|
|
}
|
|
|
|
static struct pm_notifier console_notifier = {
|
|
.state_entry = pm_console_flush,
|
|
};
|
|
|
|
/* ========== 2. Keep the node awake for a window after boot ========== */
|
|
|
|
#if CONFIG_ZEPHCORE_PM_BOOT_AWAKE_MS > 0
|
|
|
|
static void boot_window_expired(struct k_work *work)
|
|
{
|
|
ARG_UNUSED(work);
|
|
|
|
zc_pm_unblock_sleep();
|
|
LOG_INF("boot window elapsed — light sleep enabled "
|
|
"(USB console answers again after a reset)");
|
|
}
|
|
|
|
static K_WORK_DELAYABLE_DEFINE(boot_window_work, boot_window_expired);
|
|
|
|
#endif /* CONFIG_ZEPHCORE_PM_BOOT_AWAKE_MS > 0 */
|
|
|
|
static int pm_console_init(void)
|
|
{
|
|
pm_notifier_register(&console_notifier);
|
|
|
|
#if CONFIG_ZEPHCORE_PM_BOOT_AWAKE_MS > 0
|
|
/* Taken here rather than released here: the lock is held from init and
|
|
* dropped by the work item, so there is no window at startup in which
|
|
* the node could sleep before the guard is in place. */
|
|
zc_pm_block_sleep();
|
|
k_work_schedule(&boot_window_work,
|
|
K_MSEC(CONFIG_ZEPHCORE_PM_BOOT_AWAKE_MS));
|
|
|
|
LOG_INF("light sleep deferred %d ms (console configuration window)",
|
|
CONFIG_ZEPHCORE_PM_BOOT_AWAKE_MS);
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* POST_KERNEL: needs the kernel work queue, and must be in place before the
|
|
* application can idle long enough to sleep. */
|
|
SYS_INIT(pm_console_init, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
|