ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
link_manager.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 link_manager.h
6 * @brief Multi-interface egress selection + graceful escalation/failover (PROTOCORE_ENABLE_LINK_MANAGER).
7 *
8 * Once a device has more than one network interface (a wired Ethernet PHY brought up alongside WiFi STA,
9 * plus maybe a softAP), something has to decide which one carries traffic and when to switch: escalate to
10 * the wired link when it comes up (usually faster / more reliable), and fail over to WiFi when it drops.
11 * The stack owns the routes and `Physical.egress(Physical.internal)` reports the live one in
12 * `Physical.if_kind`; this is the *policy* that drives it - a small table of interfaces (each a kind +
13 * priority + up/down) with a deterministic "best link that is up" selection, plus change detection so
14 * the app only reconfigures on an actual transition.
15 *
16 * Pure, no heap, no stdlib, host-testable. The real PHY bring-up and the netif reconfigure are
17 * the app's; this just says which interface should be active.
18 */
19
20#ifndef PROTOCORE_LINK_MANAGER_H
21#define PROTOCORE_LINK_MANAGER_H
22
23#include "protocore_config.h" // the entry point: protocore_types.h for the widths
24
25#if PROTOCORE_ENABLE_LINK_MANAGER
26
28
29/** @brief Interface kind (informational; selection is by priority). Stored in a uint8_t field and
30 * compared, so integer constants in a namespacing struct - cast-free. */
31#define LINK_KIND_ETH 0 ///< wired Ethernet PHY.
32#define LINK_KIND_WIFI_STA 1 ///< WiFi station.
33#define LINK_KIND_WIFI_AP 2 ///< WiFi softAP.
34#define LINK_KIND_OTHER 3
35
36/** @brief One managed interface. */
37typedef struct
38{
39 uint8_t kind; ///< LINK_KIND_*.
40 uint8_t priority; ///< higher wins when up (ties break to the lower index).
41 proto_bool up; ///< link currently up.
42} LinkIface;
43
44/** @brief The link-manager state over a caller-owned interface table. */
45typedef struct
46{
47 LinkIface *ifaces;
48 size_t n;
49 int active; ///< index of the active egress, or -1 if none is up.
50} LinkManager;
51
52/** @brief The table a call acts on, and the interface it names. */
53typedef struct
54{
55 LinkManager *m; ///< the manager a call acts on
56 const LinkManager *m_ro; ///< the same manager, where a call only reads it
57 LinkIface *ifaces; ///< the interface table a bind installs
58 size_t n; ///< how many entries it has
59 size_t idx; ///< the interface whose state a set changes
60 proto_bool up; ///< that interface's new carrier state
61} LinkArgs;
62
63/**
64 * @brief The interface failover policy over a caller-owned manager.
65 *
66 * A caller sets the members a call takes, invokes it through ::Link, and reads the outcome off the
67 * same handle. The manager and its table are the caller's.
68 *
69 * @var LinkManagerNs::args the table a call acts on, and the interface it names
70 * @var LinkManagerNs::i32 the interface a select or an active lookup reports, -1 for none
71 * @var LinkManagerNs::from the interface that was active before a set
72 * @var LinkManagerNs::to the one active after it
73 * @var LinkManagerNs::changed that set moved the active interface
74 * @var LinkManagerNs::init bind the table and pick the first active interface
75 * @var LinkManagerNs::select the highest-priority interface that is up, -1 when none is
76 * @var LinkManagerNs::active the interface currently carrying traffic
77 * @var LinkManagerNs::set change one interface's carrier state and reselect
78 *
79 * Higher priority wins; the lower index breaks a tie. No storage member: the manager is the
80 * caller's, so nothing is held here.
81 */
82typedef struct
83{
84 LinkArgs args;
85 int i32;
86 int from;
87 int to;
88 proto_bool changed;
89} LinkVars;
90
91/** @brief The operands and the outcome. */
92extern LinkVars LinkV;
93
94/** @brief The entries. */
95typedef struct
96{
97 void (*const init)(uint8_t *work);
98 void (*const select)(uint8_t *work);
99 void (*const active)(uint8_t *work);
100 void (*const set)(uint8_t *work);
101} LinkManagerNs;
102
103// What the table binds, defined once in the .c and taking one parameter each: everything
104// else an entry needs is an operand in LinkV or a region of the borrow at a fixed offset.
105void protocore_link_init(uint8_t *work);
106void protocore_link_select(uint8_t *work);
107void protocore_link_active(uint8_t *work);
108void protocore_link_set(uint8_t *work);
109
110// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
111// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
112// `Link.init(work)` resolves to a named function and becomes a DIRECT call. An extern table
113// leaves the call indirect and the symbol live at every level, -O2 -flto included.
114static const LinkManagerNs Link __attribute__((unused)) = {
115 .init = protocore_link_init,
116 .select = protocore_link_select,
117 .active = protocore_link_active,
118 .set = protocore_link_set,
119};
120
122
123#endif // PROTOCORE_ENABLE_LINK_MANAGER
124
125#endif // PROTOCORE_LINK_MANAGER_H
#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