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".
This commit is contained in:
DrDoof authored and HL Yi committed 2026-10-14 20:18:07 -05:00
1 parent ed069b67fd
commit 56686185ce
31 files changed
+1885 -23

No files matched your search

+12
View File
@@ -0,0 +1,12 @@
/* Host-build shim: rtl837x_lacp.c reads min_port/max_port and, for the
* console dump, the front-panel numbering. The real map is not the identity
* on any machine, so the harness uses a shuffled one to keep a logical index
* from passing as a physical port by accident. */
#ifndef _SHIM_MACHINE_H_
#define _SHIM_MACHINE_H_
#include <stdint.h>
struct machine { uint8_t min_port; uint8_t max_port; uint8_t log_to_phys_port[9]; };
extern const struct machine machine;
struct machine_runtime { uint8_t isRTL8373; };
extern struct machine_runtime machine_detected;
#endif
+63
View File
@@ -0,0 +1,63 @@
/*
* Host-build shim for rtl837x_common.h - just enough for rtl837x_lacp.c.
* Layouts of rtl_tag/vlan_tag and the frame-desc size mirror the real header;
* the harness static-asserts the resulting on-wire struct sizes.
*/
#ifndef _SHIM_RTL837X_COMMON_H_
#define _SHIM_RTL837X_COMMON_H_
#include <stdint.h>
#include <string.h> /* memcpy/memset - firmware provides its own */
struct rtl_tag {
uint16_t tag;
uint8_t version;
uint8_t reason;
uint16_t flags;
uint16_t pmask; /* TX: port bitmask; RX: 4-bit port number */
};
struct vlan_tag {
uint16_t svlan;
uint16_t vlan;
};
#define RTL_TAG_SIZE (sizeof (struct rtl_tag))
#define VLAN_TAG_SIZE (sizeof (struct vlan_tag))
#define RTL_FRAME_TAG_ID 0x8899
#define RTL_FRAME_TAG_VERSION 0x04
#define RTL_FRAME_DESC_SIZE 12
#define ETHER_HEADER_SIZE 14
#define HTONS(n) ((uint16_t)((((uint16_t)(n)) << 8) | (((uint16_t)(n)) >> 8)))
/* Console output - provided by the harness */
void print_string(char *s);
void print_byte(uint8_t b);
void print_short(uint16_t v);
void write_char(char c);
#endif
/* Host-build shim: port masks used by lacp_isolation_update() */
#ifndef PMASK_CPU
#define CPU_PORT 9
#define PMASK_9 0x1ff
#define PMASK_6 0x1f8
#define PMASK_CPU 0x200
#endif
/* Host-build shim: directed-egress/strip-tag bit (bit15 of TX pmask word) */
#ifndef RTL_TAG_ALLOW
#define RTL_TAG_ALLOW 0x8000
#endif
/* Host-build shim: tag flags word bits (tag_rtl8_4) */
#ifndef RTL_TAG_LEARN_DIS
#define RTL_TAG_LEARN_DIS 0x0020
#define RTL_TAG_KEEP 0x0080
#define SYS_TICK_HZ 200
extern volatile uint32_t ticks;
#define TICKS_DUE(last, period) ((uint8_t)((uint8_t)ticks - (last)) >= (period))
#endif
+7
View File
@@ -0,0 +1,7 @@
/* Host-build shim: the harness mocks trunk programming and records calls */
#ifndef _SHIM_RTL837X_PORT_H_
#define _SHIM_RTL837X_PORT_H_
#include <stdint.h>
void port_lag_members_set(uint8_t lag, uint16_t members);
void port_isolate(uint8_t port, uint16_t pmask);
#endif
+19
View File
@@ -0,0 +1,19 @@
/*
* Host-build shim for rtl837x_regs.h - the register numbers rtl837x_lacp.c
* touches, with the values from the real header so a wrong constant shows up
* as a failing expectation rather than as a silent no-op.
*/
#ifndef _SHIM_RTL837X_REGS_H_
#define _SHIM_RTL837X_REGS_H_
#include <stdint.h>
#define RTL837X_RMA2_CONF 0x4ed4
#define RTL837X_RMA_ACT_TRAP 0x10
#define RTL837X_RMA_ACT_DROP 0x20
/* Registers touched by lacp_send's CPU-port targeting */
#define RTL837X_PORT_ISOLATION_BASE 0x50c0
#define RTL837X_REG_NIC_TXCMD 0x7850
#endif
+9
View File
@@ -0,0 +1,9 @@
/* Host-build shim for uip.h - frame buffer + TX hook used by rtl837x_lacp.c */
#ifndef _SHIM_UIP_H_
#define _SHIM_UIP_H_
#include <stdint.h>
#define UIP_CONF_BUFFER_SIZE 600
struct uip_eth_addr { uint8_t addr[6]; };
extern uint16_t uip_len;
void tcpip_output(void);
#endif