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.
This commit is contained in:
d00f
2026-08-18 23:29:10 +02:00
committed by d00f
parent 0160b430f4
commit 4a78b2dc8c
4 changed files with 66 additions and 38 deletions
+30
View File
@@ -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 checksum and the TCP checksum are automatically calculated (offloaded) by the
ASIC before transmitting on the wire. 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
+33
View File
@@ -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 3c:18:a0:7e:11:00 0x0001 learned 5
1c:2a:a3:23:00:02 0x0001 learned 7 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 <mac_last>
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.
+2 -12
View File
@@ -71,20 +71,10 @@ struct vlan_tag {
#define VLAN_TAG_SIZE (sizeof (struct vlan_tag)) #define VLAN_TAG_SIZE (sizeof (struct vlan_tag))
#define RTL_FRAME_TAG_ID 0x8899 #define RTL_FRAME_TAG_ID 0x8899
#define RTL_FRAME_TAG_VERSION 0x04 #define RTL_FRAME_TAG_VERSION 0x04
/* Bits of the tag's `flags` word (word2), per Linux DSA tag_rtl8_4: /* Bits of the tag's `flags` word, see doc/CpuPort.md. */
* 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. */
#define RTL_TAG_LEARN_DIS 0x0020 /* do not learn the CPU's SA on the egress port */ #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 */ #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. /* The `pmask` word, see doc/CpuPort.md. */
* 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. */
// For TX, an 8 byte (plus 4 byte padding when when VLAN is enabled) // 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 // header describing the frame to be moved to the Asic is used
+1 -26
View File
@@ -28,8 +28,6 @@ extern __xdata struct machine_runtime machine_detected;
__xdata uint32_t l2_head; __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 uint8_t l2mc_guard;
__xdata struct vlan_settings vlan_settings; __xdata struct vlan_settings vlan_settings;
@@ -320,11 +318,6 @@ void vlan_setup(void) __banked
/* /*
* Forget the dynamic L2 entries learned on one port. * 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 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:<mac_last> * Static L2 multicast entry for the link-local group 01:80:C2:00:00:<mac_last>
* in VLAN `vid`, with member portmask `pmask` (bit 9 = CPU port). * 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 <mac_last>
* 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 void port_l2mc_set(uint8_t mac_last, __xdata uint16_t vid, __xdata uint16_t pmask) __banked
{ {
l2mc_guard = 0; l2mc_guard = 0;
do { /* wait out any previous table op (bounded, cf. the IGMP guards) */ do {
reg_read_m(RTL837X_TBL_CTRL); reg_read_m(RTL837X_TBL_CTRL);
} while ((sfr_data[3] & TBL_EXECUTE) && ++l2mc_guard); } while ((sfr_data[3] & TBL_EXECUTE) && ++l2mc_guard);