lacp: keep comments to the frame layout

The comments repeated the documentation, described tests or explained
the code to its author. What stays describes the LACPDU structures. The
timer constants are decimal, in units of the LACP timer.
This commit is contained in:
d00f authored and HL Yi committed 2026-10-14 20:18:07 -05:00
1 parent 7e90f6a126
commit 6cd1f683bb
8 files changed
+52 -261

No files matched your search

+25 -135
View File
@@ -18,11 +18,10 @@
#include "machine.h"
extern __code const struct machine machine;
extern __xdata struct machine_runtime machine_detected; /* runtime chip detection, owned by rtl837x_port.c */
extern __xdata uint16_t management_vlan; /* owned by rtlplayground.c; suppressed per-frame for slow protocols */
extern __xdata struct machine_runtime machine_detected;
extern __xdata uint16_t management_vlan;
extern __xdata struct uip_eth_addr uip_ethaddr;
extern __xdata uint8_t uip_buf[UIP_CONF_BUFFER_SIZE + 2];
/* lacpEnabled: extern in rtl837x_lacp.h, owned by rtlplayground.c */
/* 20-byte Actor/Partner information body (802.3ad 43.4.2.2), network byte order */
struct lacp_info {
@@ -78,35 +77,29 @@ struct lacpdu_in {
struct lacp_info partner;
};
__xdata uint8_t lacp_rxbody; /* offset of the LACPDU body: the RTL tag, plus a VLAN tag when forwarded */
__xdata uint8_t lacp_rxbody;
#define LACP_O ((__xdata struct lacpdu *)&uip_buf[RTL_FRAME_DESC_SIZE])
#define LACP_H ((__xdata struct lacpdu_in_hdr *)&uip_buf[0])
#define LACP_I ((__xdata struct lacpdu_in *)&uip_buf[lacp_rxbody])
/* --- Per-port participant state (802.3ad 43.4.7). Indexed by physical port. --- */
__xdata uint8_t lacp_actor_state[10]; /* our Actor_State flags */
__xdata uint8_t lacp_rx_state[10]; /* LACP_RX_* receive machine */
__xdata uint8_t lacp_partner_state[10]; /* last Partner_State we saw */
__xdata uint8_t lacp_partner_sys[10][6]; /* partner System ID */
__xdata uint16_t lacp_partner_key[10]; /* partner Key */
__xdata uint16_t lacp_partner_port[10]; /* partner Port number */
/* The Partner block echoes the peer's actor identity verbatim, see
* doc/link_aggregation.md. */
__xdata uint16_t lacp_partner_sys_prio[10]; /* partner System priority */
__xdata uint16_t lacp_partner_port_prio[10]; /* partner Port priority */
__xdata uint16_t lacp_periodic[10]; /* down-counter to next TX */
__xdata uint16_t lacp_timeout[10]; /* down-counter to partner expiry*/
__xdata uint8_t lacp_ntt[10]; /* Need-To-Transmit flag */
__xdata uint8_t lacp_actor_state[10];
__xdata uint8_t lacp_rx_state[10];
__xdata uint8_t lacp_partner_state[10];
__xdata uint8_t lacp_partner_sys[10][6];
__xdata uint16_t lacp_partner_key[10];
__xdata uint16_t lacp_partner_port[10];
__xdata uint16_t lacp_partner_sys_prio[10];
__xdata uint16_t lacp_partner_port_prio[10];
__xdata uint16_t lacp_periodic[10];
__xdata uint16_t lacp_timeout[10];
__xdata uint8_t lacp_ntt[10];
/* The timers step every four system ticks (50 Hz) whatever the pace of the
* main loop; a long pass is caught up on, a few steps at a time. */
#define LACP_TICK_STEP (SYS_TICK_HZ / 50)
#define LACP_CATCH_UP 8
__xdata uint8_t lacp_tick_last;
extern volatile __xdata uint32_t ticks;
/* Per-LAG state: the hardware trunk groups run LACP independently. */
__xdata uint16_t lacp_lag_ports[LACP_NUM_LAGS];
__xdata uint8_t lacp_port_lag[10];
__xdata uint8_t lacp_agg_sys[LACP_NUM_LAGS][6];
@@ -117,12 +110,10 @@ __xdata uint16_t lacp_scratch_mask;
__xdata uint8_t lacp_scratch_flag;
__xdata uint8_t lacp_scratch_lag;
/* Per-port RX LACPDU count - primarily a hardware bring-up diagnostic to
* verify slow-protocol frames actually reach the CPU port (see "lacp show") */
__xdata uint16_t lacp_rx_count[10];
void lacp_mux_update(void) __banked; /* defined below, used from lacp_in */
void lacp_mux_update(void) __banked;
static uint8_t lacp_sys_eq(__xdata uint8_t *a, __xdata uint8_t *b)
{
@@ -134,15 +125,10 @@ static uint8_t lacp_sys_eq(__xdata uint8_t *a, __xdata uint8_t *b)
}
/*
* Selection logic (simplified 43.4.14): a port may join the aggregator when
* we have fresh partner info, the partner considers the link aggregatable,
* and the partner is the same system the aggregator was elected for.
*/
static uint8_t lacp_port_selected(uint8_t port)
{
uint8_t lag = lacp_port_lag[port];
if (lag == LACP_LAG_NONE) /* port not in any LACP LAG */
if (lag == LACP_LAG_NONE)
return 0;
if (lacp_rx_state[port] != LACP_RX_CURRENT)
return 0;
@@ -154,11 +140,6 @@ static uint8_t lacp_port_selected(uint8_t port)
}
/*
* Mux machine (simplified coupled control, 43.4.15): SYNC follows selection;
* COLLECTING+DISTRIBUTING additionally require the partner to be in sync.
* Returns 1 if the actor state changed (caller sets NTT).
*/
static uint8_t lacp_mux_machine(uint8_t port)
{
uint8_t old = lacp_actor_state[port];
@@ -183,7 +164,6 @@ static uint8_t lacp_mux_machine(uint8_t port)
#define port_bit(p) (((uint8_t)1) << (p))
/* Build and emit one LACPDU out physical port `port` (802.3ad 43.4.2). */
void lacp_send(uint8_t port) __banked
{
memset((__xdata uint8_t *)LACP_O, 0, sizeof(struct lacpdu));
@@ -194,42 +174,22 @@ void lacp_send(uint8_t port) __banked
LACP_O->rtl_tag.tag = HTONS(RTL_FRAME_TAG_ID);
LACP_O->rtl_tag.version = RTL_FRAME_TAG_VERSION;
LACP_O->rtl_tag.reason = 0x00;
/* Must go through HTONS like every other tag field: writing 0x0020 raw put
* the bits in the wrong byte (on the wire 0x2000 = EFID, not LEARN_DIS), so
* the ASIC could not parse the tag and forwarded the frame with the 0x8899
* header still attached - the partner then saw ethertype 0x8899 instead of
* 0x8809 and ignored the LACPDU. KEEP additionally stops the switch from
* rewriting the 802.1Q tag format of a CPU-injected frame (as mainline). */
LACP_O->rtl_tag.flags = HTONS(RTL_TAG_LEARN_DIS | RTL_TAG_KEEP);
/* ALLOW cleared => this word is the *forwarding* port mask, i.e. directed
* egress to exactly this port (what mainline does). Setting ALLOW instead
* makes it an allowance/permission mask on top of a normal lookup, which
* for a one-hot mask resolves to an empty egress set and the frame is
* silently dropped - verified on hardware. */
LACP_O->rtl_tag.pmask = HTONS(port_bit(port)); /* egress this port only */
LACP_O->rtl_tag.pmask = HTONS(port_bit(port));
LACP_O->ethertype = HTONS(SLOW_PROTO_ETHERTYPE);
LACP_O->subtype = SLOW_PROTO_SUBTYPE_LACP;
LACP_O->version = LACP_VERSION;
/* Actor TLV */
LACP_O->tlv_actor = LACP_TLV_ACTOR;
LACP_O->actor_len = LACP_TLV_LEN_INFO;
LACP_O->actor.sys_prio = HTONS(LACP_SYS_PRIO);
memcpy(LACP_O->actor.sys, uip_ethaddr.addr, 6);
/* Per-LAG Actor Key so a partner never merges ports of our different LAGs
* into one aggregate (only ever called for ports in a LACP LAG). */
LACP_O->actor.key = HTONS((uint16_t)(lacp_port_lag[port] + 1));
LACP_O->actor.port_prio = HTONS(LACP_DEF_PORT_PRIO);
LACP_O->actor.port = HTONS((uint16_t)port + 1); /* 1-based port id */
LACP_O->actor.port = HTONS((uint16_t)port + 1);
LACP_O->actor.state = lacp_actor_state[port];
/* Partner TLV: echo the last partner info we recorded, VERBATIM. A Linux
* 802.3ad partner (__record_pdu) only accepts the SYNC bit we set above if
* this block mirrors its own actor identity exactly - system+priority, key,
* port+priority. Priorities must be echoed, not hardcoded: Linux defaults to
* system priority 0xffff, so a hardcoded 0 here cleared its partner-SYNC
* (0x3f->0x37) and the aggregate never collected/distributed. */
LACP_O->tlv_partner = LACP_TLV_PARTNER;
LACP_O->partner_len = LACP_TLV_LEN_INFO;
LACP_O->partner.sys_prio = HTONS(lacp_partner_sys_prio[port]);
@@ -239,7 +199,6 @@ void lacp_send(uint8_t port) __banked
LACP_O->partner.port = HTONS(lacp_partner_port[port]);
LACP_O->partner.state = lacp_partner_state[port];
/* Collector TLV + Terminator */
LACP_O->tlv_collector = LACP_TLV_COLLECTOR;
LACP_O->collector_len = LACP_TLV_LEN_COLLECTOR;
LACP_O->collector_max_delay = 0;
@@ -247,14 +206,6 @@ void lacp_send(uint8_t port) __banked
LACP_O->terminator_len = 0x00;
lacp_ntt[port] = 0;
/* Slow-protocol frames are link-local and must egress untagged. With a
* management VLAN set, tcpip_output() splices an 802.1Q tag after the SA,
* shifting our in-frame rtl_tag out of the position the ASIC expects - so it
* fails to parse/strip the CPU tag and the 0x8899 header leaks onto the wire
* (the partner then ignores the LACPDU). Suppress the VLAN insert for this
* frame only; management traffic keeps its tag. Verified on HW: with the
* insert suppressed the LACPDU egresses as clean 0x8809 and the bond
* partner converges (partner MAC becomes the switch, both links aggregate). */
uint16_t saved_mgmt_vlan = management_vlan;
management_vlan = 0;
uip_len = sizeof(struct lacpdu);
@@ -263,11 +214,6 @@ void lacp_send(uint8_t port) __banked
}
/*
* Process an incoming LACPDU. Called from the rtlplayground.c dispatch when a
* frame with DA 01:80:C2:00:00:02 and ethertype 0x8809/subtype 0x01 arrives.
* The ingress port is the low nibble of rtl_tag.pmask on RX.
*/
void lacp_in(void) __banked
{
__xdata uint8_t port, lag;
@@ -283,14 +229,10 @@ void lacp_in(void) __banked
|| LACP_I->subtype != SLOW_PROTO_SUBTYPE_LACP)
return;
/* Per rtl837x_common.h, pmask carries a 4-bit port number on RX
* (hardware-verified on RTL8373: per-port rx counters track the actual
* ingress port through full LACP convergence). */
port = ((uint8_t)HTONS(LACP_H->rtl_tag.pmask)) & 0x0f;
if (port < machine.min_port || port > machine.max_port)
return;
/* Only ports assigned to a LACP-mode LAG participate in the protocol. */
lag = lacp_port_lag[port];
if (lag == LACP_LAG_NONE)
return;
@@ -305,7 +247,6 @@ void lacp_in(void) __banked
lacp_rx_count[port]++;
/* Record partner = the remote's Actor block (802.3ad 43.4.9 recordPDU) */
memcpy(lacp_partner_sys[port], LACP_I->actor.sys, 6);
lacp_scratch_mask = HTONS(LACP_I->actor.key);
lacp_partner_key[port] = lacp_scratch_mask;
@@ -317,22 +258,17 @@ void lacp_in(void) __banked
lacp_partner_port_prio[port] = lacp_scratch_mask;
lacp_partner_state[port] = LACP_I->actor.state;
/* Receive machine -> CURRENT, (re)arm partner timeout (43.4.12) */
lacp_rx_state[port] = LACP_RX_CURRENT;
lacp_timeout[port] = LACP_SHORT_TIMEOUT;
/* Elect this LAG's aggregator partner system on first contact (43.4.14) */
if (!lacp_agg_valid[lag]) {
memcpy(lacp_agg_sys[lag], LACP_I->actor.sys, 6);
lacp_agg_valid[lag] = 1;
}
/* Selection + Mux for this port; state change => Need-To-Transmit */
if (lacp_mux_machine(port))
lacp_ntt[port] = 1;
/* update_NTT (43.4.12): if the partner's view of us is stale (their
* Partner block does not match our actor state/port), tell them again. */
if (LACP_I->partner.state != lacp_actor_state[port]
|| HTONS(LACP_I->partner.port) != (uint16_t)port + 1)
lacp_ntt[port] = 1;
@@ -341,13 +277,10 @@ void lacp_in(void) __banked
}
/* Recompute each LACP LAG's hardware trunk membership: a port joins its LAG's
* trunk once it is fully in sync (SYNC+COLLECTING+DISTRIBUTING and the partner
* in SYNC). Each LACP-mode LAG is programmed independently (simplified 43.4.15). */
void lacp_mux_update(void) __banked
{
for (uint8_t lag = 0; lag < LACP_NUM_LAGS; lag++) {
if (!lacp_lag_ports[lag]) /* LAG not under LACP management */
if (!lacp_lag_ports[lag])
continue;
lacp_scratch_mask = 0;
for (uint8_t i = machine.min_port; i <= machine.max_port; i++) {
@@ -357,8 +290,6 @@ void lacp_mux_update(void) __banked
&& (lacp_partner_state[i] & LACP_STATE_SYNC))
lacp_scratch_mask |= port_bit(i);
}
/* Program the trunk only when this LAG's membership actually changed
* - this runs on every timer tick. */
if (lacp_scratch_mask != lacp_members_last[lag]) {
lacp_members_last[lag] = lacp_scratch_mask;
port_lag_members_set(lag, lacp_scratch_mask);
@@ -370,10 +301,9 @@ void lacp_mux_update(void) __banked
static void lacp_step(void)
{
for (uint8_t i = machine.min_port; i <= machine.max_port; i++) {
if (lacp_port_lag[i] == LACP_LAG_NONE) /* not in a LACP LAG */
if (lacp_port_lag[i] == LACP_LAG_NONE)
continue;
/* Periodic transmit machine (802.3ad 43.4.13) */
if (lacp_periodic[i]) {
lacp_periodic[i]--;
} else {
@@ -382,9 +312,6 @@ static void lacp_step(void)
lacp_ntt[i] = 1;
}
/* Partner timeout -> DEFAULTED (802.3ad 43.4.12): the mux machine
* then drops SYNC/COLLECTING/DISTRIBUTING and the port leaves the
* trunk. TODO(43.4.12): churn detection is diagnostics-only, skipped. */
if (lacp_timeout[i]) {
if (!--lacp_timeout[i]) {
lacp_rx_state[i] = LACP_RX_DEFAULTED;
@@ -398,11 +325,6 @@ static void lacp_step(void)
lacp_send(i);
}
/* Release each LAG's aggregator identity once none of its ports still
* hears the elected partner, so a re-cabled setup can elect a new one
* (43.4.14). A port that is CURRENT with some other system must not keep
* the old identity alive: it would never be selected, and neither would
* the rest of the LAG once it moves to that system too. */
for (uint8_t lag = 0; lag < LACP_NUM_LAGS; lag++) {
if (!lacp_agg_valid[lag])
continue;
@@ -432,11 +354,10 @@ void lacp_timers(void) __banked
}
/* Bring one port up as an active-fast LACP participant (43.4.12 init). */
static void lacp_port_init(uint8_t port)
{
lacp_actor_state[port] = LACP_STATE_ACTIVITY | LACP_STATE_AGGREGATION
| LACP_STATE_TIMEOUT; /* active + fast */
| LACP_STATE_TIMEOUT;
lacp_rx_state[port] = LACP_RX_INITIALIZE;
lacp_partner_state[port] = LACP_STATE_DEFAULTED;
memset(lacp_partner_sys[port], 0, 6);
@@ -444,13 +365,10 @@ static void lacp_port_init(uint8_t port)
lacp_partner_port[port] = 0;
lacp_periodic[port] = LACP_FAST_PERIODIC;
lacp_timeout[port] = 0;
lacp_ntt[port] = 1; /* announce ourselves immediately */
lacp_ntt[port] = 1;
lacp_rx_count[port] = 0;
}
/* Return a port to non-LACP state. (Port isolation is no longer touched - a
* trapped LACPDU never enters the forwarding path, so a sibling cannot see
* it, which is what the isolation workaround was for.) */
static void lacp_port_release(uint8_t port)
{
lacp_actor_state[port] = 0;
@@ -461,7 +379,6 @@ static void lacp_port_release(uint8_t port)
memset(lacp_partner_sys[port], 0, 6);
}
/* True while any LAG is under LACP management (drives the global RMA trap). */
static uint8_t lacp_any_lag(void)
{
for (uint8_t l = 0; l < LACP_NUM_LAGS; l++)
@@ -470,11 +387,6 @@ static uint8_t lacp_any_lag(void)
return 0;
}
/*
* Turn the LACP engine on: trap the Slow-Protocols group to the CPU port. The
* trap acts on the address before the VLAN lookup and before the ingress
* filters, so delivery needs no L2 entry and no PVID of its own.
*/
static void lacp_engine_on(void)
{
lacp_tick_last = (uint8_t)ticks;
@@ -482,16 +394,12 @@ static void lacp_engine_on(void)
trap_rma_set(RTL837X_RMA2_CONF, RTL837X_RMA_ACT_TRAP);
}
/* Turn the LACP engine off: stop delivering slow-protocols to the CPU. */
static void lacp_engine_off(void)
{
trap_rma_set(RTL837X_RMA2_CONF, RTL837X_RMA_ACT_DROP);
lacpEnabled = 0;
}
/* One-time boot init: no LAG runs LACP yet, so every port maps to LACP_LAG_NONE.
* MUST run before any "lag ... lacp" config replay - xdata is not zeroed to the
* 0xff sentinel, and a stray 0 would make a port look like it belongs to LAG 0. */
void lacp_init(void) __banked
{
for (uint8_t i = 0; i < 10; i++) {
@@ -507,13 +415,6 @@ void lacp_init(void) __banked
lacpEnabled = 0;
}
/*
* Assign a candidate-port mask to LACP-mode LAG `lag` (`lag <n> lacp <ports>`).
* ports == 0 removes the LAG from LACP management. Ports leaving the LAG are
* returned to normal switching; ports joining start the protocol immediately.
* The engine's RMA trap is enabled on the first LACP LAG and torn down with the
* last. Never wipes a static "lag" the user configured on a different group.
*/
void lacp_lag_set(uint8_t lag, __xdata uint16_t ports) __banked
{
if (lag >= LACP_NUM_LAGS)
@@ -522,16 +423,12 @@ void lacp_lag_set(uint8_t lag, __xdata uint16_t ports) __banked
return;
lacp_scratch_flag = lacp_any_lag();
/* Drop ports that were in this LAG but are not in the new mask. */
for (uint8_t i = machine.min_port; i <= machine.max_port; i++) {
if (lacp_port_lag[i] == lag && !(ports & port_bit(i))) {
lacp_port_lag[i] = LACP_LAG_NONE;
lacp_port_release(i);
}
}
/* Release this LAG's trunk: LACP's own, or the static one a group put
* under LACP still has. Taking the group out (ports == 0) leaves a static
* trunk alone. */
if (lacp_members_last[lag] || ports) {
lacp_members_last[lag] = 0;
port_lag_members_set(lag, 0);
@@ -539,11 +436,9 @@ void lacp_lag_set(uint8_t lag, __xdata uint16_t ports) __banked
lacp_agg_valid[lag] = 0;
lacp_lag_ports[lag] = ports;
if (ports && !lacp_scratch_flag) /* first LACP LAG: bring the engine up first */
if (ports && !lacp_scratch_flag)
lacp_engine_on();
/* Adopt the new member ports (with the engine already up). A port that
* belonged to another LACP LAG leaves that LAG's candidate set first. */
for (uint8_t i = machine.min_port; i <= machine.max_port; i++) {
if (!(ports & port_bit(i)))
continue;
@@ -563,15 +458,13 @@ void lacp_lag_set(uint8_t lag, __xdata uint16_t ports) __banked
lacp_port_init(i);
}
if (!lacp_any_lag() && lacp_scratch_flag) /* removed the last LACP LAG */
if (!lacp_any_lag() && lacp_scratch_flag)
lacp_engine_off();
lacp_mux_update(); /* a LAG that lost a port to this one drops it from its trunk */
lacp_mux_update();
}
/* "lacp off": remove every LACP LAG (releases their trunks, stops the engine).
* There is no "lacp on": the engine comes up with the first "lag <n> lacp". */
void lacp_off(void) __banked
{
for (uint8_t l = 0; l < LACP_NUM_LAGS; l++)
@@ -580,9 +473,6 @@ void lacp_off(void) __banked
}
/* "lacp show": per-port protocol state - the primary bring-up diagnostic.
* A stuck rx=0 counter means slow-protocol frames never reach the CPU port
* (see the RMA note in lacp_engine_on()). */
void lacp_show(void) __banked
{
print_string("LACP "); print_string(lacpEnabled ? "on" : "off"); write_char('\n');