From 98369c37fe973ac48fb3111228e4c3091aff516e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Silva?= Date: Sat, 17 Oct 2026 18:40:37 +0100 Subject: [PATCH] nic: Fix incorrect wrap handling of RX circular buffer nic: move other nic functions into the dedicated nic files uip: alloc buffer multiple of 8 bytes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: João Silva --- Makefile | 1 + boot.c | 2 +- doc/CpuPort.md | 15 +++ rtl837x_common.h | 2 +- rtl837x_igmp.c | 2 +- rtl837x_lacp.c | 2 +- rtl837x_nic.c | 90 ++++++++++++++ rtl837x_nic.h | 20 ++++ rtl837x_regs.h | 3 + rtl837x_stp.c | 2 +- rtlplayground.c | 118 ++---------------- test/Makefile | 6 + test/test_httpd_tx.c | 2 +- test/test_lacp.c | 2 +- test/test_nic_rx.c | 277 +++++++++++++++++++++++++++++++++++++++++++ uip/uip-conf.h | 4 + uip/uip.c | 2 +- uip/uipopt.h | 2 +- 18 files changed, 435 insertions(+), 117 deletions(-) create mode 100644 rtl837x_nic.c create mode 100644 rtl837x_nic.h create mode 100644 test/test_nic_rx.c diff --git a/Makefile b/Makefile index 00bbf7b9..c95f12af 100644 --- a/Makefile +++ b/Makefile @@ -103,6 +103,7 @@ SRCS += \ rtl837x_flash.c \ rtl837x_igmp.c \ rtl837x_init.c \ + rtl837x_nic.c \ rtl837x_leds.c \ rtl837x_leds_dump.c \ rtl837x_phy.c \ diff --git a/boot.c b/boot.c index 81a37fab..adcb192b 100644 --- a/boot.c +++ b/boot.c @@ -257,7 +257,7 @@ void nic_setup(void) __banked // This sets the size of the RX buffer, the filling level is in 0x7874 // R7848-000004ff - REG_SET(RTL837X_REG_NIC_RXBUFF_RX, 0x4ff); + REG_SET(RTL837X_REG_NIC_RXBUFF_RX, RTL837X_NIC_RX_LAST_SLOT); // R7844-000007fe REG_SET(RTL837X_REG_NIC_BUFFSIZE_TX, 0x7fe); diff --git a/doc/CpuPort.md b/doc/CpuPort.md index c10d0a15..652b1cbb 100644 --- a/doc/CpuPort.md +++ b/doc/CpuPort.md @@ -34,6 +34,21 @@ Next, transfer the actual packet over by repeating the SFR function with a pointer to the frame on the ASIC directly after the frame header and a length as given by the length in the frame header + 7, again divided by 8. +The RX ring's inclusive last slot is configured as `0x4ff`, giving a byte +range of `0x0000` through `0x27ff`. If the descriptor occupies the last slot +at `0x27f8`, its payload starts at `0x0000`, not `0x2800`. The RX copy code +normalizes that starting address before DMA. It relies on the hardware to +wrap transfers that start inside the ring and cross its end, as observed +on SWTGW218AS. Programming an initial address of `0x2800` does not wrap +and can instead read zeros or stale TX data outside the RX ring. + +Frame lengths are checked against `UIP_CONF_BUFFER_SIZE` before copying. +DMA works in whole eight-byte units. `UIP_CONF_BUFFER_ALLOC_SIZE` rounds +the backing storage up to the next complete word: 1,560 bytes for the +1,556-byte logical frame limit. This padding allows a maximum-size frame +to be transferred directly without a separate tail copy. The logical +frame limit and advertised MSS are unchanged. + 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 diff --git a/rtl837x_common.h b/rtl837x_common.h index b0589ca5..4d4047f9 100644 --- a/rtl837x_common.h +++ b/rtl837x_common.h @@ -142,7 +142,7 @@ extern __xdata bool stp_enabled; * advertises it as the System Name TLV) read it from here. */ extern __xdata char hostname[24]; -extern __xdata uint8_t uip_buf[UIP_CONF_BUFFER_SIZE+2]; +extern __xdata uint8_t uip_buf[UIP_CONF_BUFFER_ALLOC_SIZE]; extern __xdata struct uip_eth_addr uip_ethaddr; // Headers for calls in the common code area (HOME/BANK0) diff --git a/rtl837x_igmp.c b/rtl837x_igmp.c index b5513b4d..35940446 100644 --- a/rtl837x_igmp.c +++ b/rtl837x_igmp.c @@ -28,7 +28,7 @@ extern __xdata uint8_t igmpEnabled; extern __xdata uint8_t cpuPort; extern __xdata uint8_t sfr_data[4]; extern __xdata struct machine_runtime machine_detected; -extern __xdata uint8_t uip_buf[UIP_CONF_BUFFER_SIZE + 2]; +extern __xdata uint8_t uip_buf[UIP_CONF_BUFFER_ALLOC_SIZE]; __xdata uint16_t idx; #define IGMP_IP_HDR offsetof(struct igmp_pkt, hlen) diff --git a/rtl837x_lacp.c b/rtl837x_lacp.c index c1bf4dc2..2573bc34 100644 --- a/rtl837x_lacp.c +++ b/rtl837x_lacp.c @@ -21,7 +21,7 @@ extern __code const struct machine machine; extern __xdata struct machine_runtime machine_detected; extern __xdata uint16_t management_vlan; extern __xdata struct uip_eth_addr uip_ethaddr; -extern __xdata uint8_t uip_buf[UIP_CONF_BUFFER_SIZE + 2]; +extern __xdata uint8_t uip_buf[UIP_CONF_BUFFER_ALLOC_SIZE]; /* 20-byte Actor/Partner information body (802.3ad 43.4.2.2), network byte order */ struct lacp_info { diff --git a/rtl837x_nic.c b/rtl837x_nic.c new file mode 100644 index 00000000..af755628 --- /dev/null +++ b/rtl837x_nic.c @@ -0,0 +1,90 @@ +#include "rtl837x_common.h" +#include "rtl837x_regs.h" +#include "rtl837x_sfr.h" +#include "rtl837x_nic.h" + +extern __xdata uint8_t rx_headers[16]; + + +#ifndef RTLP_HOST_TEST +/* One RX DMA operation, starting inside the configured ring. The hardware + * wraps transfers that cross its end; the initial address must be normalized. + * Host tests supply a simulated DMA engine for this hardware boundary. */ +bool nic_rx_dma(__xdata uint8_t *buffer, uint16_t ring_ptr, uint8_t words) __reentrant +{ + uint16_t guard = 0; + + SFR_NIC_DATA_U16LE = (uint16_t)buffer; + SFR_NIC_RING_U16LE = ring_ptr; + SFR_NIC_CTRL = words; + while (SFR_NIC_CTRL != 0) { + if (++guard == 0) { + print_string("NIC: RX transfer did not complete\n"); + return false; + } + } + return true; +} +#endif + +static bool nic_rx_copy(__xdata uint8_t *buffer, uint16_t ring_ptr, uint8_t words) __reentrant +{ + if (ring_ptr > RTL837X_NIC_RX_RING_BYTES || (ring_ptr & 7)) + return false; + if (ring_ptr == RTL837X_NIC_RX_RING_BYTES) + ring_ptr = 0; + + return nic_rx_dma(buffer, ring_ptr, words); +} + + +bool nic_rx_header(uint16_t ring_ptr) __reentrant +{ + if (ring_ptr >= RTL837X_NIC_RX_RING_BYTES) + return false; + return nic_rx_copy(rx_headers, ring_ptr, 1); +} + + +bool nic_rx_packet(__xdata uint8_t *buffer, uint16_t ring_ptr) __reentrant +{ + uint16_t len = ((uint16_t)rx_headers[5] << 8) | rx_headers[4]; + + if (!len || len > UIP_CONF_BUFFER_SIZE) + return false; + return nic_rx_copy(buffer, ring_ptr, (len + 7) >> 3); +} + + +/* + * Transfers data in XMEM to the ASIC for transmission by the NIC + */ +void nic_tx_packet(__xdata uint8_t *buffer, uint16_t len, uint16_t ring_ptr) +{ + uint16_t guard = 0; + + SFR_NIC_DATA_U16LE = (uint16_t)(uintptr_t)buffer; + +#ifdef RXTXDBG + print_string("TX: \n"); + for (uint8_t i = 0; i < 100; i++) { + print_byte(uip_buf[i]); + write_char(' '); + } + write_char('\n'); +#endif + + ring_ptr <<= 3; + ring_ptr |= 0x8000; + SFR_NIC_RING_U16LE = ring_ptr; + + len += 0xf; + len >>= 3; + SFR_NIC_CTRL = len; + while (SFR_NIC_CTRL != 0) { + if (++guard == 0) { + print_string("NIC: TX transfer did not complete\n"); + return; + } + } +} diff --git a/rtl837x_nic.h b/rtl837x_nic.h new file mode 100644 index 00000000..2eb54d21 --- /dev/null +++ b/rtl837x_nic.h @@ -0,0 +1,20 @@ +#ifndef RTL837X_NIC_H +#define RTL837X_NIC_H + +#include +#include + +// #define RXTXDBG 1 + +bool nic_rx_header(uint16_t ring_ptr) __reentrant; +bool nic_rx_packet(__xdata uint8_t *buffer, uint16_t ring_ptr) __reentrant; + +/* buffer starts at the eight-byte NIC descriptor; len excludes that + * descriptor. ring_ptr is the ASIC TX position in eight-byte slots. */ +void nic_tx_packet(__xdata uint8_t *buffer, uint16_t len, uint16_t ring_ptr); + +/* RX DMA in eight-byte units. ring_ptr must start inside the configured + * RX ring; the hardware wraps transfers that cross its end. */ +bool nic_rx_dma(__xdata uint8_t *buffer, uint16_t ring_ptr, uint8_t words) __reentrant; + +#endif diff --git a/rtl837x_regs.h b/rtl837x_regs.h index 93eb7f44..5527039b 100644 --- a/rtl837x_regs.h +++ b/rtl837x_regs.h @@ -147,6 +147,9 @@ */ #define RTL837X_REG_NIC_BUFFSIZE_TX 0x7844 #define RTL837X_REG_NIC_RXBUFF_RX 0x7848 +/* RX ring: inclusive final eight-byte slot, as programmed by nic_setup(). */ +#define RTL837X_NIC_RX_LAST_SLOT 0x04ff +#define RTL837X_NIC_RX_RING_BYTES ((RTL837X_NIC_RX_LAST_SLOT + 1U) * 8U) #define RTL837X_REG_NIC_RXCMD 0x784c #define RTL837X_REG_NIC_TXCMD 0x7850 #define RTL837X_REG_RX_CTRL 0x785c diff --git a/rtl837x_stp.c b/rtl837x_stp.c index ff451f2d..bc810d96 100644 --- a/rtl837x_stp.c +++ b/rtl837x_stp.c @@ -30,7 +30,7 @@ extern __xdata struct machine_runtime machine_detected; extern __xdata struct uip_eth_addr uip_ethaddr; -extern __xdata uint8_t uip_buf[UIP_CONF_BUFFER_SIZE + 2]; +extern __xdata uint8_t uip_buf[UIP_CONF_BUFFER_ALLOC_SIZE]; extern __xdata uint8_t cmd_buffer[CMD_BUF_SIZE]; extern __xdata uint8_t cmd_words_len; diff --git a/rtlplayground.c b/rtlplayground.c index 657dc77d..0afa93c8 100644 --- a/rtlplayground.c +++ b/rtlplayground.c @@ -2,7 +2,6 @@ #include // #define REGDBG 1 -// #define RXTXDBG 1 #include "rtl837x_sfr.h" #include "rtl837x_regs.h" @@ -12,6 +11,7 @@ #include "rtl837x_phy.h" #include "rtl837x_port.h" #include "rtl837x_trap.h" +#include "rtl837x_nic.h" #include "rtl837x_stp.h" #include "rtl837x_lacp.h" #include "rtl837x_igmp.h" @@ -123,8 +123,8 @@ __code const uint8_t * __code const hex = "0123456789abcdef"; __xdata uint8_t flash_buf[FLASH_BUF_SIZE]; // NIC buffers for packet RX/TX -__xdata uint8_t rx_headers[16]; // Packet header(s) on RX -__xdata uint8_t uip_buf[UIP_CONF_BUFFER_SIZE+2]; +__xdata uint8_t rx_headers[16]; // RX descriptor +__xdata uint8_t uip_buf[UIP_CONF_BUFFER_ALLOC_SIZE]; __xdata uint16_t rx_packet_vlan; __xdata uint16_t management_vlan; @@ -714,108 +714,6 @@ void get_random_32(void) } -/* - * Transfer Network Interface RX data from the ASIC to the 8051 XMEM - * data will be stored in the rx_header structure - * len is the length of data to be transferred - */ -bool nic_rx_header(uint16_t ring_ptr) -{ - uint16_t buffer = (uint16_t) &rx_headers[0]; - uint16_t guard = 0; - - SFR_NIC_DATA_U16LE = buffer; - SFR_NIC_RING_U16LE = ring_ptr; - SFR_NIC_CTRL = 1; - while (SFR_NIC_CTRL != 0) { - if (++guard == 0) { - print_string("NIC: RX header transfer did not complete\n"); - return false; - } - } - return true; -} - - -/* - * Transfer Network Interface RX data from the ASIC to the 8051 XMEM - * the description of the packet must be in the rx_headers data structure - * data will be returned in the xmem buffer points to - * ring_ptr is the current position of the RX Ring on the ASIC side - */ -bool nic_rx_packet(uint16_t buffer, uint16_t ring_ptr) -{ - uint16_t guard = 0; - - SFR_NIC_DATA_U16LE = buffer; - SFR_NIC_RING_U16LE = ring_ptr; - - uint16_t len = (((uint16_t)rx_headers[5]) << 8) | rx_headers[4]; - len += 7; - len >>= 3; -#ifdef RXTXDBG - print_string(" len: "); - print_short(len); -#endif - SFR_NIC_CTRL = len; - while (SFR_NIC_CTRL != 0) { - if (++guard == 0) { - print_string("NIC: RX transfer did not complete\n"); - return false; - } - } - return true; -} - - -/* - * Transfers data in XMEM to the ASIC for transmission by the nic - */ -void nic_tx_packet(uint16_t ring_ptr) -{ - uint16_t len; - uint16_t guard = 0; - - /* A frame that got a dot1Q tag was shifted forward over its padding, so it - * starts at uip_buf and carries the q_frame layout. One that did not keeps - * the padding in front and the nonq_frame layout, so the padding is skipped. - */ - if (frame_tagged) { - SFR_NIC_DATA_U16LE = (uint16_t) uip_buf; - len = FRAME_Q->len; - /* - (__xdata struct rtl_dot1q_frame *)uip_buf -#define FRAME (((__xdata struct rtl_dot1q_frame *)&uip_buf[0]).nonq_frame)*/ - } else { - SFR_NIC_DATA_U16LE = (uint16_t) uip_buf + VLAN_TAG_SIZE; - len = FRAME->len; - } - -#ifdef RXTXDBG - print_string("TX: \n"); - for (uint8_t i = 0; i < 100; i++) { - print_byte(uip_buf[i]); - write_char(' '); - } - write_char('\n'); -#endif - - ring_ptr <<= 3; - ring_ptr |= 0x8000; - SFR_NIC_RING_U16LE = ring_ptr; - - len += 0xf; - len >>= 3; - SFR_NIC_CTRL = len; - while (SFR_NIC_CTRL != 0) { - if (++guard == 0) { - print_string("NIC: TX transfer did not complete\n"); - return; - } - } -} - - /* Read flash using the MMIO capabilities of the DW8051 core * Bank is < 0x3f and is the MSB * addr gives the address in the bank @@ -999,8 +897,12 @@ void tcpip_output(void) uint16_t ring_ptr = ((uint16_t)sfr_data[2]) << 8; ring_ptr |= sfr_data[3]; - // Move data over from xmem buffer to ASIC side using DMA - nic_tx_packet(ring_ptr); + // Move data over from xmem buffer to ASIC side using DMA. A VLAN-tagged + // frame starts at uip_buf; an untagged frame retains four bytes of padding. + if (frame_tagged) + nic_tx_packet(uip_buf, FRAME_Q->len, ring_ptr); + else + nic_tx_packet(uip_buf + VLAN_TAG_SIZE, FRAME->len, ring_ptr); // New position of the ring-pointer on the NIC-side indicates number of bytes transmitted reg_read_m(RTL837X_REG_NIC_TX_CURR_PKT); @@ -1037,7 +939,7 @@ void handle_rx(void) write_char(' '); } #endif - if (!nic_rx_packet((uint16_t) &uip_buf[0], ring_ptr + 8)) { + if (!nic_rx_packet(uip_buf, ring_ptr + 8)) { REG_SET(RTL837X_REG_NIC_RXCMD, 1); return; } diff --git a/test/Makefile b/test/Makefile index cdb27355..5e4dd292 100644 --- a/test/Makefile +++ b/test/Makefile @@ -29,6 +29,7 @@ TESTS = $(BUILDDIR)/test_cmd_editor \ $(BUILDDIR)/test_page_json \ $(BUILDDIR)/test_httpd_tx \ $(BUILDDIR)/test_tick_gate \ + $(BUILDDIR)/test_nic_rx \ $(BUILDDIR)/test_lacp # The table tests link the register mock and one environment file; stub/ @@ -52,6 +53,11 @@ $(BUILDDIR)/test_port_tables: test_port_tables.c $(TABLE_ENV) hw_mock.h | $(BUIL $(BUILDDIR)/test_tick_gate: test_tick_gate.c support.c $(SRCROOT)/cmd_editor.c | $(BUILDDIR) $(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS) +# Exercise the production RX ring reader; only the hardware DMA edge is mocked. +# Discard unused TX functions so this RX fixture needs no TX register mocks. +$(BUILDDIR)/test_nic_rx: test_nic_rx.c $(SRCROOT)/rtl837x_nic.c $(SRCROOT)/rtl837x_nic.h | $(BUILDDIR) + $(CC) $(CFLAGS) -ffunction-sections -fdata-sections $(filter %.c,$^) -o $@ $(LDFLAGS) -Wl,--gc-sections + $(BUILDDIR)/test_page_json: test_page_json.c $(TABLE_ENV) hw_mock.h | $(BUILDDIR) $(CC) $(CFLAGS) $(filter %.c,$^) -o $@ $(LDFLAGS) diff --git a/test/test_httpd_tx.c b/test/test_httpd_tx.c index 3f056030..9b31141d 100644 --- a/test/test_httpd_tx.c +++ b/test/test_httpd_tx.c @@ -112,7 +112,7 @@ void read_reg_timer(uint32_t *tmr) { *tmr = 0; } /* uip-conf.h asks uIP not to define the packet buffer: on the switch it lives * at a fixed XDATA address, so the bench provides the storage itself. */ -u8_t uip_buf[UIP_BUFSIZE + 2]; +u8_t uip_buf[UIP_CONF_BUFFER_ALLOC_SIZE]; uint16_t strlen_x(const char *s) { return (uint16_t)strlen(s); } diff --git a/test/test_lacp.c b/test/test_lacp.c index dbadea04..160c773e 100644 --- a/test/test_lacp.c +++ b/test/test_lacp.c @@ -11,7 +11,7 @@ /* ---------- platform mocks ---------- */ -uint8_t uip_buf[UIP_CONF_BUFFER_SIZE + 2]; +uint8_t uip_buf[UIP_CONF_BUFFER_ALLOC_SIZE]; uint16_t uip_len; struct uip_eth_addr uip_ethaddr = { .addr = {0x02,0x11,0x22,0x33,0x44,0x55} }; const struct machine machine = { .min_port = 0, .max_port = 7, diff --git a/test/test_nic_rx.c b/test/test_nic_rx.c new file mode 100644 index 00000000..4ad36310 --- /dev/null +++ b/test/test_nic_rx.c @@ -0,0 +1,277 @@ +#include +#include +#include +#include + +#include "rtl837x_common.h" +#include "rtl837x_regs.h" +#include "rtl837x_nic.h" + +uint8_t rx_headers[16]; + +/* Model the DMA behavior reported on SWTGW218AS: a transfer starting inside + * the RX ring wraps at its end. An initial address outside the ring instead + * reads flat ASIC memory, including stale TX bytes at 0x2800. */ +static uint8_t asic[0x8000]; +static struct { + uint8_t before[16]; + uint8_t data[UIP_CONF_BUFFER_ALLOC_SIZE]; + uint8_t after[16]; +} destination; + +struct dma_request { + uint8_t *buffer; + uint16_t ring; + uint8_t words; +}; +static struct dma_request requests[8]; +static unsigned request_count, fail_request; +static int failures; + +static void check(bool condition, const char *what) +{ + if (!condition) { + printf(" FAIL: %s\n", what); + failures++; + } +} + +bool nic_rx_dma(uint8_t *buffer, uint16_t ring, uint8_t words) +{ + size_t bytes = (size_t)words * 8; + unsigned call = ++request_count; + + if (call <= sizeof(requests) / sizeof(requests[0])) + requests[call - 1] = (struct dma_request){ buffer, ring, words }; + if (call == fail_request || !words || (size_t)ring + bytes > sizeof(asic)) + return false; + if (ring < RTL837X_NIC_RX_RING_BYTES) { + for (size_t i = 0; i < bytes; i++) + buffer[i] = asic[((size_t)ring + i) % RTL837X_NIC_RX_RING_BYTES]; + } else { + memcpy(buffer, asic + ring, bytes); + } + return true; +} + +static void fixture_reset(void) +{ + memset(asic, 0xd7, sizeof(asic)); + memset(&destination, 0xa5, sizeof(destination)); + memset(rx_headers, 0x6c, sizeof(rx_headers)); + memset(requests, 0, sizeof(requests)); + request_count = 0; + fail_request = 0; +} + +static bool filled(const uint8_t *data, size_t len, uint8_t value) +{ + while (len--) + if (*data++ != value) + return false; + return true; +} + +static bool guards_intact(void) +{ + return filled(destination.before, sizeof(destination.before), 0xa5) + && filled(destination.after, sizeof(destination.after), 0xa5); +} + +static bool requests_start_in_rx_ring(void) +{ + for (unsigned i = 0; i < request_count; i++) { + if (i >= sizeof(requests) / sizeof(requests[0]) || !requests[i].words + || (requests[i].ring & 7) + || requests[i].ring >= RTL837X_NIC_RX_RING_BYTES) + return false; + } + return true; +} + +static void ring_write(uint16_t offset, const uint8_t *data, size_t len) +{ + for (size_t i = 0; i < len; i++) + asic[((size_t)offset + i) % RTL837X_NIC_RX_RING_BYTES] = data[i]; +} + +static void put_descriptor(uint16_t offset, uint16_t len) +{ + uint8_t descriptor[8] = { 0x03, 0x00, 0xb0, 0xc8, len, len >> 8, 0, 0 }; + ring_write(offset, descriptor, sizeof(descriptor)); +} + +static void expect_descriptor(uint16_t len) +{ + uint8_t descriptor[8] = { 0x03, 0x00, 0xb0, 0xc8, len, len >> 8, 0, 0 }; + check(!memcmp(rx_headers, descriptor, sizeof(descriptor)), "the first eight descriptor bytes stay intact"); +} + +static void pattern(uint8_t *data, size_t len) +{ + for (size_t i = 0; i < len; i++) + data[i] = (uint8_t)(i * 37 + (i >> 8) + 0x13); +} + +static void t_last_descriptor(void) +{ + static const uint8_t root_bpdu[68] = { + 0x01, 0x80, 0xc2, 0, 0, 0, 0xd0, 0xea, 0x11, 0x12, 0x34, 0x56, + 0x88, 0x99, 0x04, 0, 0, 0, 0, 8, + 0, 0x27, 0x42, 0x42, 3, 0, 0, 2, 2, 0x3c, + 0x10, 0, 0xd0, 0xea, 0x11, 0x12, 0x34, 0x56, 0, 0, 0, 0, + 0x10, 0, 0xd0, 0xea, 0x11, 0x12, 0x34, 0x56, 0x80, 0x0a, + 0, 0, 0x14, 0, 2, 0, 0x0f, 0, 0 + }; + uint8_t stale_tx[72] = { 0 }; + uint16_t last = RTL837X_NIC_RX_RING_BYTES - 8; + + printf("[test] the last RX descriptor wraps its BPDU payload before DMA\n"); + fixture_reset(); + put_descriptor(last, sizeof(root_bpdu)); + ring_write(0, root_bpdu, sizeof(root_bpdu)); + memcpy(stale_tx, root_bpdu, sizeof(root_bpdu)); + stale_tx[17] = 0x20; + stale_tx[19] = 1; /* retained TX destination mask, not ingress port 8 */ + memcpy(asic + RTL837X_NIC_RX_RING_BYTES, stale_tx, sizeof(stale_tx)); + check(nic_rx_header(last), "the final descriptor is readable"); + expect_descriptor(sizeof(root_bpdu)); + check(nic_rx_packet(destination.data, last + 8), "a payload address at the RX ring limit wraps to zero"); + check(!memcmp(destination.data, root_bpdu, sizeof(root_bpdu)), "the complete upstream BPDU is read instead of stale TX bytes"); + check(destination.data[19] == 8, "the upstream ingress tag survives the ring boundary"); + check(request_count == 2 && requests[0].ring == last && requests[0].words == 1 + && requests[1].ring == 0 && requests[1].words == 9, + "descriptor and rounded payload DMA both start in the RX ring"); + check(requests_start_in_rx_ring() && guards_intact(), "DMA starts inside the RX ring and preserves destination guards"); + expect_descriptor(sizeof(root_bpdu)); +} + +static void t_payload_layouts(void) +{ + static const struct { + uint16_t descriptor, len; + } cases[] = { + { 0x0100, 8 }, { 0x0100, 16 }, { 0x0100, 64 }, + { 0x0100, 68 }, { 0x0100, 72 }, + { RTL837X_NIC_RX_RING_BYTES - 16, 8 }, + { RTL837X_NIC_RX_RING_BYTES - 16, 16 }, + { RTL837X_NIC_RX_RING_BYTES - 16, 68 }, + { RTL837X_NIC_RX_RING_BYTES - 72, 64 }, + { RTL837X_NIC_RX_RING_BYTES - 48, 68 } + }; + uint8_t frame[80]; + + printf("[test] payload DMA handles ordinary, exact-boundary, and crossing frames\n"); + pattern(frame, sizeof(frame)); + for (unsigned i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) { + size_t rounded = (cases[i].len + 7u) & ~7u; + fixture_reset(); + put_descriptor(cases[i].descriptor, cases[i].len); + ring_write(cases[i].descriptor + 8, frame, rounded); + check(nic_rx_header(cases[i].descriptor), "the descriptor transfer completes"); + check(nic_rx_packet(destination.data, cases[i].descriptor + 8), "the payload transfer completes"); + check(!memcmp(destination.data, frame, rounded), "every payload and permitted padding byte reaches the correct destination offset"); + check(filled(destination.data + rounded, sizeof(destination.data) - rounded, 0xa5), "DMA does not write beyond the rounded frame"); + check(request_count == 2, "the payload uses one DMA request even when crossing the ring end"); + check(requests_start_in_rx_ring() && guards_intact(), "crossing DMA starts inside the RX ring and preserves destination guards"); + expect_descriptor(cases[i].len); + } +} + +static void t_maximum_frames(void) +{ + uint8_t frame[(UIP_CONF_BUFFER_SIZE + 7u) & ~7u]; + static const uint16_t lengths[] = { 1, 7, 1549, 1550, 1551, 1552, 1553, 1554, 1555, 1556 }; + + printf("[test] rounded maximum frames fit without overwriting their descriptor\n"); + pattern(frame, sizeof(frame)); + for (unsigned layout = 0; layout < 3; layout++) { + uint16_t descriptor = layout == 0 ? 0x0100 + : RTL837X_NIC_RX_RING_BYTES - (layout == 1 ? 16 : 8); + for (unsigned i = 0; i < sizeof(lengths) / sizeof(lengths[0]); i++) { + uint16_t len = lengths[i]; + size_t rounded = (len + 7u) & ~7u; + fixture_reset(); + put_descriptor(descriptor, len); + ring_write(descriptor + 8, frame, rounded); + check(nic_rx_header(descriptor), "the maximum-frame descriptor transfer completes"); + check(nic_rx_packet(destination.data, descriptor + 8), "all supported frame lengths fit the destination"); + check(!memcmp(destination.data, frame, rounded), "the full rounded frame is preserved"); + check(filled(destination.data + rounded, sizeof(destination.data) - rounded, 0xa5), "rounded DMA writes no bytes beyond the available destination"); + check(request_count == 2, "maximum frames use a single payload DMA request"); + check(requests_start_in_rx_ring() && guards_intact(), "rounded DMA starts inside the RX ring and preserves destination guards"); + expect_descriptor(len); + } + } +} + +static void t_invalid_frames(void) +{ + static const uint16_t invalid_lengths[] = { 0, UIP_CONF_BUFFER_SIZE + 1, 0xffff }; + static const uint16_t invalid_pointers[] = { 1, 7, RTL837X_NIC_RX_RING_BYTES + 8, 0xfff8 }; + + printf("[test] invalid lengths and DMA addresses are rejected before transfer\n"); + for (unsigned i = 0; i < sizeof(invalid_lengths) / sizeof(invalid_lengths[0]); i++) { + fixture_reset(); + rx_headers[4] = invalid_lengths[i]; + rx_headers[5] = invalid_lengths[i] >> 8; + check(!nic_rx_packet(destination.data, 0x0100), "unsupported or overflowing lengths are rejected"); + check(!request_count && filled(destination.data, sizeof(destination.data), 0xa5) && guards_intact(), + "invalid frame lengths cause no DMA or destination changes"); + } + for (unsigned i = 0; i < sizeof(invalid_pointers) / sizeof(invalid_pointers[0]); i++) { + fixture_reset(); + rx_headers[4] = 68; + rx_headers[5] = 0; + check(!nic_rx_header(invalid_pointers[i]), "an invalid descriptor address is rejected"); + check(!nic_rx_packet(destination.data, invalid_pointers[i]), "an invalid payload address is rejected"); + check(!request_count && guards_intact(), "invalid addresses never reach DMA"); + } + fixture_reset(); + check(!nic_rx_header(RTL837X_NIC_RX_RING_BYTES) && !request_count, + "a descriptor at the ring limit is rejected rather than wrapped"); +} + +static void t_dma_failure(void) +{ + uint8_t frame[UIP_CONF_BUFFER_ALLOC_SIZE]; + static const uint16_t lengths[] = { 68, UIP_CONF_BUFFER_SIZE }; + + printf("[test] failed descriptor and single-payload DMA transfers propagate failure\n"); + fixture_reset(); + fail_request = 1; + check(!nic_rx_header(0x0100) && request_count == 1, "descriptor DMA failure reaches the caller"); + pattern(frame, sizeof(frame)); + for (unsigned layout = 0; layout < 3; layout++) { + uint16_t descriptor = layout == 0 ? 0x0100 + : RTL837X_NIC_RX_RING_BYTES - (layout == 1 ? 16 : 8); + for (unsigned i = 0; i < sizeof(lengths) / sizeof(lengths[0]); i++) { + uint16_t len = lengths[i]; + fixture_reset(); + put_descriptor(descriptor, len); + ring_write(descriptor + 8, frame, (len + 7u) & ~7u); + check(nic_rx_header(descriptor), "the descriptor precedes the injected payload failure"); + fail_request = 2; + check(!nic_rx_packet(destination.data, descriptor + 8), "payload DMA failure reaches the caller"); + check(request_count == fail_request, "a failed payload DMA is not retried or split"); + check(filled(destination.data, sizeof(destination.data), 0xa5) && guards_intact(), + "an injected failure before DMA preserves the destination and guards"); + expect_descriptor(len); + } + } +} + +int main(void) +{ + t_last_descriptor(); + t_payload_layouts(); + t_maximum_frames(); + t_invalid_frames(); + t_dma_failure(); + if (failures) { + printf("%d NIC RX check(s) failed\n", failures); + return 1; + } + puts("all NIC RX checks passed"); + return 0; +} diff --git a/uip/uip-conf.h b/uip/uip-conf.h index 9e87fda2..f93e6bd7 100644 --- a/uip/uip-conf.h +++ b/uip/uip-conf.h @@ -132,6 +132,10 @@ typedef unsigned short uip_stats_t; */ #define UIP_CONF_BUFFER_SIZE 1556 +/* Backing storage includes padding for the NIC's eight-byte DMA transfers. + * Keep UIP_CONF_BUFFER_SIZE as the logical frame/MSS limit. */ +#define UIP_CONF_BUFFER_ALLOC_SIZE ((UIP_CONF_BUFFER_SIZE + 7U) & ~7U) + /** * Bytes of the buffer kept out of the advertised MSS. * diff --git a/uip/uip.c b/uip/uip.c index 774067da..c8d0f20b 100644 --- a/uip/uip.c +++ b/uip/uip.c @@ -136,7 +136,7 @@ static __code const uip_ipaddr_t all_zeroes_addr = #endif /* UIP_CONF_IPV6 */ #ifndef UIP_CONF_EXTERNAL_BUFFER -u8_t uip_buf[UIP_BUFSIZE + 2]; /* The packet buffer that contains +u8_t uip_buf[UIP_CONF_BUFFER_ALLOC_SIZE]; /* The packet buffer that contains incoming packets. */ #endif /* UIP_CONF_EXTERNAL_BUFFER */ diff --git a/uip/uipopt.h b/uip/uipopt.h index 620d6054..84ea318c 100644 --- a/uip/uipopt.h +++ b/uip/uipopt.h @@ -419,7 +419,7 @@ #endif /* UIP_CONF_BUFFER_EXTRA */ -extern __xdata uint8_t uip_buf[UIP_CONF_BUFFER_SIZE+2]; +extern __xdata uint8_t uip_buf[UIP_CONF_BUFFER_ALLOC_SIZE]; /** * Determines if statistics support should be compiled in.