diff --git a/cmd_parser.c b/cmd_parser.c index 5c7e0dfd..9e226dbf 100644 --- a/cmd_parser.c +++ b/cmd_parser.c @@ -540,8 +540,6 @@ void parse_lag(void) return; } - /* "lag lacp [port]..." - run LACP on this LAG with the given candidate - * ports; "lag 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); diff --git a/doc/CpuPort.md b/doc/CpuPort.md index f5e9859b..c10d0a15 100644 --- a/doc/CpuPort.md +++ b/doc/CpuPort.md @@ -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 diff --git a/doc/link_aggregation.md b/doc/link_aggregation.md index 462b66de..b725e419 100644 --- a/doc/link_aggregation.md +++ b/doc/link_aggregation.md @@ -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. diff --git a/httpd/page_impl.c b/httpd/page_impl.c index 94ccb483..e1f43f6e 100644 --- a/httpd/page_impl.c +++ b/httpd/page_impl.c @@ -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]); diff --git a/rtl837x_lacp.c b/rtl837x_lacp.c index 276e80d5..5a14e3b4 100644 --- a/rtl837x_lacp.c +++ b/rtl837x_lacp.c @@ -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 lacp `). - * 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 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'); diff --git a/rtl837x_lacp.h b/rtl837x_lacp.h index 514f9e35..44e3ad4b 100644 --- a/rtl837x_lacp.h +++ b/rtl837x_lacp.h @@ -8,92 +8,61 @@ #include -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 lacp `). - * 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 lacp `. - */ #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 diff --git a/rtl837x_regs.h b/rtl837x_regs.h index 13150ec4..c2117fb1 100644 --- a/rtl837x_regs.h +++ b/rtl837x_regs.h @@ -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 diff --git a/rtlplayground.c b/rtlplayground.c index 38e322e7..4a8b4ec7 100644 --- a/rtlplayground.c +++ b/rtlplayground.c @@ -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();