ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
hw_health.h
Go to the documentation of this file.
1// ProtoCore v1.0.16 - Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file hw_health.h
6 * @brief Hardware-health diagnostics: rail droop, SPI CRC backoff, GPIO short, cap leakage
7 * (PROTOCORE_ENABLE_HW_HEALTH).
8 *
9 * Four pure decision cores an app feeds with samples it reads from the hardware (ADC millivolts, a SPI
10 * CRC pass/fail, a driven-vs-readback GPIO level, a capacitor decay time). Each turns raw measurements
11 * into an actionable verdict for a "/health" panel or a fail-safe hook, without touching a peripheral
12 * itself:
13 *
14 * - **Power-rail voltage-drop logger**: track a rail's worst droop and count sag / brownout crossings.
15 * - **SPI-bus CRC audit + clock backoff**: a hysteretic state machine that halves the SPI clock after a
16 * run of CRC failures and steps it back up after a run of good transfers.
17 * - **GPIO short-circuit test**: compare a driven level to its readback to spot a short to ground / Vcc.
18 * - **Capacitor-leakage diag**: compare a measured RC decay time to the expected one to spot a leaky
19 * cap (too fast) or a high-ESR / open path (too slow).
20 *
21 * Pure, zero heap, no stdlib, host-testable.
22 */
23
24#ifndef PROTOCORE_HW_HEALTH_H
25#define PROTOCORE_HW_HEALTH_H
26
27#include "protocore_config.h" // the entry point: protocore_types.h for the widths
28
29#if PROTOCORE_ENABLE_HW_HEALTH
30
32
33/** @brief Rail sample verdict (the sole return of protocore_hwhealth_rail_sample). */
34typedef enum PROTO_ENUM_PACKED
35{
36 HW_RAIL_OK = 0, ///< at or above the warn threshold.
37 HW_RAIL_SAG = 1, ///< below warn, at or above crit.
38 HW_RAIL_BROWNOUT = 2 ///< below the crit threshold.
39} HwRailVerdict;
40
41/** @brief GPIO short-circuit verdict (the sole return of protocore_hwhealth_gpio_short). */
42typedef enum PROTO_ENUM_PACKED
43{
44 HW_GPIO_OK = 0, ///< readback matches the driven level.
45 HW_GPIO_SHORT_GND = 1, ///< drove high, read low: shorted to ground.
46 HW_GPIO_SHORT_VCC = 2 ///< drove low, read high: shorted to Vcc.
47} HwGpioVerdict;
48
49/** @brief Capacitor-leakage verdict (the sole return of protocore_hwhealth_cap_leak). */
50typedef enum PROTO_ENUM_PACKED
51{
52 HW_CAP_OK = 0, ///< decay time within tolerance of expected.
53 HW_CAP_LEAK = 1, ///< decays too fast: leaky capacitor.
54 HW_CAP_HIGH_ESR = 2 ///< decays too slow: high-ESR / open charge path.
55} HwCapVerdict;
56
57/** @brief Rolling monitor for one power rail (in millivolts). */
58typedef struct
59{
60 uint32_t nominal_mv;
61 uint32_t warn_mv; ///< below this -> SAG.
62 uint32_t crit_mv; ///< below this -> BROWNOUT.
63 uint32_t min_mv; ///< lowest sample seen (worst droop).
64 uint32_t sag_events;
65 uint32_t brownout_events;
66} HwRailMonitor;
67
68/** @brief Hysteretic SPI clock backoff state. */
69typedef struct
70{
71 uint32_t hz; ///< current clock.
72 uint32_t min_hz; ///< floor.
73 uint32_t max_hz; ///< ceiling.
74 uint16_t fail_streak;
75 uint16_t ok_streak;
76 uint16_t fail_trip; ///< consecutive failures that halve the clock.
77 uint16_t ok_trip; ///< consecutive successes that double the clock.
78} HwSpiBackoff;
79
80/** @brief The rail a call watches, and the reading it just took. */
81typedef struct
82{
83 HwRailMonitor *m; ///< the monitor a call acts on
84 const HwRailMonitor *m_ro; ///< the same monitor, where a call only reads it
85 uint32_t nominal_mv; ///< the rail's nominal level
86 uint32_t warn_mv; ///< below this a reading is a sag
87 uint32_t crit_mv; ///< below this it is a brownout
88 uint32_t mv; ///< the reading just taken
89} HwRailArgs;
90
91/** @brief The bus a backoff governs, and how it just fared. */
92typedef struct
93{
94 HwSpiBackoff *s; ///< the backoff state a call acts on
95 uint32_t start_hz; ///< the clock it starts at
96 uint32_t min_hz; ///< its floor
97 uint32_t max_hz; ///< its ceiling
98 uint16_t fail_trip; ///< consecutive failures before it halves
99 uint16_t ok_trip; ///< consecutive successes before it doubles
100 proto_bool crc_ok; ///< the transfer just completed checked out
101} HwSpiArgs;
102
103/** @brief A pin driven against what it read back, and a discharge against what was expected. */
104typedef struct
105{
106 proto_bool driven_high; ///< the level the pin was driven to
107 proto_bool read_high; ///< the level it read back
108 uint32_t measured_ms; ///< the discharge just timed
109 uint32_t expected_ms; ///< what it should have been
110 uint8_t tol_pct; ///< the tolerance band around that, as a percentage
111} HwProbeArgs;
112
113/** @brief Where a report is written. */
114typedef struct
115{
116 char *out; ///< where the JSON lands
117 size_t cap; ///< how much room it has
118} HwOutArgs;
119
120/**
121 * @brief The hardware health checks over caller-owned monitors.
122 *
123 * A caller sets the members a call takes, invokes it through ::HwHealth, and reads the outcome off
124 * the same handle. Every monitor is the caller's.
125 *
126 * @var HwHealthNs::rail the rail a call watches, and the reading it just took
127 * @var HwHealthNs::spi the bus a backoff governs, and how it just fared
128 * @var HwHealthNs::probe a pin driven against what it read back, and a timed discharge
129 * @var HwHealthNs::out_args where a report is written
130 * @var HwHealthNs::rail_verdict what a rail sample decided
131 * @var HwHealthNs::gpio_verdict what a pin probe decided
132 * @var HwHealthNs::cap_verdict what a discharge probe decided
133 * @var HwHealthNs::hz the clock a backoff settled on
134 * @var HwHealthNs::n bytes a report wrote, or 0 when it did not fit
135 * @var HwHealthNs::rail_init arm a rail monitor at its thresholds
136 * @var HwHealthNs::rail_sample judge one reading and tally what it was
137 * @var HwHealthNs::rail_json report the rail's tallies
138 * @var HwHealthNs::spi_init arm a bus backoff between its floor and ceiling
139 * @var HwHealthNs::spi_result feed one transfer's outcome in and take the new clock
140 * @var HwHealthNs::gpio_short a pin that does not read back what it was driven to
141 * @var HwHealthNs::cap_leak a discharge outside its tolerance band
142 *
143 * No storage member: every call works in the caller's monitor.
144 */
145typedef struct
146{
147 HwRailArgs rail;
148 HwSpiArgs spi;
149 HwProbeArgs probe;
150 HwOutArgs out_args;
151 HwRailVerdict rail_verdict;
152 HwGpioVerdict gpio_verdict;
153 HwCapVerdict cap_verdict;
154 uint32_t hz;
155 size_t n;
156} HwHealthVars;
157
158/** @brief The operands and the outcome. */
159extern HwHealthVars HwHealthV;
160
161/** @brief The entries. */
162typedef struct
163{
164 void (*const rail_init)(uint8_t *work);
165 void (*const rail_sample)(uint8_t *work);
166 void (*const rail_json)(uint8_t *work);
167 void (*const spi_init)(uint8_t *work);
168 void (*const spi_result)(uint8_t *work);
169 void (*const gpio_short)(uint8_t *work);
170 void (*const cap_leak)(uint8_t *work);
171} HwHealthNs;
172
173// What the table binds, defined once in the .c and taking one parameter each: everything
174// else an entry needs is an operand in HwHealthV or a region of the borrow at a fixed offset.
175void protocore_hw_health_rail_init(uint8_t *work);
176void protocore_hw_health_rail_sample(uint8_t *work);
177void protocore_hw_health_rail_json(uint8_t *work);
178void protocore_hw_health_spi_init(uint8_t *work);
179void protocore_hw_health_spi_result(uint8_t *work);
180void protocore_hw_health_gpio_short(uint8_t *work);
181void protocore_hw_health_cap_leak(uint8_t *work);
182
183// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
184// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
185// `HwHealth.rail_init(work)` resolves to a named function and becomes a DIRECT call. An extern table
186// leaves the call indirect and the symbol live at every level, -O2 -flto included.
187static const HwHealthNs HwHealth __attribute__((unused)) = {
188 .rail_init = protocore_hw_health_rail_init,
189 .rail_sample = protocore_hw_health_rail_sample,
190 .rail_json = protocore_hw_health_rail_json,
191 .spi_init = protocore_hw_health_spi_init,
192 .spi_result = protocore_hw_health_spi_result,
193 .gpio_short = protocore_hw_health_gpio_short,
194 .cap_leak = protocore_hw_health_cap_leak,
195};
196
198
199#endif // PROTOCORE_ENABLE_HW_HEALTH
200
201#endif // PROTOCORE_HW_HEALTH_H
PROTO_ENUM_PACKED
Application protocol spoken on a listener port or connection slot.
#define PROTOCORE_BEGIN_DECLS
Give a header's declarations C linkage, so their symbol names carry no parameter types.
Definition types.h:96
_Bool proto_bool
The truth value.
Definition types.h:64
#define PROTOCORE_END_DECLS
Definition types.h:97