From c8c524b71f68d2990463aa8382a0b14b314aa623 Mon Sep 17 00:00:00 2001 From: logicog Date: Sat, 6 Dec 2025 17:41:17 +0100 Subject: [PATCH] Add an implementation for IGMP This adds an implementation for trapping IGMP packets to the CPU which will identify IGMPv1/2/3 packets, but handle only v3. The implementation then inserts/updates/deletes L3 MC entries in the L3 lookup table. The entries consist of an Ipv4 Destination IP (the IPv4 MC address), a Source IP (0.0.0.0) and a Portmask. Note that this implementation is not VLAN aware, as there is no hardware support in the device. An alternative strategy is to control switching of the L2-MC packets in which the IPv4-MC packets are transported (dst-MaC is 01:00:5e:xx:yy:zz, with xx:yy:zz corresponding to bits in the Ipv4-MC address). This will allow to use VLAN-aware packet switching. While code support is there for table insert/update/deletes, some further L2 configuration is missing. There is no support for IPv6 MC, yet. --- rtl837x_common.h | 7 +- rtl837x_igmp.c | 317 ++++++++++++++++++++++++++++++++++++++++++----- rtl837x_igmp.h | 3 + rtl837x_regs.h | 3 + rtl837x_stp.c | 4 +- rtlplayground.c | 12 +- 6 files changed, 310 insertions(+), 36 deletions(-) diff --git a/rtl837x_common.h b/rtl837x_common.h index eb7e99e..7bf1d9f 100644 --- a/rtl837x_common.h +++ b/rtl837x_common.h @@ -74,12 +74,11 @@ extern __xdata uint8_t uip_buf[UIP_CONF_BUFFER_SIZE+2]; struct rtl_tag { uint16_t tag; uint8_t version; - uint16_t dummy; - uint8_t flag; - uint16_t pmask; + uint8_t reason; + uint16_t flags; + uint16_t pmask; // A bit mask for a TX pkt, 4-bit port-number for RX }; - // Headers for calls in the common code area (HOME/BANK0) void print_string(__code char *p); void print_long(__xdata uint32_t a); diff --git a/rtl837x_igmp.c b/rtl837x_igmp.c index 050219a..ad16b2b 100644 --- a/rtl837x_igmp.c +++ b/rtl837x_igmp.c @@ -6,6 +6,8 @@ // #define REGDBG // #define DEBUG +#define IPMC_USES_L3MC + #include #include "rtl837x_common.h" #include "rtl837x_sfr.h" @@ -14,9 +16,71 @@ #include "machine.h" extern __code struct machine machine; + +#include "uip.h" + +#pragma codeseg BANK1 +#pragma constseg BANK1 + extern __xdata uint8_t cpuPort; extern __xdata uint8_t sfr_data[4]; +extern __xdata uint8_t uip_buf[UIP_CONF_BUFFER_SIZE + 2]; + +__xdata uint16_t idx; + +#ifdef IPMC_USES_L3MC +struct ipmc_table_entry { + uint8_t sip[4]; + uint8_t dip[4]; + uint16_t pmask; + uint8_t igmp_index; + uint8_t igmp_asic; +}; +static __xdata struct ipmc_table_entry entry; +#else +struct l2mc_table_entry { + uint8_t mac[6]; + uint16_t vlan; + uint16_t pmask; + uint8_t is_svl; + uint8_t igmp_index; + uint8_t igmp_asic; +}; +static __xdata struct l2mc_table_entry entry; +#endif + +struct igmp_pkt { + uint8_t ipv4mc_addr[6]; + uint8_t src_addr[6]; + struct rtl_tag rtl_tag; + uint16_t ipv4_tag; + uint8_t hlen; + uint8_t dscp; + uint16_t len; + uint16_t id; + uint16_t flags; + uint8_t ttl; + uint8_t protocol; + uint16_t checksum; + uint8_t src_ip[4]; + uint8_t dst_ip[4]; + uint8_t ip_opt; + uint8_t ip_len; + uint16_t ra; + uint8_t igmp_type; + uint8_t igmp_res1; + uint16_t igmp_checksum; + uint16_t igmp_res2; + uint16_t igmp_records; + uint8_t igmp_rtype; + uint8_t igmp_auxlen; + uint16_t igmp_nsrc; + uint8_t mc_ip[4]; +}; +#define IGMP_I ((__xdata struct igmp_pkt *)&uip_buf[0]) + + void igmp_setup(void) __banked { uint8_t i; @@ -25,20 +89,9 @@ void igmp_setup(void) __banked REG_SET(RTL837X_IPV4_PORT_MC_LM_ACT, LOOKUP_MISS_FLOOD); REG_SET(RTL837X_IPV6_PORT_MC_LM_ACT, LOOKUP_MISS_FLOOD); - // For now, forward all unkown MC pkts (2 bits per port. 00: flood via floodmask, 01: drop, 10: trap, 11: to rport) - REG_SET(RTL837X_MC_LOOKUPMISS_ACTIONS, 0x00000000); //0x4f78 - // Define ports where unknown MC addresses are flooded to: - REG_SET(RTL837X_MC_FLOODMASK, machine.isRTL8373 ? PMASK_9 : PMASK_6); - - // Define ports where unknown MC addresses are flooded to: - if (isRTL8373) { - REG_SET(RTL837X_IPV4_UNKN_MC_FLD_PMSK, PMASK_9); - REG_SET(RTL837X_IPV6_UNKN_MC_FLD_PMSK, PMASK_9); - } else { - REG_SET(RTL837X_IPV4_UNKN_MC_FLD_PMSK, PMASK_6); - REG_SET(RTL837X_IPV6_UNKN_MC_FLD_PMSK, PMASK_6); - } + REG_SET(RTL837X_IPV4_UNKN_MC_FLD_PMSK, machine.isRTL8373? PMASK_9: PMASK_6); + REG_SET(RTL837X_IPV6_UNKN_MC_FLD_PMSK, machine.isRTL8373? PMASK_9: PMASK_6); // Enable lookup of IPv4 MC addresses in table reg_bit_set(RTL837X_L2_CTRL, L2_CTRL_LUT_IPMC_HASH); @@ -60,46 +113,43 @@ void igmp_setup(void) __banked * 00: handle in HW by ASIC * 01: flood * 10: trap - * 10: drop + * 11: drop * Bits 0-1: IGMPv1, 2-3: IGMPv2, 4-5: IGMPv3, 6-7: MLDv1 (for IPv6), 8-9: MLDv2 * For now the IGMP protocols are flooded (01), MLD which MAC-based is handled by ASIC * All messages are allowed and maximum MC group is 0xff */ - for (i = minPort; i <= maxPort; i++) + for (i = machine.min_port; i <= machine.max_port; i++) { + print_byte(i); write_char(':'); REG_SET(RTL837X_IGMP_PORT_CFG + (i << 2), IGMP_MAX_GROUP | IGMP_PROTOCOL_ENABLE | IGMP_FLOOD); + write_char('\n'); + } - // Allow all physical ports to be dynamic router ports +/* // Allow all physical ports to be dynamic router ports reg_read_m(RTL837X_IGMP_ROUTER_PORT); if (isRTL8373) { REG_WRITE(RTL837X_IGMP_ROUTER_PORT, PMASK_9 >> 8, PMASK_9 & 0xff, sfr_data[1], sfr_data[0]); } else { REG_WRITE(RTL837X_IGMP_ROUTER_PORT, PMASK_6 >> 8, PMASK_6 & 0xff, sfr_data[1], sfr_data[0]); } - +*/ } void igmp_enable(void) __banked { - uint8_t i; - print_string("igmp_enable called\n"); // Configure trapping of unhandled IGMP protocol packets to CPU REG_SET(RTL837X_IGMP_TRAP_CFG, IGMP_CPU_PORT | IGMP_TRAP_PRIORITY); // Drop unknown IP-MC packets - if (isRTL8373) { - REG_SET(RTL837X_IPV4_PORT_MC_LM_ACT, LOOKUP_MISS_DROP_9); - REG_SET(RTL837X_IPV6_PORT_MC_LM_ACT, LOOKUP_MISS_DROP_9); - } else { - REG_SET(RTL837X_IPV4_PORT_MC_LM_ACT, LOOKUP_MISS_DROP_6); - REG_SET(RTL837X_IPV6_PORT_MC_LM_ACT, LOOKUP_MISS_DROP_6); - } + REG_SET(RTL837X_IPV4_PORT_MC_LM_ACT, machine.isRTL8373? LOOKUP_MISS_DROP_9: LOOKUP_MISS_DROP_6); +// REG_SET(RTL837X_IPV6_PORT_MC_LM_ACT, machine.isRTL8373? LOOKUP_MISS_DROP_9: LOOKUP_MISS_DROP_6); // Configure per-port IGMP configuration, bits 0-10 enable MC protocol snooping, // bits 16-24 configure max MC group used by that port. Trap to CPU (10) - for (i = minPort; i <= maxPort; i++) - REG_SET(RTL837X_IGMP_PORT_CFG + (i << 2), IGMP_MAX_GROUP | IGMP_PROTOCOL_ENABLE | IGMP_ASIC); + for (uint8_t i = machine.min_port; i <= machine.max_port; i++) { + REG_SET(RTL837X_IGMP_PORT_CFG + (i << 2), IGMP_MAX_GROUP | IGMP_PROTOCOL_ENABLE | IGMP_TRAP); + } } @@ -114,3 +164,214 @@ void igmp_router_port_set(uint16_t pmask) __banked print_sfr_data(); write_char('\n'); REG_WRITE(RTL837X_IGMP_ROUTER_PORT, sfr_data[0], sfr_data[1], pmask >> 8, pmask & 0xff); } + + +/* + * IGMP show the current entries and state + */ +void igmp_show(void) __banked +{ + print_string("igmp_show called\n"); + for (uint8_t i = machine.min_port; i <= machine.max_port; i++) { + write_char('0' + i); write_char(':'); + reg_read_m(RTL837X_IGMP_PORT_CFG + (i << 2)); + print_sfr_data(); + write_char('\n'); + } + // TODO: print all L3MC entries in the table +} + + +#ifdef IPMC_USES_L3MC +void entry_to_l3mc(void) +{ + REG_WRITE(RTL837x_TBL_DATA_IN_A, entry.sip[0], entry.sip[1], entry.sip[2], entry.sip[3]); + REG_WRITE(RTL837x_TBL_DATA_IN_B, ((entry.pmask & 0x3) << 6) | (entry.dip[0] & 0xf) | 0x10, entry.dip[1], entry.dip[2], entry.dip[3]); + REG_WRITE(RTL837x_TBL_DATA_IN_C, 0x00, entry.igmp_asic & 1, entry.igmp_index, entry.pmask >> 2); +} +#else +void entry_to_l2mc(void) +{ + // R5cb8-5e004201 R5cbc-20010100 R5cc0-00000020 R5cac-00000403 + REG_WRITE(RTL837x_TBL_DATA_IN_A, entry.mac[2], entry.mac[3], entry.mac[4], entry.mac[5]); + REG_WRITE(RTL837x_TBL_DATA_IN_B, 0x20 | ((entry.pmask & 0x3) << 6) | (entry.vlan >> 8), entry.vlan & 0xff, entry.mac[0], entry.mac[1]); + REG_WRITE(RTL837x_TBL_DATA_IN_C, 0x00, entry.igmp_asic & 1, entry.igmp_index, entry.pmask >> 2); +} +#endif + + +void igmp_packet_handler(void) __banked +{ + // By default we do not send anything out + uip_len = 0; + +#ifdef DEBUG + print_string("\nIPv4 MC packet:\n"); + for (uint8_t i = 0; i < 80; i++) { + print_byte(uip_buf[i]); + write_char(' '); + } + write_char('\n'); +#endif + if (IGMP_I->protocol != 2) + return; +#ifdef DEBUG + print_string("Found IGMP, type: "); print_byte(IGMP_I->igmp_type); write_char('\n'); +#endif + // We react to IGMPv1/v2 and v3 membership reports + if (!(IGMP_I->igmp_type == 0x12 || IGMP_I->igmp_type == 0x16 || IGMP_I->igmp_type == 0x22)) + return; +#ifdef DEBUG + print_string("IGMP membership report, type "); print_byte(IGMP_I->igmp_rtype); write_char('\n'); +#endif + +#ifdef IPMC_USES_L3MC + memset(&entry, 0, sizeof(struct ipmc_table_entry)); + // For IPv4 MC, the Source-IP is 0.0.0.0 + entry.sip[0] = 0x00; entry.sip[1] = 0x00; entry.sip[2] = 0x00; entry.sip[3] = 0x00; + // For IPv4 MC, the Destination-IP is the IPv4 MC address + entry.dip[0] = IGMP_I->mc_ip[0]; entry.dip[1] = IGMP_I->mc_ip[1]; entry.dip[2] = IGMP_I->mc_ip[2]; entry.dip[3] = IGMP_I->mc_ip[3]; + entry_to_l3mc(); +#else + /* The L2 Multicast MAC for IP-Multicast is 01:00:5e:xx:yy:zz, where + * xx = MC_IP[1] & 0x7f + * yy = MC_IP[2] + * zz = MC_IP[3] + */ + memset(&entry, 0, sizeof(struct l2mc_table_entry)); + entry.mac[0] = 0x01; entry.mac[1] = 0x00; entry.mac[2] = 0x5e; + entry.mac[3] = IGMP_I->mc_ip[1] & 0x7f; entry.mac[4] = IGMP_I->mc_ip[2]; entry.mac[5] = IGMP_I->mc_ip[3]; + entry.vlan = 1; //TODO: Get this out of the packet and compare with VLAN table! + entry_to_ipmc(); +#endif + // Wait for any pending Table operations to end + do { + reg_read_m(RTL837X_TBL_CTRL); + } while (sfr_data[3] & 1); + + reg_read_m(RTL837x_TBL_DATA_0); +#ifdef DEBUG + print_sfr_data(); +#endif + sfr_data[2] &= 0x3f; // Sets the Read-method to 0 (why MAC-lookup?) and clear the CLEAR-Entry bit + sfr_data[1] &= 0xf8; + reg_write_m(RTL837x_TBL_DATA_0); +#ifdef DEBUG + print_string(" l2 ctrl now: "); + print_sfr_data(); +#endif + // First try to find entry to see whether it needs to be updated + REG_WRITE(RTL837X_TBL_CTRL, 0x00, 0x00, TBL_L2_UNICAST, TBL_EXECUTE); + do { + reg_read_m(RTL837X_TBL_CTRL); + } while (sfr_data[3] & 0x1); +#ifdef DEBUG + print_string("\nsearch done\n"); + print_string("Table data searched:\n"); + reg_read_m(RTL837x_TBL_DATA_IN_A); + print_sfr_data(); write_char(' '); + reg_read_m(RTL837x_TBL_DATA_IN_B); + print_sfr_data(); write_char(' '); + reg_read_m(RTL837x_TBL_DATA_IN_C); + print_sfr_data(); write_char('\n'); + print_string("Table data gotten:\n"); + reg_read_m(RTL837x_L2_DATA_OUT_A); write_char(' '); + print_sfr_data(); + reg_read_m(RTL837x_L2_DATA_OUT_B); + print_sfr_data(); write_char(' '); + reg_read_m(RTL837x_L2_DATA_OUT_C); + print_sfr_data(); write_char('\n'); + print_string("Result: "); +#endif + reg_read_m(RTL837x_TBL_DATA_0); +#ifdef DEBUG + print_sfr_data(); +#endif + idx = ((sfr_data[2] & 0xf) << 8) | sfr_data[3]; + if (IGMP_I->igmp_rtype == 0x4) {// Join group + if (sfr_data[2] & 0x10) { + print_string("\nIGMP-Entry FOUND\n"); + reg_read_m(RTL837x_L2_DATA_OUT_B); + entry.pmask = sfr_data[0] >> 6; + reg_read_m(RTL837x_L2_DATA_OUT_C); + entry.pmask |= ((uint16_t)sfr_data[3]) << 2; + } + // Update (found) entry with portmask from trapped Packet + entry.pmask |= (1L << (IGMP_I->rtl_tag.pmask >> 8)); // Swap bytes from network order, only 4 LSB count +// print_string("\nPort-Mask: "); print_short(entry.pmask); write_char('\n'); + } else if (IGMP_I->igmp_rtype == 0x3){ // Leave group + if (sfr_data[2] & 0x10) { + print_string("\nIGMP_Entry FOUND\n"); + reg_read_m(RTL837x_L2_DATA_OUT_B); + entry.pmask = sfr_data[0] >> 6; + reg_read_m(RTL837x_L2_DATA_OUT_C); + entry.pmask |= ((uint16_t)sfr_data[3]) << 2; +#ifdef DEBUG + print_string("Portmask: "); + print_short(entry.pmask); + print_string("Index: "); + print_short(idx); + write_char('\n'); +#endif + // Remove portmask of IGMP packet from entry + entry.pmask &= ~(1L << (IGMP_I->rtl_tag.pmask >> 8)); // Swap bytes from network order, only 4 LSB count +// print_string("\nPort-Mask: "); print_short(entry.pmask); write_char('\n'); + } else { + print_string("IGMP Entry already deleted\n"); + return; + } + if (!entry.pmask && idx) { // No more ports in that group and an actual entry? + // Delete Entry + reg_read_m(RTL837x_TBL_DATA_0); + sfr_data[1] |= 0x04; // Clear entry + reg_write_m(RTL837x_TBL_DATA_0); + REG_WRITE(RTL837X_TBL_CTRL, idx >> 8, idx & 0xff, TBL_L2_UNICAST, TBL_WRITE | TBL_EXECUTE); + do { + reg_read_m(RTL837X_TBL_CTRL); + } while (sfr_data[3] & 0x1); + print_string("IGMP Entry deleted\n"); + return; + } + } else { // Unknown message: ignore. + return; + } + + if (!entry.pmask) + return; + print_string("Updating IGMP entry\n"); + // Write the updated entry +#ifdef IPMC_USES_L3MC + entry_to_l3mc(); +#else + entry_to_ipmc(); +#endif + reg_read_m(RTL837x_TBL_DATA_0); +#ifdef DEBUG + print_sfr_data(); +#endif + sfr_data[2] &= 0x3f; // Set the Read-method to 0 and clear the CLEAR-Entry bit + sfr_data[1] &= 0xf8; + reg_write_m(RTL837x_TBL_DATA_0); +#ifdef DEBUG + print_string(" l2 ctrl now: "); + print_sfr_data(); +#endif + reg_read_m(RTL837X_TBL_CTRL); + REG_WRITE(RTL837X_TBL_CTRL, sfr_data[0], sfr_data[1], TBL_L2_UNICAST, TBL_WRITE | TBL_EXECUTE); + do { + reg_read_m(RTL837X_TBL_CTRL); + } while (sfr_data[3] & 0x1); +#ifdef DEBUG + print_string("\nupdate done\n"); + print_string("Table data written:\n"); + reg_read_m(RTL837x_TBL_DATA_IN_A); + print_sfr_data(); write_char(' '); + reg_read_m(RTL837x_TBL_DATA_IN_B); + print_sfr_data(); write_char(' '); + reg_read_m(RTL837x_TBL_DATA_IN_C); + print_sfr_data(); write_char('\n'); + print_string("Result: "); + reg_read_m(RTL837x_TBL_DATA_0); + print_sfr_data(); +#endif +} diff --git a/rtl837x_igmp.h b/rtl837x_igmp.h index 9891634..ab3cf39 100644 --- a/rtl837x_igmp.h +++ b/rtl837x_igmp.h @@ -5,5 +5,8 @@ void igmp_setup(void) __banked; void igmp_enable(void) __banked; +void igmp_router_port_set(uint16_t pmask) __banked; +void igmp_packet_handler(void) __banked; +void igmp_show(void) __banked; #endif diff --git a/rtl837x_regs.h b/rtl837x_regs.h index ba88e07..dc0ea62 100644 --- a/rtl837x_regs.h +++ b/rtl837x_regs.h @@ -127,6 +127,8 @@ #define RTL837x_L2_DATA_OUT_B 0x5cd0 #define RTL837x_L2_DATA_OUT_C 0x5cd4 #define RTL837x_TBL_DATA_IN_A 0x5cb8 +#define RTL837x_TBL_DATA_IN_B 0x5cbc +#define RTL837x_TBL_DATA_IN_C 0x5cc0 #define RTL837x_PVID_BASE_REG 0x4e1c #define RTL837x_L2_TBL_FLUSH_CTRL 0x53d4 @@ -186,6 +188,7 @@ #define RTL837X_IGMP_PORT_CFG 0x52a0 #define IGMP_MAX_GROUP 0x00ff0000 #define IGMP_PROTOCOL_ENABLE 0x00007c00 +#define IGMP_TRAP 0x0000002a #define IGMP_FLOOD 0x00000015 #define IGMP_ASIC 0x00000000 #define RTL837X_IGMP_ROUTER_PORT 0x529c diff --git a/rtl837x_stp.c b/rtl837x_stp.c index 3aca109..e04be9a 100644 --- a/rtl837x_stp.c +++ b/rtl837x_stp.c @@ -151,8 +151,8 @@ void stp_cnf_send(uint8_t port) STP_O->rtl_tag.tag = HTONS(0x8899); STP_O->rtl_tag.version = 0x04; - STP_O->rtl_tag.dummy = 0x0000; - STP_O->rtl_tag.flag = 0x20; // WHY ??? + STP_O->rtl_tag.reason = 0x00; + STP_O->rtl_tag.flags = 0x0020; // Disable L2 learning STP_O->rtl_tag.pmask = HTONS(((uint16_t)1) << port); STP_O->msg_len = HTONS(0x27); diff --git a/rtlplayground.c b/rtlplayground.c index c488fb2..5f81c54 100644 --- a/rtlplayground.c +++ b/rtlplayground.c @@ -849,6 +849,8 @@ void handle_rx(void) rx_packet_vlan |= uip_buf[12 + RTL_TAG_SIZE + 3]; #ifdef RXTXDBG print_string(" RX-VLAN: "); print_short(rx_packet_vlan); write_char('\n'); + print_string(" RX dst: "); print_byte(uip_buf[0]); print_byte(uip_buf[1]); print_byte(uip_buf[2]); + print_byte(uip_buf[3]); print_byte(uip_buf[4]); print_byte(uip_buf[5]); write_char('\n'); #endif if (stpEnabled && uip_buf[0] == 0x01 && uip_buf[1] == 0x80 && uip_buf[2] == 0xc2 // STP packet? && uip_buf[3] == 0x00 && uip_buf[4] == 0x00 && uip_buf[5] == 0x00) { @@ -857,13 +859,19 @@ void handle_rx(void) print_string("STP TX\n"); tcpip_output(); } + } else if (uip_buf[0] == 0x01 && uip_buf[1] == 0x00 && uip_buf[2] == 0x5e // IPv4-MC packet? + && uip_buf[3] == 0x00 && uip_buf[4] == 0x00 && uip_buf[5] == 0x16) { + igmp_packet_handler(); + if (uip_len) { + tcpip_output(); + } } else if (uip_buf[ETHERTYPE_OFFSET] == 0x08 && uip_buf[ETHERTYPE_OFFSET + 1] == 0x06) { // ARP? uip_arp_arpin(); if (uip_len) { tcpip_output(); } - } else if (uip_buf[ETHERTYPE_OFFSET] == 0x08 && uip_buf[ETHERTYPE_OFFSET + 1] == 0x00) { - uip_arp_ipin(); + } else if (uip_buf[ETHERTYPE_OFFSET] == 0x08 && uip_buf[ETHERTYPE_OFFSET + 1] == 0x00) { // TCP? + uip_arp_ipin(); // Learn MAC addresses in TCP packets uip_input(); if (uip_len) { // Add ethernet frame