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.
This commit is contained in:
DrDoof authored and d00f committed 2026-09-23 16:12:32 +02:00
1 parent ac3b4b17ae
commit 6e2701bc8c
4 files changed
+23 -18

No files matched your search

-1
View File
@@ -61,7 +61,6 @@ __xdata uint8_t hexvalue[4] = { 0 };
// Buffer for writing to flash 0x1fd000, copy to 0x1fe000 // Buffer for writing to flash 0x1fd000, copy to 0x1fe000
__xdata uint8_t cmd_buffer[CMD_BUF_SIZE]; __xdata uint8_t cmd_buffer[CMD_BUF_SIZE];
__xdata uint8_t cmd_available;
__xdata char save_cmd; __xdata char save_cmd;
+1 -1
View File
@@ -6,7 +6,7 @@
#include "rtl837x_common.h" #include "rtl837x_common.h"
extern __xdata uint8_t cmd_buffer[CMD_BUF_SIZE]; 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; extern __xdata uint8_t err_status;
/* Health instrumentation, built with HEALTH=1. Without it the hooks below /* Health instrumentation, built with HEALTH=1. Without it the hooks below
+4
View File
@@ -145,6 +145,10 @@
#define RTL837X_REG_RX_CTRL 0x785c #define RTL837X_REG_RX_CTRL 0x785c
#define RTL837X_REG_TX_CTRL 0x7860 #define RTL837X_REG_TX_CTRL 0x7860
#define RTL837X_REG_NIC_RX_BUFF_DATA 0x7874 #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_CPU_RX_CURR_PKT 0x787c
#define RTL837X_REG_NIC_TX_CURR_PKT 0x7884 #define RTL837X_REG_NIC_TX_CURR_PKT 0x7884
#define RTL837X_REG_CPU_TX_CURR_PKT 0x7890 #define RTL837X_REG_CPU_TX_CURR_PKT 0x7890
+18 -16
View File
@@ -137,13 +137,15 @@ __code const uint16_t bit_mask[16] = {
__xdata uint8_t linkbits_last[4]; __xdata uint8_t linkbits_last[4];
__xdata uint8_t linkbits_last_p89; __xdata uint8_t linkbits_last_p89;
volatile __bit link_irq; __data __at(0x20) volatile uint8_t evflags;
__xdata uint8_t idle_tick_last; 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 // Last known state of the SFP detection/Loss of Signal pins
__xdata bool button_last; __xdata bool button_last;
__xdata uint8_t button_sec_counter_last; __xdata uint8_t button_sec_counter_last;
volatile __bit tx_buf_empty; volatile __bit tx_buf_empty;
__bit rx_seen;
struct eth_in { struct eth_in {
@@ -201,6 +203,7 @@ void isr_timer0(void) __interrupt(1)
void isr_timer2(void) __interrupt(5) void isr_timer2(void) __interrupt(5)
{ {
ticks++; ticks++;
tick_pending = 1;
if (sleep_ticks > 0) if (sleep_ticks > 0)
sleep_ticks--; sleep_ticks--;
sec_counter++; sec_counter++;
@@ -511,9 +514,8 @@ void isr_ext0(void) __interrupt(0)
*/ */
void isr_ext1(void) __interrupt(2) void isr_ext1(void) __interrupt(2)
{ {
// This flag should only be reset after all packets have been read
EX1 = 0; EX1 = 0;
EX1 = 1; rx_irq = 1;
} }
/* /*
@@ -997,14 +999,10 @@ void handle_rx(void)
__xdata uint8_t budget = RX_BUDGET; __xdata uint8_t budget = RX_BUDGET;
while (budget--) { while (budget--) {
if (rx_seen) {
rx_seen = 0;
} else {
// Check the amount of data available on the NIC/ASIC side // Check the amount of data available on the NIC/ASIC side
reg_read(RTL837X_REG_NIC_RX_BUFF_DATA); reg_read(RTL837X_REG_NIC_RX_BUFF_DATA);
if (!SFR_DATA_U16) if (!SFR_DATA_U16)
break; break;
}
reg_read(RTL837X_REG_CPU_RX_CURR_PKT); reg_read(RTL837X_REG_CPU_RX_CURR_PKT);
uint16_t ring_ptr = SFR_DATA_U16; uint16_t ring_ptr = SFR_DATA_U16;
ring_ptr <<= 3; ring_ptr <<= 3;
@@ -1284,13 +1282,11 @@ static void handle_tick(void)
void idle(void) void idle(void)
{ {
reg_read(RTL837X_REG_NIC_RX_BUFF_DATA); if (!evflags)
rx_seen = SFR_DATA_U16 != 0;
if (!rx_seen)
PCON |= 1; PCON |= 1;
health_loop_start(); health_loop_start();
if (TICKS_DUE(idle_tick_last, 1)) { if (tick_pending) {
idle_tick_last = (uint8_t)ticks; tick_pending = 0;
handle_tick(); handle_tick();
} }
if (link_irq) { if (link_irq) {
@@ -1300,7 +1296,12 @@ void idle(void)
check_links(); check_links();
} }
health_phase(HEALTH_PH_LINK); health_phase(HEALTH_PH_LINK);
if (rx_irq) {
rx_irq = 0;
handle_rx(); handle_rx();
REG_SET(RTL837X_NIC_INT_STS, NIC_INT_RXIS);
EX1 = 1;
}
health_phase(HEALTH_PH_RX); health_phase(HEALTH_PH_RX);
if (cmd_available) { if (cmd_available) {
cmd_available = 0; cmd_available = 0;
@@ -1343,7 +1344,8 @@ void setup_external_irqs(void)
IT0 = 1; // External IRQ 0 on falling edge (link change) IT0 = 1; // External IRQ 0 on falling edge (link change)
EX0 = 1; 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 EX2 = 1; // External IRQ 2 enable: bit EIE.0
EX3 = 1; // External IRQ 3 enable: bit EIE.1 EX3 = 1; // External IRQ 3 enable: bit EIE.1
PX3 = 1; // Set EIP.1 = 1: External IRQ 3 set to high priority PX3 = 1; // Set EIP.1 = 1: External IRQ 3 set to high priority
@@ -1625,7 +1627,6 @@ void main(void)
{ {
ticks = 0; ticks = 0;
stp_tick_last = (uint8_t)ticks; stp_tick_last = (uint8_t)ticks;
idle_tick_last = (uint8_t)ticks;
sfp_tick_last = (uint8_t)ticks; sfp_tick_last = (uint8_t)ticks;
dhcp_state.state = DHCP_OFF; dhcp_state.state = DHCP_OFF;
sbuf_ptr = 0; sbuf_ptr = 0;
@@ -1770,6 +1771,7 @@ void main(void)
stp_enabled = 0; stp_enabled = 0;
stp_defaults(); /* 802.1D/w default config before any "stp ..." replay */ stp_defaults(); /* 802.1D/w default config before any "stp ..." replay */
nic_setup(); nic_setup();
REG_SET(RTL837X_NIC_INT_MSK, NIC_INT_RXIE);
vlan_setup(); vlan_setup();
port_l2_setup(); port_l2_setup();
igmp_setup(); igmp_setup();