From 4a78b2dc8c91428ca2bc65cde81a885a844559ed Mon Sep 17 00:00:00 2001 From: d00f Date: Tue, 11 Aug 2026 17:32:25 +0200 Subject: [PATCH] doc: move the L2 multicast and tag word details out of the code Review asked for this directly: the hardware layout above port_l2mc_set() would be better as documentation than as a comment, keeping only the two lines that say what the function does. doc/l2.md gains a section on static multicast entries, why delivery uses the forward action rather than the trap, and the SMI layout of the entry. doc/CpuPort.md gains the layout of the tag's flags and pmask words, with the byte order trap that cost an afternoon: writing the flags constant raw instead of through HTONS puts 0x0020 on the wire as 0x2000, which is EFID rather than LEARN_DIS, and the ASIC then leaves the 0x8899 header on the frame. The comments those paragraphs came from are replaced by a pointer to the file that now holds them. --- doc/CpuPort.md | 30 ++++++++++++++++++++++++++++++ doc/l2.md | 33 +++++++++++++++++++++++++++++++++ rtl837x_common.h | 14 ++------------ rtl837x_port.c | 27 +-------------------------- 4 files changed, 66 insertions(+), 38 deletions(-) diff --git a/doc/CpuPort.md b/doc/CpuPort.md index 9e0b028..c10d0a1 100644 --- a/doc/CpuPort.md +++ b/doc/CpuPort.md @@ -64,3 +64,33 @@ Writing 0x1 to register 0x7850 will transmit the frame. The Ethernet frame checksum and the TCP checksum are automatically calculated (offloaded) by the ASIC before transmitting on the wire. + +## The RTL tag words + +The frame header uses the Realtek Remote Control Protocol (RRCP) format or +the like. + +The `flags` word: + +``` +bit15 EFID_EN | 14:12 EFID | 11 PRI_EN | 10:8 PRI | +bit7 KEEP | 6 VSEL | 5 LEARN_DIS | 4:0 VIDX +``` + +All fields are in network byte order. + +* `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 +* `KEEP`: keep the 802.1Q tagging of the frame exactly as injected, + bypassing the egress tagging rules of the port +* `VSEL`, `VIDX`: classify the frame into the VLAN at this index of the + VLAN table +* `LEARN_DIS`: do not learn the source address from this frame + +The `pmask` word: bit 15 is `ALLOW`, bits 14 to 0 are a port mask. + +* `ALLOW` clear: the mask is the egress set, the frame goes to exactly + the ports given +* `ALLOW` set: the ASIC looks the destination up as usual and the mask + only limits which ports the result may use diff --git a/doc/l2.md b/doc/l2.md index c0aaf61..0a785a8 100644 --- a/doc/l2.md +++ b/doc/l2.md @@ -65,3 +65,36 @@ ASIC and flushing the table in order to quickly forget the learned entries. 3c:18:a0:7e:11:00 0x0001 learned 5 1c:2a:a3:23:00:02 0x0001 learned 7 ``` + +## Static multicast entries + +Slow-protocol frames such as LACPDUs and STP BPDUs have to reach the CPU +without being flooded to the other ports. No bridge relays these frames: +their addresses are in the set that 802.1D-2004 clause 7.12.6 forbids a +bridge to forward, and what travels the network is the information, with +every bridge regenerating BPDUs of its own on its designated ports. The reserved-multicast *trap* action +cannot do that on this hardware, because its destination is an external CPU +attached to a physical port, which these boards do not populate. The protocol +modules therefore leave the reserved-multicast action at *forward* and constrain +the egress with a static L2 multicast entry instead: the lookup hits the entry's +own port mask rather than the VLAN flood mask. Verified on a SWTGW218AS both +ways, with the CPU bit cleared, where delivery stops, and with the CPU bit alone, +where nothing egresses. + +`port_l2mc_set()` writes one such entry. The SMI layout is the L2 multicast +variant of the table entry: + +``` +DATA_IN_A = MAC bytes 5..2 -> c2 00 00 +DATA_IN_B = MAC[1..0] | vid<<16 | IVL<<29 | pmask[1:0]<<30 +DATA_IN_C = pmask[9:2] +``` + +Lookups are IVL, so an entry made for VID 0 is never matched and a caller adds +one entry per PVID in use. The write goes through the table access register +with the table selector set to the L2 lookup table, `TBL_L2_UNICAST` in the +code, a name that despite appearances covers the multicast entries as well. +The hardware hashes MAC and VID to pick the bucket slot by itself. +Writing the same MAC and VID again replaces the entry rather than adding a +second one, so a caller can retarget the mask at will, for instance back to all +ports to restore flooding. diff --git a/rtl837x_common.h b/rtl837x_common.h index 8cd556e..50bffb1 100644 --- a/rtl837x_common.h +++ b/rtl837x_common.h @@ -71,20 +71,10 @@ struct vlan_tag { #define VLAN_TAG_SIZE (sizeof (struct vlan_tag)) #define RTL_FRAME_TAG_ID 0x8899 #define RTL_FRAME_TAG_VERSION 0x04 -/* Bits of the tag's `flags` word (word2), per Linux DSA tag_rtl8_4: - * bit15 EFID_EN | 14:12 EFID | 11 PRI_EN | 10:8 PRI | - * bit7 KEEP | 6 VSEL | 5 LEARN_DIS | 4:0 VIDX - * NOTE: this word must be written through HTONS like every other tag field - - * writing the constant raw puts the bits in the wrong byte (0x0020 raw lands on - * the wire as 0x2000 = EFID, not LEARN_DIS), the ASIC then fails to parse the - * tag and forwards the frame with the 0x8899 header still on it. */ +/* Bits of the tag's `flags` word, see doc/CpuPort.md. */ #define RTL_TAG_LEARN_DIS 0x0020 /* do not learn the CPU's SA on the egress port */ #define RTL_TAG_KEEP 0x0080 /* keep the frame's 802.1Q tag format as injected */ -/* The `pmask` word (word3): bit15 ALLOW selects how 14:0 is interpreted. - * ALLOW=0 -> forwarding port mask (directed egress: frame goes exactly to the - * ports set). ALLOW=1 -> allowance mask (permission filter on a normal lookup), - * which for a one-hot mask yields an empty egress set - the frame disappears. - * Directed egress therefore requires ALLOW cleared, as mainline does. */ +/* The `pmask` word, see doc/CpuPort.md. */ // For TX, an 8 byte (plus 4 byte padding when when VLAN is enabled) // header describing the frame to be moved to the Asic is used diff --git a/rtl837x_port.c b/rtl837x_port.c index 1de3a4f..f9168bf 100644 --- a/rtl837x_port.c +++ b/rtl837x_port.c @@ -28,8 +28,6 @@ extern __xdata struct machine_runtime machine_detected; __xdata uint32_t l2_head; -/* Bounded-wait counter for the L2 table helpers; xdata because the 8051 - * internal-RAM overlay (OSEG) is full. */ __xdata uint8_t l2mc_guard; __xdata struct vlan_settings vlan_settings; @@ -320,11 +318,6 @@ void vlan_setup(void) __banked /* * Forget the dynamic L2 entries learned on one port. - * - * Same flush engine as port_l2_forget(), but with a single-port mask so a - * topology change only ages out the affected port instead of the whole - * table. Bounded wait (cf. port_l2mc_set): this runs from the STP tick, and - * an unbounded poll on a stuck engine would freeze the main loop. */ void port_l2_forget_port(uint8_t port) __banked { @@ -429,30 +422,12 @@ void port_l2_learned(void) __banked /* * Static L2 multicast entry for the link-local group 01:80:C2:00:00: * in VLAN `vid`, with member portmask `pmask` (bit 9 = CPU port). - * - * Slow-protocol frames (LACP, STP BPDUs) must reach the CPU without being - * flooded to other ports. The RMA "trap" action cannot deliver to the - * internal NIC on this hardware (its destination is an external CPU on a - * physical port), so the protocol modules keep the RMA action at "forward" - * and constrain the egress with this entry instead: the lookup hits the - * entry's portmask rather than the VLAN flood mask (hardware-verified with - * both the CPU bit cleared - delivery stops - and CPU-only - no egress). - * - * SMI layout (vendor SDK, L2-multicast entry variant): - * DATA_IN_A = MAC bytes 5..2 -> c2 00 00 - * DATA_IN_B = MAC[1..0] | vid<<16 | IVL<<29 | pmask[1:0]<<30 - * DATA_IN_C = pmask[9:2] - * Lookups are IVL (a VID-0 entry is not matched), so callers add one entry - * per PVID in use. The write command (table 4 = the whole L2 LUT) hashes - * MAC+VID and picks the bucket slot itself; TBL_EXECUTE self-clears. - * Overwriting the same MAC+VID replaces the entry, so a caller can retarget - * the mask at will (e.g. back to all ports to restore flooding). */ void port_l2mc_set(uint8_t mac_last, __xdata uint16_t vid, __xdata uint16_t pmask) __banked { l2mc_guard = 0; - do { /* wait out any previous table op (bounded, cf. the IGMP guards) */ + do { reg_read_m(RTL837X_TBL_CTRL); } while ((sfr_data[3] & TBL_EXECUTE) && ++l2mc_guard);