From 2d8c0efabdb61ff58ba35380b558bc688f5afded Mon Sep 17 00:00:00 2001 From: logicog Date: Fri, 4 Jul 2025 09:17:10 +0200 Subject: [PATCH] Add 802.1Q VLAN and PVID support. Support VLAN tag at CPU-port --- doc/CpuPort.md | 3 +- rtl837x_port.c | 186 ++++++++++++++++++++++++++++++++++++++---------- rtl837x_port.h | 4 +- rtlplayground.c | 75 +++++++++++++++++-- 4 files changed, 222 insertions(+), 46 deletions(-) diff --git a/doc/CpuPort.md b/doc/CpuPort.md index fecc2d0..96b8d88 100644 --- a/doc/CpuPort.md +++ b/doc/CpuPort.md @@ -31,7 +31,8 @@ length as given by the length in the frame header + 7, again divided by 8. The received frame will have an RTL proprietary Ethernet frame type of 0x8899 (RRPC) where normally the frame type 0x0800 for IPv4 would be located. Further 6 bytes follow describing the frame, before the normal IPv4 data -starts. +starts. A documentation can be found here: +[TAG8899_COMMIT](https://github.com/torvalds/linux/commit/1521d5adfc2b557e15f97283c8b7ad688c3ebc40) After copying over header and frame, the frame is marked read in the ring buffer on the ASIC side by writing 0x1 to RTL837X_REG_RX_DONE (0x784c). diff --git a/rtl837x_port.c b/rtl837x_port.c index 380ec94..87bfc51 100644 --- a/rtl837x_port.c +++ b/rtl837x_port.c @@ -3,7 +3,8 @@ * This code is in the Public Domain */ -// #define REGDBG +#define REGDBG +#define DEBUG #include #include "rtl837x_common.h" @@ -17,6 +18,7 @@ extern __xdata uint8_t minPort; extern __xdata uint8_t maxPort; extern __xdata uint8_t nSFPPorts; extern __xdata uint8_t sfr_data[4]; +extern __xdata uint8_t cpuPort; __xdata uint32_t l2_head; @@ -29,7 +31,12 @@ __xdata uint32_t l2_head; * CC: BIT 0: 01: Execute. Bit 1: 1: WRITE, 0: READ * TT: 04: L2-table, 03: VLAN-table */ -#define TBL_L2_UNICAST 0x04 +// Table operation bit-smasks +#define TBL_WRITE 0x02 +#define TBL_EXECUTE 0x01 +// Table types +#define TBL_L2_UNICAST 0x04 +#define TBL_VLAN 0x03 #define RTL837x_TBL_DATA_0 0x5cb0 #define RTL837x_L2_LIST_DATA_A 0x5ccc @@ -39,6 +46,83 @@ __xdata uint32_t l2_head; #define RTL837x_PVID_BASE_REG 0x4e1c + +void port_mirror(uint8_t port, uint16_t source_mask, uint8_t directions) __banked +{ + print_string("\r\nport_mirror called \r\n"); +} + + +void port_pvid_set(uint8_t port, uint16_t pvid) __banked +{ + // r4e1c:00001001 R4e1c-000017d0 r6738:00000000 R6738-00000000 (no filtering) + print_string("\r\nport_pvid_set called \r\n"); + uint16_t reg = RTL837x_PVID_BASE_REG + ((port >> 1) << 2); + + reg_read_m(reg); + if (port & 0x1) { + REG_WRITE(reg, sfr_data[0], pvid >> 4, sfr_data[2] & 0x0f | (pvid << 4), sfr_data[3]); + } else { + REG_WRITE(reg, sfr_data[0], sfr_data[1], sfr_data[2] & 0xf0 | (pvid >> 8), pvid); + } +} + + +void vlan_delete(uint16_t vlan) __banked +{ + print_string("\r\nvlan_delete called \r\n"); + // R5cac-07d30301 r5cac:07d30300 +} + + +/* + * A member that is not tagged, is untagged + */ +void vlan_create(uint16_t vlan, uint16_t members, uint16_t tagged) __banked +{ + /* First line: + 7-9: Untagged: 1-1, Not-Member: 1-0 + 0111111111 + 1111000000 + 9 port 0 + + 1-3: Untagged: 1-1 + 0111111111 + 1000000111 + + 1-3: Tagged: 0-1 + 0111111000 + 1000000111 + + 7-9: Tagged: 0-1 + 0000111111 + 1111000000 + In 1-0 -> 1-1 FIRST Bit: XOR, Second bit stays + In 1-1 -> 0-1 + In 0-1 // Not allowed + In 0-0 -> 1-0 + */ + // R5cb8-02 07e207 R5cac-07d20303 + print_string("\r\nvlan_create called: "); print_short(vlan); write_char(' '); print_short(members); write_char(':'); print_short(tagged); + + uint16_t a = members ^ tagged; + // Initialize VLAN table with VLAN 1 + REG_WRITE(RTL837x_TBL_DATA_IN_A, 0x02, (a >> 8) & 0x0f, (a << 2) | (tagged >> 8), tagged); + REG_WRITE(RTL837X_TBL_CTRL, vlan >> 8, vlan, TBL_VLAN, TBL_WRITE | TBL_EXECUTE); + do { + reg_read_m(RTL837X_TBL_CTRL); + } while (sfr_data[3] & TBL_EXECUTE); + print_string("\r\nvlan_create done \r\n"); +} + + +/* + * Configures a default VLAN 1 and enables 4k VLAN tables + * All ports are made members of the VLAN and VLAN filtering + * is enabled on all ports + * PVID is set to 1 for all ports + * Called upon reboot + */ void vlan_setup(void) __banked { print_string("\r\nvlan_setup called \r\n"); @@ -48,43 +132,66 @@ void vlan_setup(void) __banked REG_SET(RTL837X_TBL_CTRL, 0x00010303); do { reg_read_m(RTL837X_TBL_CTRL); - } while (sfr_data[3] & 0x01); + } while (sfr_data[3] & TBL_EXECUTE); // Set PVID 1 for every port. TODO: Skip unused ports! - for (uint8_t i = minPort; i <= maxPort + 1; i++) { // Do this also for the CPU port - print_byte(i); write_char(':'); write_char(' '); + for (uint8_t i = minPort; i <= maxPort + 1; i++) { // Do this also for the CPU port (+1) uint16_t reg = RTL837x_PVID_BASE_REG + ((i >> 1) << 2); +#ifdef DEBUG + print_byte(i); write_char(':'); write_char(' '); print_short(reg); write_char(' '); write_char('B'); write_char('>'); print_sfr_data(); +#endif reg_read_m(reg); if (i & 0x1) { REG_WRITE(reg, sfr_data[0], 0, sfr_data[2] & 0x0f | 0x10, sfr_data[3]); } else { REG_WRITE(reg, sfr_data[0], sfr_data[1], sfr_data[2] & 0xf0, 0x01); } +#ifdef DEBUG + reg_read_m(reg); write_char(' '); write_char('A'); write_char('>'); print_sfr_data(); +#endif + // EGRESS filtering for port: removal of additional VLAN tag reg_bit_clear(0x6738, i << 1); - reg_bit_clear(0x6738, i << 1 + 1); - reg_bit_set(0x4e18, i); + reg_bit_clear(0x6738, (i << 1) + 1); + if (i != cpuPort) + reg_bit_set(0x4e18, i); + else + reg_bit_clear(0x4e18, i); +#ifdef DEBUG print_string("\r\n"); +#endif } - // Enable 4k VLAN REG_SET(0x4e14, 4); REG_SET(0x4e30, 0); REG_SET(0x4e34, 0); - // Enable VLAN 1 - REG_SET(RTL837x_TBL_DATA_IN_A, 0x0207ffff); - REG_SET(RTL837X_TBL_CTRL, 0x00010303); + // Enable VLAN 1: Ports 0-9, i.e. including the CPU port are untagged members + REG_SET(RTL837x_TBL_DATA_IN_A, 0x0207ffff); // 02: Entry valid, 7ffff: membership + REG_SET(RTL837X_TBL_CTRL, 0x00010303); // Write VLAN 1 do { reg_read_m(RTL837X_TBL_CTRL); - } while (sfr_data[3] & 0x01); + } while (sfr_data[3] & TBL_EXECUTE); + + // Configure trunking + REG_SET(0x4f4c, 0x0000007e); + +#ifdef DEBUG + print_string("\r\nvlan_setup, REG 0x6738: "); print_reg(0x6738); + print_string("\r\nvlan_setup, REG 0x4e18: "); print_reg(0x4e18); + print_string("\r\nvlan_setup, REG 0x4e14: "); print_reg(0x4e14); + print_string("\r\nvlan_setup, REG 0x4e30: "); print_reg(0x4e30); + print_string("\r\nvlan_setup, REG 0x4e34: "); print_reg(0x4e34); + print_string("\r\nvlan_setup, REG 0x4f4c: "); print_reg(0x4f4c); +#endif print_string("\r\nvlan_setup done \r\n"); } + /* * Forget all dynamic L2 learned entries */ @@ -119,7 +226,7 @@ void port_l2_learned(void) __banked uint16_t first_entry = 0xffff; // Table does not have that many entries while (1) { - uint8_t port = 0; + uint8_t port = 0, other = 0; reg_read_m(RTL837x_TBL_DATA_0); REG_WRITE(RTL837x_TBL_DATA_0, sfr_data[0], sfr_data[1],sfr_data[2] | 0xc0, sfr_data[3]); @@ -130,32 +237,34 @@ void port_l2_learned(void) __banked // MAC reg_read_m(RTL837x_L2_LIST_DATA_B); - print_byte(sfr_data[2]); write_char(':'); - print_byte(sfr_data[3]); write_char(':'); - port = (sfr_data[0] >> 6) & 0x3; - reg_read_m(RTL837x_L2_LIST_DATA_A); - print_byte(sfr_data[0]); write_char(':'); - print_byte(sfr_data[1]); write_char(':'); - print_byte(sfr_data[2]); write_char(':'); - print_byte(sfr_data[3]); write_char('\t'); + if ((sfr_data[0] & 0x20)) { // Check entry is valid + print_byte(sfr_data[2]); write_char(':'); + print_byte(sfr_data[3]); write_char(':'); + port = (sfr_data[0] >> 6) & 0x3; + other = sfr_data[0]; + reg_read_m(RTL837x_L2_LIST_DATA_A); + print_byte(sfr_data[0]); write_char(':'); + print_byte(sfr_data[1]); write_char(':'); + print_byte(sfr_data[2]); write_char(':'); + print_byte(sfr_data[3]); write_char('\t'); - // VLAN - reg_read_m(RTL837x_L2_LIST_DATA_B); - print_short( ((uint16_t) (sfr_data[0] & 0x0f)) | sfr_data[1]); // VLAN + // VLAN + reg_read_m(RTL837x_L2_LIST_DATA_B); + print_short( ((uint16_t) (sfr_data[0] & 0x0f)) | sfr_data[1]); // VLAN - // type - reg_read_m(RTL837x_L2_LIST_DATA_C); - if (sfr_data[2] & 0x1) - print_string("\tstatic\t"); - else - print_string("\tlearned\t"); - - port |= (sfr_data[3] & 0x3) << 2; - if (port < 9) - write_char('1' + port); - else - print_string("10"); + // type + reg_read_m(RTL837x_L2_LIST_DATA_C); + if (sfr_data[2] & 0x1) + print_string("\tstatic\t"); + else + print_string("\tlearned\t"); + port |= (sfr_data[3] & 0x3) << 2; + if (port < 9) + write_char('1' + port); + else + print_string("10"); + } reg_read_m(RTL837x_TBL_DATA_0); entry = (((uint16_t)sfr_data[2] & 0x0f) << 8) | sfr_data[3] + 1; if (first_entry == 0xffff) { @@ -164,9 +273,12 @@ void port_l2_learned(void) __banked if (first_entry == entry) break; } +#ifdef DEBUG + write_char(' '); print_sfr_data(); + write_char(' '); print_byte(other); +#endif print_string("\r\n"); } - print_string("\r\nport_l2_learned done \r\n"); } diff --git a/rtl837x_port.h b/rtl837x_port.h index bf57195..8cbfc8e 100644 --- a/rtl837x_port.h +++ b/rtl837x_port.h @@ -5,5 +5,7 @@ uint8_t port_l2_forget(void) __banked; void port_l2_learned(void) __banked; void port_stats_print(void) __banked; void vlan_setup(void) __banked; - +void port_pvid_set(uint8_t port, uint16_t pvid) __banked; +void vlan_create(uint16_t vlan, uint16_t members, uint16_t tagged) __banked; +void vlan_delete(uint16_t vlan) __banked; #endif diff --git a/rtlplayground.c b/rtlplayground.c index a6ad672..d48272d 100644 --- a/rtlplayground.c +++ b/rtlplayground.c @@ -47,7 +47,7 @@ volatile __xdata uint32_t ticks; volatile __xdata uint8_t sec_counter; volatile __xdata uint16_t sleep_ticks; -#define N_WORDS 10 +#define N_WORDS 16 __xdata signed char cmd_words_b[N_WORDS]; // Buffer for serial input, SBUF_SIZE must be power of 2 < 256 @@ -62,8 +62,11 @@ __code uint8_t * __code hex = "0123456789abcdef"; __xdata uint8_t flash_buf[256]; // For RX data, a propriatary RTL FRAME is inserted. Instead of 0x0800 for IPv4, -// the RTL_FRAME_TAG_ID is used as part of an 8-byte tag +// the RTL_FRAME_TAG_ID is used as part of an 8-byte tag. When VLAN is activated, +// the VLAN tag is inserted after the RTL tag +// See here for the RTL tag: https://github.com/torvalds/linux/commit/1521d5adfc2b557e15f97283c8b7ad688c3ebc40 #define RTL_TAG_SIZE 8 +#define VLAN_TAG_SIZE 4 #define RTL_FRAME_TAG_ID 0x8899 // For RX and TX, an 8 byte header describing the frame to be moved to the Asic @@ -82,6 +85,7 @@ __xdata uint32_t ipv4_checksum; // Note that this is little endian __xdata uint8_t minPort; __xdata uint8_t maxPort; __xdata uint8_t nSFPPorts; +__xdata uint8_t cpuPort; __xdata uint8_t was_offline; @@ -754,14 +758,14 @@ void prepare_icmp_reply(void) tx_buf[34 + i] = ownIP[i]; // RTL Tag after dest-mac and source-mac: 8 Bytes for (uint8_t i = 0; i < 4; i++) - tx_buf[26 + i] = rx_buf[26 + i]; + tx_buf[26 + i] = rx_buf[18 + RTL_TAG_SIZE + VLAN_TAG_SIZE + i]; for (uint8_t i = 0; i < 4; i++) // DEST-IP - tx_buf[38 + i] = rx_buf[34 + i]; + tx_buf[38 + i] = rx_buf[26 + RTL_TAG_SIZE + VLAN_TAG_SIZE + i]; tx_buf[4] = tx_buf[25] = 84; // TCP length tx_buf[4] = 84 + ETHER_HEADER_SIZE; // Total Ethernet frame len tx_buf[5] = tx_buf[24] = 0; for (uint8_t i = 0; i < 60; i++) // Copy sequence number, id, timestamp and data over - tx_buf[RTL_FRAME_HEADER_SIZE + 38 + i] = rx_buf[RTL_TAG_SIZE + 38 + i]; + tx_buf[RTL_FRAME_HEADER_SIZE + 38 + i] = rx_buf[RTL_TAG_SIZE + VLAN_TAG_SIZE + 38 + i]; } @@ -822,7 +826,7 @@ void handle_rx(void) #endif } else if (rx_buf[0] == ownMAC[0] && rx_buf[1] == ownMAC[1] && rx_buf[2] == ownMAC[2] && rx_buf[3] == ownMAC[3] && rx_buf[4] == ownMAC[4] && rx_buf[5] == ownMAC[5]) { - if (rx_buf[31] == 0x01) { + if (rx_buf[23 + RTL_TAG_SIZE + VLAN_TAG_SIZE] == 0x01) { #ifdef RXTXDBG print_string("ICMP PING REQ\r\n"); #endif @@ -1413,6 +1417,7 @@ void rtl8372_init(void) uint16_t reg = 0x1238; // Port base register for the bits we set minPort = 0; maxPort = 8; + cpuPort = 9; nSFPPorts = 1; // FIXME: It could also be 2 if (!isRTL8373) { minPort = 3; @@ -1526,6 +1531,21 @@ void setup_i2c(void) } +uint8_t atoi_short(uint16_t *vlan, uint8_t idx) +{ + uint8_t err = 1; + *vlan = 0; + + while (sbuf[idx] >= '0' && sbuf[idx] <= '9') { + err = 0; + *vlan = (*vlan * 10) + sbuf[idx] - '0'; + idx++; + } + return err; +} + + + void bootloader(void) { ticks = 0; @@ -1628,7 +1648,6 @@ void bootloader(void) print_long(ticks); #endif // Print line and parse command into words - print_string("\r\n CMD: "); is_white = 1; uint8_t word = 0; cmd_words_b[0] = -1; @@ -1641,6 +1660,11 @@ void bootloader(void) is_white = 1; write_char(sbuf[line_ptr++]); line_ptr &= SBUF_SIZE - 1; + if (word >= N_WORDS - 1) { + print_string("\r\ntoo many arguments, truncated"); + line_ptr = l; // BUG: We should probably ignore the command + break; + } } cmd_words_b[word++] = line_ptr; cmd_words_b[word++] = -1; @@ -1734,6 +1758,43 @@ void bootloader(void) if (cmd_compare(0, "l2")) { port_l2_learned(); } + if (cmd_compare(0, "pvid") && cmd_words_b[1] > 0 && cmd_words_b[2] > 0) { + __xdata uint16_t pvid; + uint8_t port; + port = sbuf[cmd_words_b[1]] - '0'; + if (!atoi_short(&pvid, cmd_words_b[2])) + port_pvid_set(port, pvid); + } + if (cmd_compare(0, "vlan")) { + __xdata uint16_t vlan; + __xdata uint16_t members = 0; + __xdata uint16_t tagged = 0; + if (!atoi_short(&vlan, cmd_words_b[1])) { + print_short(vlan); + if (cmd_words_b[2] > 0 && sbuf[cmd_words_b[2]] == 'd') { + vlan_delete(vlan); + } else { + uint8_t w = 2; + while (cmd_words_b[w] > 0) { + uint8_t port; + if (sbuf[cmd_words_b[w]] >= '0' && sbuf[cmd_words_b[w]] <= '9') { + port = sbuf[cmd_words_b[w]] - '1'; + if (sbuf[cmd_words_b[w] + 1] >= '0' && sbuf[cmd_words_b[w] + 1] <= '9') { + port = (port + 1) * 10 + sbuf[cmd_words_b[w] + 1] - '1'; + if (sbuf[cmd_words_b[w] + 2] == 't') + tagged |= ((uint16_t)1) << port; + } else { + if (sbuf[cmd_words_b[w] + 1] == 't') + tagged |= ((uint16_t)1) << port; + } + members |= ((uint16_t)1) << port; + } + w++; + } + vlan_create(vlan, members, tagged); + } + } + } } print_string("\r\n> "); }