Files
RTLPlayground/rtl837x_lacp.c
T
DrDoof c3b4cc893b 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-12 15:09:57 +02:00

608 lines
21 KiB
C

/*
* LACP (IEEE 802.3ad Clause 43) driver for the RTL837x platform.
* This code is in the Public Domain.
*/
// #define DEBUG
#pragma codeseg BANK3
#pragma constseg BANK3
#include <stdint.h>
#include "rtl837x_common.h"
#include "rtl837x_regs.h"
#include "rtl837x_lacp.h"
#include "rtl837x_trap.h"
#include "rtl837x_port.h"
#include "uip.h"
#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 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 {
uint16_t sys_prio;
uint8_t sys[6];
uint16_t key;
uint16_t port_prio;
uint16_t port;
uint8_t state;
uint8_t reserved[3];
};
/* Outgoing LACPDU (no VLAN tag on TX, as in stp_pkt) */
struct lacpdu {
uint8_t dst[6];
uint8_t src[6];
struct rtl_tag rtl_tag;
uint16_t ethertype; /* 0x8809 */
uint8_t subtype; /* 0x01 */
uint8_t version; /* 0x01 */
uint8_t tlv_actor; /* 0x01 */
uint8_t actor_len; /* 0x14 */
struct lacp_info actor;
uint8_t tlv_partner; /* 0x02 */
uint8_t partner_len; /* 0x14 */
struct lacp_info partner;
uint8_t tlv_collector; /* 0x03 */
uint8_t collector_len; /* 0x10 */
uint16_t collector_max_delay;
uint8_t collector_reserved[12];
uint8_t tlv_terminator; /* 0x00 */
uint8_t terminator_len; /* 0x00 */
uint8_t terminator_reserved[50];
};
/* Incoming LACPDU, split because a VLAN tag sits between the two only when the
* frame was forwarded: a trapped one carries none (cf. stp_pkt_in) */
struct lacpdu_in_hdr {
uint8_t dst[6];
uint8_t src[6];
struct rtl_tag rtl_tag;
};
struct lacpdu_in {
uint16_t ethertype;
uint8_t subtype;
uint8_t version;
uint8_t tlv_actor;
uint8_t actor_len;
struct lacp_info actor;
uint8_t tlv_partner;
uint8_t partner_len;
struct lacp_info partner;
};
__xdata uint8_t lacp_rxbody; /* offset of the LACPDU body: the RTL tag, plus a VLAN tag when forwarded */
#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 */
/* 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];
__xdata uint8_t lacp_agg_valid[LACP_NUM_LAGS];
__xdata uint16_t lacp_members_last[LACP_NUM_LAGS];
__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 */
static uint8_t lacp_sys_eq(__xdata uint8_t *a, __xdata uint8_t *b)
{
for (uint8_t i = 0; i < 6; i++) {
if (a[i] != b[i])
return 0;
}
return 1;
}
/*
* 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 */
return 0;
if (lacp_rx_state[port] != LACP_RX_CURRENT)
return 0;
if (!(lacp_partner_state[port] & LACP_STATE_AGGREGATION))
return 0;
if (!lacp_agg_valid[lag])
return 0;
return lacp_sys_eq(lacp_partner_sys[port], lacp_agg_sys[lag]);
}
/*
* 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];
uint8_t new = old;
if (lacp_port_selected(port)) {
new |= LACP_STATE_SYNC;
if (lacp_partner_state[port] & LACP_STATE_SYNC)
new |= LACP_STATE_COLLECTING | LACP_STATE_DISTRIBUTING;
else
new &= ~(LACP_STATE_COLLECTING | LACP_STATE_DISTRIBUTING);
} else {
new &= ~(LACP_STATE_SYNC | LACP_STATE_COLLECTING | LACP_STATE_DISTRIBUTING);
}
if (new == old)
return 0;
lacp_actor_state[port] = new;
return 1;
}
#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));
LACP_O->dst[0] = 0x01; LACP_O->dst[1] = 0x80; LACP_O->dst[2] = 0xc2;
LACP_O->dst[3] = 0x00; LACP_O->dst[4] = 0x00; LACP_O->dst[5] = LACP_DST5;
memcpy(LACP_O->src, uip_ethaddr.addr, 6);
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->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.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]);
memcpy(LACP_O->partner.sys, lacp_partner_sys[port], 6);
LACP_O->partner.key = HTONS(lacp_partner_key[port]);
LACP_O->partner.port_prio = HTONS(lacp_partner_port_prio[port]);
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;
LACP_O->tlv_terminator = LACP_TLV_TERMINATOR;
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);
tcpip_output();
management_vlan = saved_mgmt_vlan;
}
/*
* 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
{
lacp_rxbody = TRAP_RX_BODY();
if (uip_len < lacp_rxbody + sizeof(struct lacpdu_in)) {
uip_len = 0;
return;
}
uip_len = 0;
if (LACP_I->ethertype != HTONS(SLOW_PROTO_ETHERTYPE)
|| 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). */
uint8_t 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. */
uint8_t lag = lacp_port_lag[port];
if (lag == LACP_LAG_NONE)
return;
#ifdef DEBUG
print_string("LACP in, port "); print_byte(port);
print_string(" partner_state "); print_byte(LACP_I->actor.state); write_char('\n');
#endif
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_partner_key[port] = HTONS(LACP_I->actor.key);
lacp_partner_port[port] = HTONS(LACP_I->actor.port);
lacp_partner_sys_prio[port] = HTONS(LACP_I->actor.sys_prio);
lacp_partner_port_prio[port] = HTONS(LACP_I->actor.port_prio);
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_I->actor.state & LACP_STATE_TIMEOUT)
? LACP_SHORT_TIMEOUT : LACP_LONG_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;
lacp_mux_update();
}
/* 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 */
continue;
lacp_scratch_mask = 0;
for (uint8_t i = machine.min_port; i <= machine.max_port; i++) {
if (lacp_port_lag[i] != lag)
continue;
if ((lacp_actor_state[i] & LACP_STATE_FULL) == LACP_STATE_FULL
&& (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);
}
}
}
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 */
continue;
/* Periodic transmit machine (802.3ad 43.4.13) */
if (lacp_periodic[i]) {
lacp_periodic[i]--;
} else {
lacp_periodic[i] = (lacp_partner_state[i] & LACP_STATE_TIMEOUT)
? LACP_FAST_PERIODIC : LACP_SLOW_PERIODIC;
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;
lacp_partner_state[i] = LACP_STATE_DEFAULTED;
if (lacp_mux_machine(i))
lacp_ntt[i] = 1;
}
}
if (lacp_ntt[i])
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;
uint8_t any_current = 0;
for (uint8_t i = machine.min_port; i <= machine.max_port; i++) {
if (lacp_port_lag[i] == lag && lacp_rx_state[i] == LACP_RX_CURRENT
&& lacp_sys_eq(lacp_partner_sys[i], lacp_agg_sys[lag])) {
any_current = 1;
break;
}
}
if (!any_current)
lacp_agg_valid[lag] = 0;
}
lacp_mux_update();
}
void lacp_timers(void) __banked
{
uint8_t n = LACP_CATCH_UP;
while (n-- && TICKS_DUE(lacp_tick_last, LACP_TICK_STEP)) {
lacp_tick_last += LACP_TICK_STEP;
lacp_step();
}
}
/* 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_rx_state[port] = LACP_RX_INITIALIZE;
lacp_partner_state[port] = LACP_STATE_DEFAULTED;
memset(lacp_partner_sys[port], 0, 6);
lacp_partner_key[port] = 0;
lacp_partner_port[port] = 0;
lacp_periodic[port] = LACP_FAST_PERIODIC;
lacp_timeout[port] = 0;
lacp_ntt[port] = 1; /* announce ourselves immediately */
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;
lacp_ntt[port] = 0;
lacp_partner_state[port] = 0;
lacp_rx_state[port] = LACP_RX_INITIALIZE;
lacp_rx_count[port] = 0;
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++)
if (lacp_lag_ports[l])
return 1;
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;
lacpEnabled = 1;
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++) {
lacp_port_lag[i] = LACP_LAG_NONE;
lacp_actor_state[i] = 0;
lacp_ntt[i] = 0;
}
for (uint8_t l = 0; l < LACP_NUM_LAGS; l++) {
lacp_lag_ports[l] = 0;
lacp_agg_valid[l] = 0;
lacp_members_last[l] = 0;
}
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, uint16_t ports) __banked
{
if (lag >= LACP_NUM_LAGS)
return;
if (ports && ports == lacp_lag_ports[lag])
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);
}
lacp_agg_valid[lag] = 0;
lacp_lag_ports[lag] = ports;
if (ports && !lacp_scratch_flag) /* first LACP LAG: bring the engine up first */
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;
lacp_scratch_lag = lacp_port_lag[i];
if (lacp_scratch_lag != LACP_LAG_NONE && lacp_scratch_lag != lag) {
lacp_lag_ports[lacp_scratch_lag] &= ~port_bit(i);
if (!lacp_lag_ports[lacp_scratch_lag]) {
lacp_agg_valid[lacp_scratch_lag] = 0;
if (lacp_members_last[lacp_scratch_lag]) {
lacp_members_last[lacp_scratch_lag] = 0;
port_lag_members_set(lacp_scratch_lag, 0);
}
}
}
lacp_port_lag[i] = lag;
lacp_port_init(i);
}
if (!lacp_any_lag() && lacp_scratch_flag) /* removed the last LACP LAG */
lacp_engine_off();
lacp_mux_update(); /* a LAG that lost a port to this one drops it from its trunk */
}
/* "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++)
if (lacp_lag_ports[l])
lacp_lag_set(l, 0);
}
/* "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');
for (uint8_t l = 0; l < LACP_NUM_LAGS; l++) {
if (!lacp_lag_ports[l])
continue;
print_string("lag "); print_byte(l + 1);
print_string(" ports "); print_short(lacp_lag_ports[l]);
print_string(" aggregator ");
if (lacp_agg_valid[l]) {
for (uint8_t j = 0; j < 6; j++)
print_byte(lacp_agg_sys[l][j]);
} else {
print_string("(none)");
}
print_string(" members "); print_short(lacp_members_last[l]); write_char('\n');
}
for (uint8_t i = machine.min_port; i <= machine.max_port; i++) {
print_string("port "); print_byte(machine.log_to_phys_port[i]);
print_string(" actor "); print_byte(lacp_actor_state[i]);
print_string(" partner "); print_byte(lacp_partner_state[i]);
print_string(" rxst "); print_byte(lacp_rx_state[i]);
print_string(" rx "); print_short(lacp_rx_count[i]);
print_string(" psys ");
for (uint8_t j = 0; j < 6; j++)
print_byte(lacp_partner_sys[i][j]);
write_char('\n');
}
}