Files
RTLPlayground/rtl837x_lacp.h
T
DrDoof 56686185ce lacp: implement IEEE 802.3ad link aggregation
Dynamic link aggregation on top of the static LAGG, for RTL8372/8373:
receive, selection and mux state machines (coupled mux, one aggregator
per group), with "lacp show" to look inside. Each of the four trunk
groups runs its own aggregator on the candidate ports assigned with
"lag <n> lacp <ports>"; the engine starts with the first such group and
stops with the last.

LACPDUs reach the CPU through the reserved-multicast trap, so delivery
needs no L2 entry and nothing written per VLAN or per PVID. The trap
acts on the address before the VLAN lookup and before the ingress
filters, so a LACPDU also arrives on a port that admits tagged frames
only and on a port STP holds in blocking, neither of which a forwarded
one reaches. A trapped frame carries no VLAN tag,
so lacp_in() takes the offset of the PDU body from TRAP_RX_BODY(). The
action is set with trap_rma_set(), which leaves the flags sharing that
register alone, and "lacp off" puts the slow-protocols drop default
back. doc/lacp.md and doc/link_aggregation.md record what was measured,
including why the trap looked like a dead end at first.

The web UI gets a Static/LACP mode per group on the LAG page with live
status, and the startup-config validator knows the per-group syntax. A
host-side sandbox for the state machines runs in the test harness
together with the LAG hash check. It drives both forms of an incoming
LACPDU, so the body offset of a trapped one is covered as well.

The L2 table page iterated with the "next unicast" read method, so
static multicast entries such as an IGMP group were invisible. It now
iterates with "next address", skips L3 entries (their layout carries IP
addresses where a MAC is expected), and reports the lowest member of a
multicast entry's port mask, so such an entry shows up as a static
entry on that port.

A port taken out of LACP forgets its partner, so "lacp show" after
"lacp off" no longer reports one. Applying the same candidate set to a
group again, as the LAG page does on every Apply, leaves the trunk
alone instead of tearing it down for a second or two. lacp_in() checks
the frame length and ethertype before reading a LACPDU, so a runt or a
foreign frame on the slow-protocols address cannot leave stale buffer
bytes as a partner. The sandbox covers these three in T20-T22. On the
LAG page, LACP mode with no ports checked sends "lag N lacp off", and
the startup-config validator wants at least one port after "lacp".
2026-10-14 20:18:07 -05:00

102 lines
5.0 KiB
C

#ifndef _RTL837X_LACP_H_
#define _RTL837X_LACP_H_
/*
* LACP (IEEE 802.3ad Clause 43) for the RTL837x platform.
* This code is in the Public Domain.
*/
#include <stdint.h>
void lacp_init(void) __banked; /* boot init: clear per-LAG state */
void lacp_in(void) __banked;
void lacp_timers(void) __banked;
void lacp_off(void) __banked;
void lacp_show(void) __banked; /* "lacp show" - per-port state + RX counters */
/* Assign a candidate-port mask to a LACP-mode LAG (`lag <n> lacp <ports>`).
* ports == 0 removes the LAG from LACP management. Enables the engine on first
* LACP LAG and tears it down when the last one goes away. */
void lacp_lag_set(uint8_t lag, uint16_t ports) __banked;
/*
* Per-LAG LACP: each of the 4 hardware trunk groups (0-3) can independently run
* LACP on an admin-assigned set of candidate ports, forming its own aggregator
* (its own elected partner System). A port participates in LACP only if it has
* been assigned to a LACP-mode LAG (lacp_port_lag[port] != LACP_LAG_NONE).
* Ports of the same LAG that converge with a consistent partner join that LAG's
* hardware trunk. This is the port-channel model: `lag <n> lacp <ports>`.
*/
#define LACP_NUM_LAGS 4
#define LACP_LAG_NONE 0xff /* port not assigned to any LACP LAG */
/* Protocol state, exposed read-only for the web UI (page_impl.c send_lacp())
* and the serial console. Owned by rtl837x_lacp.c / rtlplayground.c. */
extern __xdata uint8_t lacpEnabled;
extern __xdata uint8_t lacp_actor_state[10];
extern __xdata uint8_t lacp_partner_state[10];
extern __xdata uint8_t lacp_rx_state[10];
extern __xdata uint8_t lacp_partner_sys[10][6];
extern __xdata uint16_t lacp_rx_count[10];
extern __xdata uint8_t lacp_port_lag[10]; /* LAG (0-3) a port runs LACP on, or LACP_LAG_NONE */
extern __xdata uint16_t lacp_lag_ports[LACP_NUM_LAGS]; /* admin candidate-port mask per LACP LAG (0 = LAG not LACP) */
extern __xdata uint8_t lacp_agg_sys[LACP_NUM_LAGS][6]; /* elected partner System per LAG */
extern __xdata uint8_t lacp_agg_valid[LACP_NUM_LAGS]; /* aggregator elected for this LAG */
extern __xdata uint16_t lacp_members_last[LACP_NUM_LAGS];/* trunk members we last programmed per LAG */
/* Slow-Protocols / LACPDU identifiers (802.3ad 43.4) */
#define SLOW_PROTO_ETHERTYPE 0x8809
#define SLOW_PROTO_SUBTYPE_LACP 0x01
#define LACP_VERSION 0x01
#define LACP_DST5 0x02 /* 01:80:C2:00:00:02 last octet */
/* LACPDU TLV framing (802.3ad 43.4.2): type bytes + fixed TLV lengths. */
#define LACP_TLV_ACTOR 0x01
#define LACP_TLV_PARTNER 0x02
#define LACP_TLV_COLLECTOR 0x03
#define LACP_TLV_TERMINATOR 0x00
#define LACP_TLV_LEN_INFO 0x14 /* Actor/Partner Information TLV length */
#define LACP_TLV_LEN_COLLECTOR 0x10 /* Collector TLV length */
/* Our aggregation identity: System priority is a constant (Linux 802.3ad
* defaults to the same 0xffff); the default Actor port priority likewise. */
#define LACP_SYS_PRIO 0xffff
#define LACP_DEF_PORT_PRIO 0x00ff
/* Actor_State / Partner_State flag bits (802.3ad 43.4.2) */
#define LACP_STATE_ACTIVITY 0x01 /* 1 = Active LACP (send periodically) */
#define LACP_STATE_TIMEOUT 0x02 /* 1 = Short (fast) timeout */
#define LACP_STATE_AGGREGATION 0x04 /* 1 = link is aggregatable */
#define LACP_STATE_SYNC 0x08 /* 1 = in sync with partner */
#define LACP_STATE_COLLECTING 0x10
#define LACP_STATE_DISTRIBUTING 0x20
#define LACP_STATE_DEFAULTED 0x40 /* partner info is administrative default*/
#define LACP_STATE_EXPIRED 0x80 /* wire-format doc; unused by the simplified RX machine */
/* Fully-participating actor: in sync AND collecting AND distributing. */
#define LACP_STATE_FULL (LACP_STATE_SYNC | LACP_STATE_COLLECTING | LACP_STATE_DISTRIBUTING)
/*
* Timer units: one step of the LACP timers, taken every four system ticks,
* 50 per second. A long pass of the main loop is caught up on a few steps at
* a time, so a period is a range and the worst case is what matters.
*
* The transmit periods are not free to choose. Our own timeouts only decide
* how patient we are, but our TX period decides when the PARTNER gives up on
* us: 802.3ad pairs fast periodic 1 s with a 3 s short timeout, and slow
* periodic 30 s with a 90 s long timeout. A period whose worst case reaches
* the partner's threshold makes it expire us intermittently, which reads as
* an aggregate that works but never stops churning. */
#define LACP_FAST_PERIODIC 0x0032 /* fast TX 1 s; partner expires at 3 s */
#define LACP_SLOW_PERIODIC 0x05dc /* slow TX 30 s; partner expires at 90 s */
#define LACP_SHORT_TIMEOUT 0x00c8 /* silent partner dropped after 4 s (802.3ad: 3 s) */
#define LACP_LONG_TIMEOUT 0x1770 /* long-timeout variant, after 120 s (802.3ad: 90 s) */
/* Per-port LACP receive-machine state (802.3ad 43.4.12) */
#define LACP_RX_INITIALIZE 0
#define LACP_RX_PORT_DISABLED 1 /* numbering reserved (exported via /lacp.json "rs") */
#define LACP_RX_EXPIRED 2 /* numbering reserved (exported via /lacp.json "rs") */
#define LACP_RX_DEFAULTED 3
#define LACP_RX_CURRENT 4
#endif