All of lore.kernel.org
 help / color / mirror / Atom feed
From: Helge Deller <deller@kernel.org>
To: qemu-devel@nongnu.org
Cc: Keith Monahan <keith@techtravels.org>,
	Richard Henderson <richard.henderson@linaro.org>,
	Helge Deller <deller@gmx.de>
Subject: [PULL v2 8/8] i82596: Various send- and transmit fixes for 715
Date: Thu,  5 Nov 2026 23:31:22 +0100	[thread overview]
Message-ID: <20261105223122.8729-9-deller@kernel.org> (raw)
In-Reply-To: <20261105223122.8729-1-deller@kernel.org>

From: Helge Deller <deller@gmx.de>

When transmitting, send the TCB data that precedes the TBDs in flexible mode,
and interrupt when the CU leaves the active state.  When receiving, keep the
RBD and RFD pointers in the chip (loaded at RU start; the SCB RFA field is
never written back), report only the RFD's own bytes in its actual count and
report every completed frame.  In the SCB, do not update the RU status with the
link state, as it's not part of it.
This fixes networking with HP-UX 9.05, HP-UX 10.20, Debian and NetBSD 9.4/10.1
on the 715 machine.

Noticed-by: Keith Monahan <keith@techtravels.org>
Signed-off-by: Helge Deller <deller@gmx.de>
---
 hw/net/i82596.c | 190 +++++++++++++++++++++++++++++++++++++++---------
 1 file changed, 154 insertions(+), 36 deletions(-)

diff --git a/hw/net/i82596.c b/hw/net/i82596.c
index 19e1bf47db..cd7bd0f7fe 100644
--- a/hw/net/i82596.c
+++ b/hw/net/i82596.c
@@ -589,6 +589,17 @@ static int i82596_tx_copy_buffers(I82596State *s, hwaddr tfd_addr,
         total_len = frame_len;
 
     } else {
+        uint16_t tcb_len = desc->tcb_count & SIZE_MASK;
+
+        if (tcb_len > sizeof(s->tx_buffer)) {
+            return -1;
+        }
+        if (tcb_len) {
+            address_space_read(&address_space_memory, tfd_addr + 16,
+                              MEMTXATTRS_UNSPECIFIED, s->tx_buffer, tcb_len);
+            total_len = tcb_len;
+        }
+
         tbd_addr = desc->tbd_addr;
         while (tbd_addr != I596_NULL && tbd_addr != 0) {
             uint16_t buf_size;
@@ -756,6 +767,91 @@ tx_complete:
     }
 }
 
+/*
+ * The RBD the RU fills next, or I596_NULL if none is free.  An RBD with EL
+ * set is the driver's last free buffer: leave it until the driver hands
+ * another one back, which moves EL on.  The chip would fill it and stop
+ * with no resources, which at QEMU speed keeps drivers in their overrun
+ * recovery (NetBSD iee marks every RBD EL there).
+ */
+static uint32_t i82596_next_rbd(I82596State *s)
+{
+    uint32_t rbd = s->rbd_p;
+
+    if (rbd == I596_NULL || rbd == 0) {
+        return I596_NULL;
+    }
+    if (!s->rx_take_el && (get_uint16(rbd + 0xC) & CMD_EOL)) {
+        return I596_NULL;
+    }
+    return rbd;
+}
+
+/* The next RFD (and, unless the RFD holds a whole frame, an RBD) is free. */
+static bool i82596_rx_ring_free(I82596State *s)
+{
+    uint32_t rbd;
+
+    if (s->rfd_p == I596_NULL || s->rfd_p == 0) {
+        return false;
+    }
+    if (get_uint16(s->rfd_p) & (STAT_C | STAT_B)) {
+        return false;           /* the driver has not handed it back yet */
+    }
+    if (!(get_uint16(s->rfd_p + 2) & CMD_FLEX) ||
+        (get_uint16(s->rfd_p + 0xE) & SIZE_MASK) >= PKT_BUF_SZ) {
+        return true;
+    }
+    rbd = i82596_next_rbd(s);
+    return rbd != I596_NULL && rbd != 0;
+}
+
+static bool i82596_rx_buffers_free(I82596State *s)
+{
+    if (i82596_rx_ring_free(s)) {
+        s->rx_hold_start = 0;
+        return true;
+    }
+    return false;
+}
+
+static void i82596_update_rx_state(I82596State *s, int new_state);
+
+/* Hold incoming frames while the ring is full; retry every 500 us. */
+static void i82596_rx_hold(I82596State *s)
+{
+    int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+
+    if (!s->rx_hold_start) {
+        s->rx_hold_start = now;
+    }
+    timer_mod(s->rx_retry_timer, now + 500 * SCALE_US);
+}
+
+static void i82596_rx_retry(void *opaque)
+{
+    I82596State *s = opaque;
+
+    /*
+     * The driver has not handed a buffer back within 20 ms: behave like the
+     * chip.  Fill the last (EL) RBD if that is all that is left, otherwise
+     * report RU out of resources so the driver can rebuild its ring.
+     */
+    if (s->rx_hold_start && s->rx_status == RX_READY &&
+        !i82596_rx_buffers_free(s) &&
+        qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) - s->rx_hold_start >
+            20 * SCALE_MS) {
+        s->rx_hold_start = 0;
+        s->rx_take_el = true;
+        if (!i82596_rx_buffers_free(s)) {
+            s->rx_take_el = false;
+            i82596_update_rx_state(s, RX_NO_RESOURCES);
+            i82596_update_scb_irq(s, true);
+        }
+    }
+    qemu_flush_queued_packets(qemu_get_queue(s->nic));
+}
+
 bool i82596_can_receive(NetClientState *nc)
 {
     I82596State *s = qemu_get_nic_opaque(nc);
@@ -769,6 +865,15 @@ bool i82596_can_receive(NetClientState *nc)
         return true;
     }
 
+    /*
+     * A real 82596 cannot be overrun this fast at 10 Mbit/s; hold frames in
+     * the net queue while the driver catches up instead of dropping them.
+     */
+    if (s->rx_status == RX_READY && !i82596_rx_buffers_free(s)) {
+        i82596_rx_hold(s);
+        return false;
+    }
+
     if (!s->throttle_state && !I596_FULL_DUPLEX) {
         return (s->queue_count < PACKET_QUEUE_SIZE);
     }
@@ -1038,6 +1143,8 @@ static ssize_t i82596_receive_packet(I82596State *s, const uint8_t *buf,
     size_t bytes_copied = 0;
     const uint8_t *packet_data = buf;
     bool out_of_resources = false;
+    bool rbds_exhausted = false;
+    size_t rfd_data_len = 0;
     size_t crc_size = i82596_get_crc_size(s);
 
     trace_i82596_receive_packet(buf, size);
@@ -1127,16 +1234,18 @@ static ssize_t i82596_receive_packet(I82596State *s, const uint8_t *buf,
                                     packet_data + bytes_copied,
                                     rfd_frame_size);
                 bytes_copied += rfd_frame_size;
+                rfd_data_len += rfd_frame_size;
             }
 
             if (bytes_copied < payload_size) {
                 size_t remaining = payload_size - bytes_copied;
-                rbd_addr = i82596_translate_address(s, rfd.rbd_addr, false);
+                rbd_addr = i82596_next_rbd(s);
 
                 if (rbd_addr == I596_NULL || rbd_addr == 0) {
                     rx_status |= RFD_STATUS_TRUNC | RFD_STATUS_NOBUFS;
                     i82596_record_error(s, RFD_STATUS_NOBUFS, false);
                     packet_completed = true;
+                    rbds_exhausted = true;
                     break;
                 } else {
                     hwaddr remaining_rbd = I596_NULL;
@@ -1148,22 +1257,18 @@ static ssize_t i82596_receive_packet(I82596State *s, const uint8_t *buf,
                                             &remaining_rbd);
                     bytes_copied += rbd_bytes;
 
-                    uint32_t next_rfd = i82596_translate_address(s, rfd.link,
-                                                                  false);
-                    if (next_rfd != I596_NULL && next_rfd != 0) {
-                        if (remaining_rbd != I596_NULL && remaining_rbd != 0) {
-                            trace_i82596_rx_rfd_update(next_rfd, remaining_rbd);
-                            set_uint32(next_rfd + 8, remaining_rbd);
-                        } else {
-                            set_uint32(next_rfd + 8, I596_NULL);
-                        }
-                    }
+                    /* the RFD points at the first RBD holding its data */
+                    trace_i82596_rx_rfd_update(rfd_addr, rbd_addr);
+                    set_addr(s, rfd_addr + 8, rbd_addr);
+                    s->rbd_p = remaining_rbd;
+                    s->rx_take_el = false;
 
                     if (out_of_resources) {
                         trace_i82596_rx_out_of_rbds();
                         i82596_record_error(s, RFD_STATUS_NOBUFS, false);
                         rx_status |= RFD_STATUS_TRUNC | RFD_STATUS_NOBUFS;
                         packet_completed = true;
+                        rbds_exhausted = true;
                         break;
                     }
 
@@ -1211,30 +1316,31 @@ rx_complete:
     }
 
     rfd.status_bits = rx_status & ~STAT_B;
-    rfd.actual_count = (bytes_copied & 0x3FFF) | 0x4000;
-    if (packet_completed) {
-        rfd.actual_count |= I596_EOF;
+    if (simplified_mode) {
+        rfd.actual_count = (bytes_copied & 0x3FFF) | 0x4000;
+        if (packet_completed) {
+            rfd.actual_count |= I596_EOF;
+        }
+    } else {
+        /* the RBDs carry their own counts and the EOF of a split frame */
+        rfd.actual_count = (rfd_data_len & 0x3FFF) | 0x4000;
+        if (packet_completed && rfd_data_len == bytes_copied) {
+            rfd.actual_count |= I596_EOF;
+        }
     }
 
     i82596_rx_desc_write(s, rfd_addr, &rfd, (simplified_mode || I596_LOOPBACK));
 
-    if (rfd.command & CMD_SUSP) {
-        i82596_update_rx_state(s, RX_SUSPENDED);
-        return size;
+    if (packet_completed) {
+        s->rfd_p = i82596_translate_address(s, rfd.link, false);
+        s->scb_status |= SCB_STATUS_FR;
     }
-
-    if (rfd.command & CMD_EOL) {
+    if (rfd.command & (CMD_SUSP | CMD_EOL)) {
         i82596_update_rx_state(s, RX_SUSPENDED);
-        return size;
+    } else if (rbds_exhausted) {
+        i82596_update_rx_state(s, RX_NO_RESOURCES);
     }
-
-    if (packet_completed && s->rx_status == RX_READY) {
-        uint32_t next_rfd_addr = i82596_translate_address(s, rfd.link, false);
-        if (next_rfd_addr != 0 && next_rfd_addr != I596_NULL) {
-            set_uint32(s->scb + 8, next_rfd_addr);
-        }
-
-        s->scb_status |= SCB_STATUS_FR;
+    if (packet_completed || rbds_exhausted) {
         i82596_update_scb_irq(s, true);
     }
     trace_i82596_rx_complete(s->crc_err, s->align_err, s->resource_err);
@@ -1256,8 +1362,6 @@ ssize_t i82596_receive(NetClientState *nc, const uint8_t *buf, size_t size)
     if (s->rx_status != RX_READY) {
         if (s->queue_count >= PACKET_QUEUE_SIZE) {
             trace_i82596_receive_queue_full();
-            s->over_err++;
-            set_uint32(s->scb + 22, s->over_err);
             i82596_record_error(s, RX_OVER_ERRORS, false);
             return size;
         }
@@ -1271,6 +1375,11 @@ queue_packet:
         return size;
     }
 
+    if (!i82596_rx_buffers_free(s)) {
+        i82596_rx_hold(s);
+        return 0;
+    }
+
     return i82596_receive_packet(s, buf, size, false);
 }
 
@@ -1363,11 +1472,6 @@ static bool i82596_check_medium_status(I82596State *s)
         return false;
     }
 
-    if (!I596_LOOPBACK && (qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) % 100 < 5)) {
-        s->collision_events++;
-        return false;
-    }
-
     return true;
 }
 
@@ -1740,6 +1844,7 @@ skip_status_update:
         if (cmd & CMD_SUSP) {
             s->cu_status = CU_SUSPENDED;
             s->scb_status |= SCB_STATUS_CNA;
+            s->send_irq = 1;
             stop = true;
         }
 
@@ -1747,6 +1852,7 @@ skip_status_update:
             s->cmd_p = I596_NULL;
             s->cu_status = CU_IDLE;
             s->scb_status |= SCB_STATUS_CNA;
+            s->send_irq = 1;
             stop = true;
         } else if (!stop) {
             if (next_addr == 0 || next_addr == I596_NULL ||
@@ -1754,6 +1860,7 @@ skip_status_update:
                 s->cmd_p = I596_NULL;
                 s->cu_status = CU_IDLE;
                 s->scb_status |= SCB_STATUS_CNA;
+                s->send_irq = 1;
                 stop = true;
             } else {
                 s->cmd_p = next_addr;
@@ -2061,7 +2168,7 @@ void i82596_poll(NetClientState *nc, bool enable)
 
 const VMStateDescription vmstate_i82596 = {
     .name = "i82596",
-    .version_id = 1,
+    .version_id = 2,
     .minimum_version_id = 1,
     .fields = (VMStateField[]) {
         VMSTATE_UINT8(mode, I82596State),
@@ -2101,6 +2208,8 @@ const VMStateDescription vmstate_i82596 = {
         VMSTATE_UINT32(total_good_frames, I82596State),
         VMSTATE_BUFFER(rx_buffer, I82596State),
         VMSTATE_UINT16(tx_frame_len, I82596State),
+        VMSTATE_UINT32_V(rbd_p, I82596State, 2),
+        VMSTATE_UINT32_V(rfd_p, I82596State, 2),
         VMSTATE_UINT16(rx_frame_len, I82596State),
         VMSTATE_UINT64(current_tx_desc, I82596State),
         VMSTATE_UINT64(current_rx_desc, I82596State),
@@ -2127,6 +2236,13 @@ static int i82596_flush_packet_queue(I82596State *s)
         int tail = s->queue_tail;
         size_t len = s->packet_queue_len[tail];
 
+        if (s->rx_status == RX_READY && !i82596_rx_buffers_free(s)) {
+            i82596_rx_hold(s);
+            timer_mod(s->flush_queue_timer,
+                      qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 500 * SCALE_US);
+            break;
+        }
+
         ssize_t ret = i82596_receive_packet(s, s->packet_queue[tail], len,
                                              true);
 
@@ -2174,6 +2290,8 @@ void i82596_common_init(DeviceState *dev, I82596State *s, NetClientInfo *info)
                 dev->id, &dev->mem_reentrancy_guard, s);
     qemu_format_nic_info_str(qemu_get_queue(s->nic), s->conf.macaddr.a);
 
+    s->rx_retry_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, i82596_rx_retry, s);
+
     if (USE_TIMER) {
         if (!s->flush_queue_timer) {
             s->flush_queue_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
-- 
2.54.0



  parent reply	other threads:[~2026-11-05 22:32 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-05 22:31 [PULL v2 0/8] Hppa 715 cpu nic patches Helge Deller
2026-11-05 22:31 ` [PULL v2 1/8] target/hppa: Move emulated power button into CPU I/O region Helge Deller
2026-11-05 22:31 ` [PULL v2 2/8] target/hppa: Add special blr instruction for HP-UX 9.05 Helge Deller
2026-11-05 22:31 ` [PULL v2 3/8] lasi_i82596: Decode the PORT command from bits 3:0, not 7:0 Helge Deller
2026-11-05 22:31 ` [PULL v2 4/8] i82596: Implement 82596CA big-endian mode (SYSBUS bit 7) Helge Deller
2026-11-05 22:31 ` [PULL v2 5/8] i82596: Initialize variables at chip reset Helge Deller
2026-11-05 22:31 ` [PULL v2 6/8] i82596: Drop crc calculation Helge Deller
2026-11-05 22:31 ` [PULL v2 7/8] i82596: Fix source address inserting Helge Deller
2026-11-05 22:31 ` Helge Deller [this message]
2026-11-06 16:46 ` [PULL v2 0/8] Hppa 715 cpu nic patches Richard Henderson

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261105223122.8729-9-deller@kernel.org \
    --to=deller@kernel.org \
    --cc=deller@gmx.de \
    --cc=keith@techtravels.org \
    --cc=qemu-devel@nongnu.org \
    --cc=richard.henderson@linaro.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.