FastLED 3.10.8
Loading...
Searching...
No Matches
watchdog.cpp.hpp
Go to the documentation of this file.
1// IWYU pragma: private
2
14
15#include "fl/wdt/watchdog.h"
16
17namespace fl {
18
19// =============================================================================
20// ResetInfo helpers
21// =============================================================================
22
23// Default subcauseName(): empty view. Per-platform richer subcause naming
24// will be wired through a follow-up issue (#2755 tracks the design); for
25// now the empty default works for every backend and describe() handles
26// the subcauseId==0 case by skipping the parenthesized name.
30
31namespace platforms {
32
33// Write `value` as a lowercase hex string into `out`, prefixed with "0x".
34// Returns bytes written (always <= 10, since u32 max is 0xFFFFFFFF = 8 hex
35// digits + 2 prefix chars). Does not write a NUL. Truncates if out is small.
36inline fl::size writeHex(fl::span<char> out, fl::u32 value) FL_NO_EXCEPT {
37 char tmp[10];
38 tmp[0] = '0';
39 tmp[1] = 'x';
40 static const char hex[] = "0123456789abcdef";
41 for (int i = 7; i >= 0; --i) {
42 tmp[2 + (7 - i)] = hex[(value >> (i * 4)) & 0xFu];
43 }
44 fl::size n = (out.size() < 10) ? out.size() : 10;
45 for (fl::size i = 0; i < n; ++i) out[i] = tmp[i];
46 return n;
47}
48
49// Copy a string_view into out[]. Returns bytes copied, truncated to out.size().
51 fl::size n = (out.size() < sv.size()) ? out.size() : sv.size();
52 for (fl::size i = 0; i < n; ++i) out[i] = sv[i];
53 return n;
54}
55
56inline fl::size writeChar(fl::span<char> out, char c) FL_NO_EXCEPT {
57 if (out.size() == 0) return 0;
58 out[0] = c;
59 return 1;
60}
61
62inline fl::size writeNulIfRoom(fl::span<char> out, fl::size written) FL_NO_EXCEPT {
63 if (written < out.size()) out[written] = '\0';
64 return written;
65}
66
67} // namespace platforms
68
69fl::size ResetInfo::describe(fl::span<char> out, bool verbose) const FL_NO_EXCEPT {
70 fl::size n = 0;
71 // <cause>
72 n += platforms::writeView(out.subspan(n), causeName());
73 // ( <sub> ) if subcauseId != 0
74 if (subcauseId != 0) {
76 if (sub.size() != 0) {
77 n += platforms::writeView(out.subspan(n), fl::string_view(" ("));
78 n += platforms::writeView(out.subspan(n), sub);
79 n += platforms::writeChar(out.subspan(n), ')');
80 }
81 }
82 if (verbose) {
83 n += platforms::writeView(out.subspan(n), fl::string_view(" raw="));
84 n += platforms::writeHex(out.subspan(n), rawRegister);
85 }
86 return platforms::writeNulIfRoom(out, n);
87}
88
89// =============================================================================
90// Watchdog::lastResetInfo() default — platforms can override
91// =============================================================================
92
93// Default: lift the normalized cause into a ResetInfo, leaving
94// subcauseId/rawRegister at 0. Per-platform richer extraction will be wired
95// through a follow-up to issue #2755 (every backend uses this default for now).
96// =============================================================================
97// Boot-loop bootloader escape (#3713)
98//
99// Platform-agnostic policy layered over the per-platform crash counter and
100// bootloader reboot. Kept here rather than in each `.impl.hpp` so all 13
101// backends share one implementation.
102// =============================================================================
103
107
111
116
118 if (!shouldEscapeToBootloader()) return false;
119 // Deliberately does NOT clear the crash counter: where bootloader reboot
120 // is unsupported this returns false, and zeroing the count would destroy
121 // the evidence of the very boot loop being detected.
122 return rebootIntoBootloader();
123}
124
125// =============================================================================
126// Early-boot loop guard counter (fl/wdt/boot_guard.h)
127//
128// Storage is per-platform: defined in the watchdog dispatcher TU by the
129// platform impl, or by the no-op in platforms/watchdog.impl.cpp.hpp.
130// =============================================================================
131
135
140
142 ResetInfo info{};
143 info.cause = lastResetCause();
144 info.subcauseId = 0;
145 info.rawRegister = 0;
146 return info;
147}
148
149// =============================================================================
150// ScopedWatchdog
151// =============================================================================
152
153namespace platforms {
154
155// Forward-declared in this TU to avoid pulling in fl::print/println headers
156// from a public header. The dispatcher TU includes the necessary print
157// header before this file.
160
161// Counts simultaneously-alive ScopedWatchdog instances. >1 is almost always
162// a programmer error: nested guards mean the lazy-init's timeout argument
163// of the second instance is silently ignored, and timeout semantics across
164// the two scopes become ambiguous. Singleton-style accessor (function-local
165// static) so the counter has well-defined lifetime even if a guard is
166// constructed before main().
168 static int count = 0;
169 return count;
170}
171
172// First-init helper, used by ScopedWatchdog's constructor. Runs only once
173// per process. Single-threaded loop() is the canonical caller — no atomics
174// are needed for the guard byte on supported MCUs.
175inline void scopedWatchdogFirstInit(fl::u32 timeout_ms) FL_NO_EXCEPT {
177 wdt.begin(timeout_ms);
178
179 ResetInfo info = wdt.lastResetInfo();
180 if (info.cause == ResetCause::WATCHDOG ||
181 info.cause == ResetCause::PANIC ||
182 info.cause == ResetCause::LOCKUP) {
183 char buf[128];
184 fl::size n = info.describe(fl::span<char>(buf, sizeof(buf)), /*verbose=*/true);
185 scopedWatchdogPrintLine(fl::string_view("[FastLED.watchdog] recovered from:"));
187
188 if (wdt.hasCrashReport()) {
189 // Hook for Tier 2 platforms. Future revision: also emit a
190 // multi-line CrashReport::describe() result here. For now the
191 // bare cause + raw register is the documented contract.
192 }
194 }
195 scopedWatchdogPrintLine(fl::string_view("[FastLED.watchdog] armed via FL_WATCHDOG_AUTO()"));
196}
197
198} // namespace platforms
199
201 static bool sInitialized = false;
202 if (!sInitialized) {
203 sInitialized = true;
205 }
206
207 // Single-instance enforcement. A second simultaneously-alive ScopedWatchdog
208 // is almost always a bug — the timeout argument here was silently ignored
209 // (only the first instance gets to arm the WDT). Warn once per process so
210 // the developer notices, but still feed so the program survives.
212 if (count >= 1) {
213 static bool sWarnedOnce = false;
214 if (!sWarnedOnce) {
215 sWarnedOnce = true;
217 "[FastLED.watchdog] WARN: nested FL_WATCHDOG_AUTO() detected — "
218 "only one scoped guard should be alive at a time"));
219 }
220 }
221 ++count;
222
224}
225
228 if (count > 0) --count;
230}
231
235
236} // namespace fl
~ScopedWatchdog() FL_NO_EXCEPT
Feed the watchdog at end-of-scope so the next loop() iteration has a clean deadline window.
ScopedWatchdog() FL_NO_EXCEPT
Default construct with the library default timeout (15 000 ms).
Definition watchdog.h:272
static int activeScopeCount() FL_NO_EXCEPT
Observability: number of simultaneously-alive ScopedWatchdog instances.
static Watchdog & instance() FL_NO_EXCEPT
bool shouldEscapeToBootloader() const FL_NO_EXCEPT
True when the escape is enabled and the board has hit the consecutive-watchdog-reset threshold.
fl::u16 bootloaderEscapeThreshold() const FL_NO_EXCEPT
Boot-loop escape hatch: drop to the bootloader instead of looping forever, so an unattended host can ...
fl::u32 bootGuardCount() const FL_NO_EXCEPT
Early-boot loop guard counter (see fl/wdt/boot_guard.h): consecutive boots since the last markBootHea...
void begin(fl::u32 timeout_ms) FL_NO_EXCEPT
void markBootHealthy() FL_NO_EXCEPT
Tell the early-boot guard this boot is healthy, zeroing its counter.
void feed() FL_NO_EXCEPT
void setBootloaderEscapeThreshold(fl::u16 threshold) FL_NO_EXCEPT
Set the consecutive-watchdog-reset count that triggers the escape.
fl::u16 mBootloaderEscapeThreshold
Definition watchdog.h:236
ResetInfo lastResetInfo() const FL_NO_EXCEPT
Detailed reset information including platform raw register + subcause id.
fl::u16 consecutiveCrashCount() const FL_NO_EXCEPT
ResetCause lastResetCause() const FL_NO_EXCEPT
bool hasCrashReport() const FL_NO_EXCEPT
bool escapeToBootloaderIfLooping() FL_NO_EXCEPT
Reboot into the bootloader if this looks like a boot loop.
bool rebootIntoBootloader() FL_NO_EXCEPT
Unified cross-platform watchdog interface.
Definition watchdog.h:123
constexpr fl::size size() const FL_NO_EXCEPT
Definition string_view.h:99
fl::size writeNulIfRoom(fl::span< char > out, fl::size written) FL_NO_EXCEPT
fl::size writeChar(fl::span< char > out, char c) FL_NO_EXCEPT
void scopedWatchdogPrintLine(fl::string_view sv) FL_NO_EXCEPT
fl::size writeView(fl::span< char > out, fl::string_view sv) FL_NO_EXCEPT
void scopedWatchdogPause3s() FL_NO_EXCEPT
void watchdogBootGuardReleaseEarlyTimer() FL_NO_EXCEPT
Disable the boot guard's early watchdog timer if it is still running and the sketch never armed the w...
fl::size writeHex(fl::span< char > out, fl::u32 value) FL_NO_EXCEPT
void scopedWatchdogFirstInit(fl::u32 timeout_ms) FL_NO_EXCEPT
void watchdogBootGuardWrite(fl::u32 boots) FL_NO_EXCEPT
fl::u32 watchdogBootGuardRead() FL_NO_EXCEPT
Boot-guard counter storage, supplied by the platform watchdog impl (or the no-op).
int & scopedWatchdogActiveCount() FL_NO_EXCEPT
u8 u8 fract8 blur_amount FL_NO_EXCEPT
Definition blur.h:186
constexpr int type_rank< T >::value
const hex_t hex
Definition ios.cpp.hpp:11
Base definition for an LED controller.
Definition crgb.hpp:179
ResetCause cause
Normalized cross-platform enum.
Definition watchdog.h:83
fl::u32 rawRegister
Raw value of the platform's reset-cause register.
Definition watchdog.h:85
fl::size describe(fl::span< char > out, bool verbose=false) const FL_NO_EXCEPT
Write a single-line human-readable description into the caller's buffer.
fl::string_view subcauseName() const FL_NO_EXCEPT
Platform-specific subcause name (zero-cost, static string_view).
fl::string_view causeName() const FL_NO_EXCEPT
Cause name (zero-cost, static string_view).
Definition watchdog.h:88
fl::u8 subcauseId
Platform-specific subcause id (0 = none)
Definition watchdog.h:84
Detailed reset information bundling the normalized cause with a platform-specific subcause id and the...
Definition watchdog.h:82
Unified cross-platform Watchdog Timer API for FastLED.