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

+1 -3
View File
@@ -540,8 +540,6 @@ void parse_lag(void)
return;
}
/* "lag <n> lacp [port]..." - run LACP on this LAG with the given candidate
* ports; "lag <n> lacp off" (or no ports) removes the LAG from LACP. */
uint8_t w = 2;
__xdata uint8_t lacp_mode = 0;
if (cmd_words_len > 2 && cmd_compare(2, "lacp")) {
@@ -560,7 +558,7 @@ void parse_lag(void)
members |= ((uint16_t)1) << atoi_results_u8;
}
if (lacp_mode) {
lacp_lag_set(group, members); /* 0 ports also valid: removes the LAG */
lacp_lag_set(group, members);
} else {
lacp_lag_set(group, 0);
port_lag_members_set(group, members);
-5
View File
@@ -79,11 +79,6 @@ bit7 KEEP | 6 VSEL | 5 LEARN_DIS | 4:0 VIDX
All fields are in network byte order.
This word has to be written through `HTONS` like every other field of the tag.
Writing a constant raw puts the bits in the wrong byte, so `0x0020` reaches the
wire as `0x2000`, which is EFID rather than LEARN_DIS. The ASIC then fails to
parse the tag and forwards the frame with the `0x8899` header still on it.
* `EFID_EN`, `EFID`: look the destination up under this filtering ID
instead of the port's own
* `PRI_EN`, `PRI`: force the given priority on the frame
-49
View File
@@ -227,52 +227,3 @@ iperf Done.
```
As you can see, the total throughput was 4.71 GBit/sec which is close to the
maximum possible with a single 5GBit link.
## LACP: how the PDUs reach the CPU
Slow-protocol frames go to the reserved group `01:80:C2:00:00:02`, and the
ASIC's default action for it is *drop*. The engine sets the action to *trap*
while it runs. A trap hands the frame to the port named in `EXT_CPU_CTRL`,
which names no port out of reset - that is why the trap first looked like no
help at all: the receive counters stayed frozen while the partner kept sending,
and the frames were counted as ingress discards. `trap_init()` points the
register at the CPU port once at startup.
The *forward* action would reach the CPU as well, since the CPU port is in the
forwarding domain, but it leaves the frame in the L2 lookup and so floods it
across the ingress VLAN. That leaks LACPDUs to unrelated ports, and the damage
is real: a bond's own ports see each other's PDUs and Linux reports an illegal
loopback, a second bond on the same switch is poisoned, and the flood escapes
through uplinks. Containing it would take a static L2 multicast entry with a
CPU-only mask for every PVID in use, since untagged LACPDUs classify into the
ingress port's PVID and the forward action is not limited to the LACP ports.
The trap keys on the address instead, before the VLAN lookup and before the
ingress filters, which settles three things at once:
* Nothing is written per VLAN or per PVID, so delivery no longer depends on
which PVIDs the ports happen to carry.
* A port that admits tagged frames only still receives: an untagged forwarded
LACPDU is dropped by the acceptable-frame-type filter, a trapped one is not.
* A port STP holds in blocking still receives, so a group converges without
waiting out the forward delay.
A trapped frame arrives without the VLAN tag the hardware inserts into a
forwarded one, so the PDU body sits four bytes earlier; `TRAP_RX_BODY()` gives
the offset for both forms.
`lacp off` puts the action back to *drop*, which acts before the L2 lookup.
## LACP: what the implementation leaves out
The four state machines of Clause 43 are present in simplified form. Mux control
is coupled rather than independent, one partner system is elected per LAG, and
there are no churn detection machines.
One interoperability rule is worth stating because getting it wrong is silent.
The Partner block of an outgoing PDU has to echo the peer's own actor identity
verbatim, meaning system and priority, key, port and port priority, and the
aggregation flag. A Linux partner's `__record_pdu()` accepts our SYNC bit only
when that block mirrors what it sent. Hardcoding the priorities to zero
mismatches Linux's default of 0xffff, which clears partner SYNC, moves the state
byte from 0x3f to 0x37, and the bond never reaches collecting and distributing.
-7
View File
@@ -737,9 +737,6 @@ void send_stp(void)
slen += strtox(outbuf + slen, "]}");
}
/* LACP protocol status for the LAG page ("/lacp.json"). State variables are
* exported read-only via rtl837x_lacp.h; hardware trunk membership itself is
* already visible through send_lag() (it reads the trunk registers). */
void send_lacp(void)
{
dbg_string("send_lacp called\n");
@@ -747,7 +744,6 @@ void send_lacp(void)
slen += strtox(outbuf + slen, "{\"on\":");
bool_to_html(lacpEnabled);
/* Per-LAG section: candidate ports, elected aggregator, trunk members. */
slen += strtox(outbuf + slen, ",\"lags\":[");
for (uint8_t l = 0; l < LACP_NUM_LAGS; l++) {
slen += strtox(outbuf + slen, "{\"cfg\":\"");
@@ -768,7 +764,6 @@ void send_lacp(void)
for (uint8_t i = machine.min_port; i <= machine.max_port; i++) {
slen += strtox(outbuf + slen, "{\"p\":");
itoa_html(machine.log_to_phys_port[i]);
/* Which LACP LAG this port belongs to; 255 = none */
slen += strtox(outbuf + slen, ",\"lag\":");
itoa_html(lacp_port_lag[i]);
slen += strtox(outbuf + slen, ",\"a\":\"");
@@ -777,8 +772,6 @@ void send_lacp(void)
byte_to_html(lacp_partner_state[i]);
slen += strtox(outbuf + slen, "\",\"rs\":");
itoa_html(lacp_rx_state[i]);
/* rx as 4-digit hex: itoa16_html() only renders values up to 9999
* correctly (see its VLAN-ID comment), the counter goes to 65535 */
slen += strtox(outbuf + slen, ",\"rx\":\"");
byte_to_html(lacp_rx_count[i] >> 8);
byte_to_html(lacp_rx_count[i]);
+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');
+22 -53
View File
@@ -8,92 +8,61 @@
#include <stdint.h>
void lacp_init(void) __banked; /* boot init: clear per-LAG state */
void lacp_init(void) __banked;
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_show(void) __banked;
void lacp_lag_set(uint8_t lag, __xdata 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 */
#define LACP_LAG_NONE 0xff
/* 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 */
extern __xdata uint8_t lacp_port_lag[10];
extern __xdata uint16_t lacp_lag_ports[LACP_NUM_LAGS];
extern __xdata uint8_t lacp_agg_sys[LACP_NUM_LAGS][6];
extern __xdata uint8_t lacp_agg_valid[LACP_NUM_LAGS];
extern __xdata uint16_t lacp_members_last[LACP_NUM_LAGS];
/* 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 */
#define LACP_DST5 0x02
/* 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 */
#define LACP_TLV_LEN_INFO 0x14
#define LACP_TLV_LEN_COLLECTOR 0x10
/* 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_ACTIVITY 0x01 /* active LACP, sends periodically */
#define LACP_STATE_TIMEOUT 0x02 /* short timeout requested */
#define LACP_STATE_AGGREGATION 0x04 /* link can be aggregated */
#define LACP_STATE_SYNC 0x08 /* in sync with the 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 */
#define LACP_STATE_DEFAULTED 0x40 /* partner information is the default */
#define LACP_STATE_EXPIRED 0x80
/* 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_FAST_PERIODIC 50
#define LACP_SLOW_PERIODIC 1500
#define LACP_SHORT_TIMEOUT 200
/* 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_PORT_DISABLED 1
#define LACP_RX_EXPIRED 2
#define LACP_RX_DEFAULTED 3
#define LACP_RX_CURRENT 4
-4
View File
@@ -284,10 +284,6 @@
* Loop detection / STP
*/
#define RTL8373_RLDP_TIMER 0x1074
/* Per reserved-multicast-address action. The registers are packed and
* consecutive by ENTRY rather than by the byte of the address: entry 0 is
* 01:80:C2:00:00:00 at 0x4ecc and entry 2 is ...:02 (Slow Protocols / LACP)
* at 0x4ed4, but entry 5 is ...:08, so the step of 4 counts entries. */
#define RTL837X_RMA0_CONF 0x4ecc
#define RTL837X_RMA0E_CONF 0x4ee8
#define RTL837X_RMA2_CONF 0x4ed4
+4 -5
View File
@@ -1062,14 +1062,14 @@ void handle_rx(void)
print_string(" MGMT-VLAN: "); print_short(management_vlan); write_char('\n');
#endif
if ((stp_enabled || lacpEnabled) && uip_buf[0] == 0x01 && uip_buf[1] == 0x80
&& uip_buf[2] == 0xc2 && uip_buf[3] == 0x00 && uip_buf[4] == 0x00) { // 01:80:C2:00:00:0x
if (stp_enabled && uip_buf[5] == 0x00) { // STP/RSTP BPDU
&& uip_buf[2] == 0xc2 && uip_buf[3] == 0x00 && uip_buf[4] == 0x00) {
if (stp_enabled && uip_buf[5] == 0x00) {
stp_in();
if (uip_len) {
print_string("STP TX\n");
tcpip_output();
}
} else if (lacpEnabled && uip_buf[5] == 0x02) { // LACPDU (slow protocols)
} else if (lacpEnabled && uip_buf[5] == 0x02) {
lacp_in();
}
} else if (igmpEnabled && uip_buf[0] == 0x01 && uip_buf[1] == 0x00 && uip_buf[2] == 0x5e // IPv4-MC packet?
@@ -1296,7 +1296,6 @@ static void handle_tick(void)
stp_timers();
}
}
// If LACP enabled, drive its machines (own tick divider lives in the banked module)
if (lacpEnabled)
lacp_timers();
health_phase(HEALTH_PH_STP);
@@ -1802,7 +1801,7 @@ void main(void)
stp_enabled = 0;
stp_defaults(); /* 802.1D/w default config before any "stp ..." replay */
lacpEnabled = 0;
lacp_init(); /* clear per-LAG state (port->LAG map = NONE) before any config replay */
lacp_init();
nic_setup();
REG_SET(RTL837X_NIC_INT_MSK, NIC_INT_RXIE);
trap_init();