From 6e2701bc8cb792dfda3ba39d9b176f3835c8a99c Mon Sep 17 00:00:00 2001 From: DrDoof <8052722+DrDoof@users.noreply.github.com> Date: Wed, 23 Sep 2026 16:12:32 +0200 Subject: [PATCH] rtlplayground: receive on the NIC interrupt instead of polling the buffer The receive interrupt was wired up but never used: EX1 was enabled, the handler re-enabled itself and did nothing, and the mask bit for it in the NIC stayed clear, so idle() read the receive buffer register on every pass to find out whether a packet had arrived, and slept until the next tick when none had. A packet that arrived during that sleep waited for the tick: 2.2 ms on average, 5 ms at worst, for every frame the CPU handles. Now the handler latches rx_irq and masks itself, and idle() sleeps unless a receive, a link change, a console command or the tick is pending. On rx_irq the loop drains the buffer, clears the status bit and re-enables EX1, in that order: the NIC holds the line as long as the status bit is set, and the bit does not clear while the buffer still holds a frame, so a frame that lands between the last read and EX1 = 1 raises the interrupt again rather than getting lost. The other order, clearing before draining, left the line asserted and produced an interrupt on every pass. The mask is written after nic_setup(), which resets it. Measured on an RTL8373 board with the same counters in both images, clocked by the tick counter, traffic from a host behind an LACP trunk: ``` polling interrupt idle, register ops/s 788 79 25 pps, RTT avg/max 2.19/4.96 0.44/0.75 ms 500 pps, RTT avg/max 2.78/5.06 0.39/5.40 ms 500 pps, loss 0.08% 0.08% ``` The handler compiles to clr EX1 / setb rx_irq / reti. The timer interrupt now raises a tick flag too, so idle() no longer works out TICKS_DUE twice per pass, and cmd_available moves from xdata to a bit. The four wake-up flags sit in the low bits of one byte at 0x20, so the sleep test is a single read of that byte. tx_buf_empty deliberately stays out: it is 1 when there is nothing to do, so including it would keep the sleep test from ever firing. --- cmd_parser.c | 1 - cmd_parser.h | 2 +- rtl837x_regs.h | 4 ++++ rtlplayground.c | 44 +++++++++++++++++++++++--------------------- 4 files changed, 28 insertions(+), 23 deletions(-) diff --git a/cmd_parser.c b/cmd_parser.c index 5b4e1f34..dc151350 100644 --- a/cmd_parser.c +++ b/cmd_parser.c @@ -61,7 +61,6 @@ __xdata uint8_t hexvalue[4] = { 0 }; // Buffer for writing to flash 0x1fd000, copy to 0x1fe000 __xdata uint8_t cmd_buffer[CMD_BUF_SIZE]; -__xdata uint8_t cmd_available; __xdata char save_cmd; diff --git a/cmd_parser.h b/cmd_parser.h index 29d08c36..f96c72ae 100644 --- a/cmd_parser.h +++ b/cmd_parser.h @@ -6,7 +6,7 @@ #include "rtl837x_common.h" extern __xdata uint8_t cmd_buffer[CMD_BUF_SIZE]; -extern __xdata uint8_t cmd_available; +extern __bit __at(0x03) cmd_available; extern __xdata uint8_t err_status; /* Health instrumentation, built with HEALTH=1. Without it the hooks below diff --git a/rtl837x_regs.h b/rtl837x_regs.h index 6409043d..c8735a03 100644 --- a/rtl837x_regs.h +++ b/rtl837x_regs.h @@ -145,6 +145,10 @@ #define RTL837X_REG_RX_CTRL 0x785c #define RTL837X_REG_TX_CTRL 0x7860 #define RTL837X_REG_NIC_RX_BUFF_DATA 0x7874 +#define RTL837X_NIC_INT_STS 0x7854 +#define RTL837X_NIC_INT_MSK 0x7858 +#define NIC_INT_RXIS 0x02 +#define NIC_INT_RXIE 0x02 #define RTL837X_REG_CPU_RX_CURR_PKT 0x787c #define RTL837X_REG_NIC_TX_CURR_PKT 0x7884 #define RTL837X_REG_CPU_TX_CURR_PKT 0x7890 diff --git a/rtlplayground.c b/rtlplayground.c index 0ef86eed..42f695e4 100644 --- a/rtlplayground.c +++ b/rtlplayground.c @@ -137,13 +137,15 @@ __code const uint16_t bit_mask[16] = { __xdata uint8_t linkbits_last[4]; __xdata uint8_t linkbits_last_p89; -volatile __bit link_irq; -__xdata uint8_t idle_tick_last; +__data __at(0x20) volatile uint8_t evflags; +volatile __bit __at(0x00) link_irq; +volatile __bit __at(0x01) rx_irq; +volatile __bit __at(0x02) tick_pending; +__bit __at(0x03) cmd_available; // Last known state of the SFP detection/Loss of Signal pins __xdata bool button_last; __xdata uint8_t button_sec_counter_last; volatile __bit tx_buf_empty; -__bit rx_seen; struct eth_in { @@ -201,6 +203,7 @@ void isr_timer0(void) __interrupt(1) void isr_timer2(void) __interrupt(5) { ticks++; + tick_pending = 1; if (sleep_ticks > 0) sleep_ticks--; sec_counter++; @@ -511,9 +514,8 @@ void isr_ext0(void) __interrupt(0) */ void isr_ext1(void) __interrupt(2) { - // This flag should only be reset after all packets have been read EX1 = 0; - EX1 = 1; + rx_irq = 1; } /* @@ -997,14 +999,10 @@ void handle_rx(void) __xdata uint8_t budget = RX_BUDGET; while (budget--) { - if (rx_seen) { - rx_seen = 0; - } else { - // Check the amount of data available on the NIC/ASIC side - reg_read(RTL837X_REG_NIC_RX_BUFF_DATA); - if (!SFR_DATA_U16) - break; - } + // Check the amount of data available on the NIC/ASIC side + reg_read(RTL837X_REG_NIC_RX_BUFF_DATA); + if (!SFR_DATA_U16) + break; reg_read(RTL837X_REG_CPU_RX_CURR_PKT); uint16_t ring_ptr = SFR_DATA_U16; ring_ptr <<= 3; @@ -1284,13 +1282,11 @@ static void handle_tick(void) void idle(void) { - reg_read(RTL837X_REG_NIC_RX_BUFF_DATA); - rx_seen = SFR_DATA_U16 != 0; - if (!rx_seen) + if (!evflags) PCON |= 1; health_loop_start(); - if (TICKS_DUE(idle_tick_last, 1)) { - idle_tick_last = (uint8_t)ticks; + if (tick_pending) { + tick_pending = 0; handle_tick(); } if (link_irq) { @@ -1300,7 +1296,12 @@ void idle(void) check_links(); } health_phase(HEALTH_PH_LINK); - handle_rx(); + if (rx_irq) { + rx_irq = 0; + handle_rx(); + REG_SET(RTL837X_NIC_INT_STS, NIC_INT_RXIS); + EX1 = 1; + } health_phase(HEALTH_PH_RX); if (cmd_available) { cmd_available = 0; @@ -1343,7 +1344,8 @@ void setup_external_irqs(void) IT0 = 1; // External IRQ 0 on falling edge (link change) EX0 = 1; - EX1 = 1; // External IRQ 1 enable + rx_irq = 1; + EX1 = 1; // External IRQ 1: the NIC has received a packet EX2 = 1; // External IRQ 2 enable: bit EIE.0 EX3 = 1; // External IRQ 3 enable: bit EIE.1 PX3 = 1; // Set EIP.1 = 1: External IRQ 3 set to high priority @@ -1625,7 +1627,6 @@ void main(void) { ticks = 0; stp_tick_last = (uint8_t)ticks; - idle_tick_last = (uint8_t)ticks; sfp_tick_last = (uint8_t)ticks; dhcp_state.state = DHCP_OFF; sbuf_ptr = 0; @@ -1770,6 +1771,7 @@ void main(void) stp_enabled = 0; stp_defaults(); /* 802.1D/w default config before any "stp ..." replay */ nic_setup(); + REG_SET(RTL837X_NIC_INT_MSK, NIC_INT_RXIE); vlan_setup(); port_l2_setup(); igmp_setup();