21void pc_hotswap_core_init(HotswapCore *c, uint8_t fail_threshold, uint32_t probe_interval_ms, uint32_t now)
27 c->state = StorageState::ABSENT;
30 c->fail_threshold = fail_threshold ? fail_threshold : 1;
31 c->probe_interval_ms = probe_interval_ms;
33 c->last_probe_ms = now - probe_interval_ms;
38bool pc_hotswap_core_io(HotswapCore *c,
bool ok)
40 if (!c || c->state != StorageState::READY)
49 if (c->fail_run < 0xFF)
53 if (c->fail_run < c->fail_threshold)
57 c->state = StorageState::FAULTED;
62bool pc_hotswap_core_due(
const HotswapCore *c, uint32_t now)
64 if (!c || c->state == StorageState::READY)
69 return (now - c->last_probe_ms) >= c->probe_interval_ms;
72bool pc_hotswap_core_probe(HotswapCore *c,
bool present,
bool mounted, uint32_t now)
78 c->last_probe_ms = now;
79 StorageState was = c->state;
80 if (present && mounted)
82 c->state = StorageState::READY;
84 if (was != StorageState::READY)
92 c->state = StorageState::ABSENT;
95 return c->state != was;
106 pc_hotswap_mount mount;
107 pc_hotswap_unmount unmount;
108 pc_hotswap_present present;
109 pc_hotswap_event event;
113static HotswapCtx s_hs = {{StorageState::ABSENT, 0, 1, 0, 0, 0, 0},
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
false};
115static void hs_notify(StorageState from, StorageState to)
119 if (s_hs.event && from != to)
121 s_hs.event(from, to, s_hs.ctx);
125void pc_hotswap_begin(pc_hotswap_mount mount, pc_hotswap_unmount unmount, pc_hotswap_present present,
void *ctx)
128 s_hs.unmount = unmount;
129 s_hs.present = present;
135void pc_hotswap_set_event_cb(pc_hotswap_event cb)
140void pc_hotswap_poll_at(uint32_t now)
142 if (!s_hs.begun || !pc_hotswap_core_due(&s_hs.core, now))
149 if (s_hs.core.state == StorageState::FAULTED && s_hs.unmount)
151 s_hs.unmount(s_hs.ctx);
154 bool present = s_hs.present ? s_hs.present(s_hs.ctx) :
true;
155 bool mounted =
false;
156 if (present && s_hs.mount)
158 mounted = s_hs.mount(s_hs.ctx);
161 StorageState was = s_hs.core.state;
162 if (pc_hotswap_core_probe(&s_hs.core, present, mounted, now))
164 hs_notify(was, s_hs.core.state);
168void pc_hotswap_poll(
void)
173bool pc_hotswap_ready(
void)
175 return s_hs.core.state == StorageState::READY;
178void pc_hotswap_io(
bool ok)
180 StorageState was = s_hs.core.state;
181 if (!pc_hotswap_core_io(&s_hs.core, ok))
189 s_hs.unmount(s_hs.ctx);
191 hs_notify(was, s_hs.core.state);
194StorageState pc_hotswap_state(
void)
196 return s_hs.core.state;
199const char *pc_hotswap_state_name(StorageState s)
203 case StorageState::READY:
205 case StorageState::FAULTED:
207 case StorageState::ABSENT:
213size_t pc_hotswap_json(
char *out,
size_t cap)
215 if (!out || cap == 0)
219 pc_sb sb_out = {out, cap, 0,
true};
221 pc_sb_put(&sb_out, pc_hotswap_state_name(s_hs.core.state));
223 pc_sb_u32(&sb_out, (uint32_t)((
unsigned)s_hs.core.mounts));
225 pc_sb_u32(&sb_out, (uint32_t)((
unsigned)s_hs.core.faults));
Pluggable monotonic clock for all library timing.
uint32_t pc_millis(void)
The library's monotonic time at 1000 Hz (milliseconds).
Safeties for removable storage that can vanish mid-write (PC_ENABLE_HOTSWAP).
#define PC_HOTSWAP_FAIL_THRESHOLD
Consecutive I/O failures that declare a removable volume gone.
#define PC_HOTSWAP_PROBE_MS
Minimum gap between remount attempts while a volume is absent or faulted (ms).
Bounded no-heap string builder that fails closed on overflow (one shared copy).
size_t pc_sb_finish(pc_sb *b)
NUL-terminate and return the built length, or 0 if the build overflowed.
void pc_sb_put(pc_sb *b, const char *s)
Append NUL-terminated s; leaves the buffer untouched and clears ok if it would not fit.
void pc_sb_u32(pc_sb *b, uint32_t v)
Append v as decimal (no leading zeros; "0" for zero).
Bump-append target; ok latches false once an append would overflow cap.