Commit cfaeaf2bf3 for qemu.org

commit cfaeaf2bf36b9cb245b3f79727da91d616fb3520
Author: Helge Deller <deller@gmx.de>
Date:   Wed Nov 4 23:07:34 2026 +0100

    i82596: Implement 82596CA big-endian mode (SYSBUS bit 7)

    NetBSD runs the 82596CA chip in big-endian mode, which means the SYSBUS bit #7
    is set, and pointers need to be read as big-endian 32-bit pointer values.
    Increment the error counters at offsets 12-32 of the SCB live in memory.

    Noticed-by: Keith Monahan <keith@techtravels.org>
    Signed-off-by: Helge Deller <deller@gmx.de>

diff --git a/hw/net/i82596.c b/hw/net/i82596.c
index 37ce20efb1..a518aa7696 100644
--- a/hw/net/i82596.c
+++ b/hw/net/i82596.c
@@ -221,39 +221,78 @@ static void set_uint32(uint32_t addr, uint32_t val)
     set_uint16(addr + 2, val >> 16);
 }

+/*
+ * Address fields of the SCB, command blocks and RX/TX descriptors.  In
+ * big-endian mode (SYSBUS BE, 82596CA) the chip reads them as 32-bit
+ * big-endian words; otherwise as two 16-bit halves, low half first.  The
+ * ISCP and SCB addresses and the SCB counters are always two halves.
+ */
+#define I596_SYSBUS_BE  0x80
+
+static uint32_t get_addr(I82596State *s, uint32_t addr)
+{
+    if (s->sysbus & I596_SYSBUS_BE) {
+        return ldl_be_phys(&address_space_memory, addr);
+    }
+    return get_uint32(addr);
+}
+
+static void set_addr(I82596State *s, uint32_t addr, uint32_t val)
+{
+    if (s->sysbus & I596_SYSBUS_BE) {
+        stl_be_phys(&address_space_memory, addr, val);
+    } else {
+        set_uint32(addr, val);
+    }
+}
+
+/* SCB error counters (linear mode), counted in memory like the chip does */
+#define SCB_CRC_ERRS        12
+#define SCB_ALIGN_ERRS      16
+#define SCB_RESOURCE_ERRS   20
+#define SCB_OVERRUN_ERRS    24
+#define SCB_RCVCDT_ERRS     28
+#define SCB_SHORT_ERRS      32
+
+static void i82596_count_error(I82596State *s, int offset)
+{
+    if (s->scb) {
+        set_uint32(s->scb + offset, get_uint32(s->scb + offset) + 1);
+    }
+}
+
 /* Centralized error detection and update mechanism */
 static void i82596_record_error(I82596State *s, uint16_t error_type, bool is_tx)
 {
     if (is_tx) {
         if (error_type & TX_ABORTED_ERRORS) {
             s->tx_aborted_errors++;
-            set_uint32(s->scb + 28, s->tx_aborted_errors);
         }
     } else {
         if (error_type & RX_CRC_ERRORS) {
             s->crc_err++;
-            set_uint32(s->scb + 16, s->crc_err);
+            i82596_count_error(s, SCB_CRC_ERRS);
         }

         if (error_type & (RX_LENGTH_ERRORS | RX_LENGTH_ERRORS_ALT |
                           RX_FRAME_ERRORS)) {
             s->align_err++;
-            set_uint32(s->scb + 18, s->align_err);
+            i82596_count_error(s, SCB_ALIGN_ERRS);
         }

         if (error_type & RFD_STATUS_NOBUFS) {
             s->resource_err++;
-            set_uint32(s->scb + 20, s->resource_err);
+            i82596_count_error(s, SCB_RESOURCE_ERRS);
         }

         if (error_type & (RX_OVER_ERRORS | RX_FIFO_ERRORS)) {
             s->over_err++;
-            set_uint32(s->scb + 22, s->over_err);
+            i82596_count_error(s, SCB_OVERRUN_ERRS);
         }

         if (error_type & RFD_STATUS_TRUNC) {
             s->short_fr_error++;
-            set_uint32(s->scb + 26, s->short_fr_error);
+            i82596_count_error(s, SCB_SHORT_ERRS);
         }
     }
 }
@@ -439,8 +478,8 @@ static void i82596_tx_tfd_read(I82596State *s, hwaddr addr,
 {
     desc->status_bits = get_uint16(addr + 0);
     desc->command = get_uint16(addr + 2);
-    desc->link_addr = get_uint32(addr + 4);
-    desc->tbd_addr = get_uint32(addr + 8);
+    desc->link_addr = get_addr(s, addr + 4);
+    desc->tbd_addr = get_addr(s, addr + 8);
     desc->tcb_count = get_uint16(addr + 12);
     address_space_read(&address_space_memory, addr + 14,
                       MEMTXATTRS_UNSPECIFIED, desc->dest_addr, 6);
@@ -452,8 +491,8 @@ static void i82596_tx_tfd_write(I82596State *s, hwaddr addr,
 {
     set_uint16(addr + 0, desc->status_bits);
     set_uint16(addr + 2, desc->command);
-    set_uint32(addr + 4, desc->link_addr);
-    set_uint32(addr + 8, desc->tbd_addr);
+    set_addr(s, addr + 4, desc->link_addr);
+    set_addr(s, addr + 8, desc->tbd_addr);
     set_uint16(addr + 12, desc->tcb_count);
     address_space_write(&address_space_memory, addr + 14,
                        MEMTXATTRS_UNSPECIFIED, desc->dest_addr, 6);
@@ -464,8 +503,8 @@ static void i82596_tbd_read(I82596State *s, hwaddr addr,
                             struct i82596_tx_buffer_desc *tbd)
 {
     tbd->size = get_uint16(addr + 0);
-    tbd->link = get_uint32(addr + 4);
-    tbd->buffer = get_uint32(addr + 8);
+    tbd->link = get_addr(s, addr + 4);
+    tbd->buffer = get_addr(s, addr + 8);
 }

 static void i82596_rx_rfd_read(I82596State *s, hwaddr addr,
@@ -473,8 +512,8 @@ static void i82596_rx_rfd_read(I82596State *s, hwaddr addr,
 {
     desc->status_bits = get_uint16(addr + 0x0);
     desc->command = get_uint16(addr + 0x2);
-    desc->link = get_uint32(addr + 0x4);
-    desc->rbd_addr = get_uint32(addr + 0x8);
+    desc->link = get_addr(s, addr + 0x4);
+    desc->rbd_addr = get_addr(s, addr + 0x8);
     desc->actual_count = get_uint16(addr + 0xC);
     desc->size = get_uint16(addr + 0xE);

@@ -494,8 +533,8 @@ static void i82596_rx_desc_write(I82596State *s, hwaddr addr,

     if (write_full) {
         set_uint16(addr + 0x2, desc->command);
-        set_uint32(addr + 0x4, desc->link);
-        set_uint32(addr + 0x8, desc->rbd_addr);
+        set_addr(s, addr + 0x4, desc->link);
+        set_addr(s, addr + 0x8, desc->rbd_addr);
         set_uint16(addr + 0xE, desc->size);

         address_space_write(&address_space_memory, addr + 0x10,
@@ -510,8 +549,8 @@ static void i82596_rbd_read(I82596State *s, hwaddr addr,
                             struct i82596_rx_buffer_desc *rbd)
 {
     rbd->actual_count = get_uint16(addr + 0x0);
-    rbd->next_rbd_addr = get_uint32(addr + 0x4);
-    rbd->buffer_addr = get_uint32(addr + 0x8);
+    rbd->next_rbd_addr = get_addr(s, addr + 0x4);
+    rbd->buffer_addr = get_addr(s, addr + 0x8);
     rbd->size = get_uint16(addr + 0xC);
 }

@@ -519,8 +558,8 @@ static void i82596_rbd_write(I82596State *s, hwaddr addr,
                              struct i82596_rx_buffer_desc *rbd)
 {
     set_uint16(addr + 0x0, rbd->actual_count);
-    set_uint32(addr + 0x4, rbd->next_rbd_addr);
-    set_uint32(addr + 0x8, rbd->buffer_addr);
+    set_addr(s, addr + 0x4, rbd->next_rbd_addr);
+    set_addr(s, addr + 0x8, rbd->buffer_addr);
     set_uint16(addr + 0xC, rbd->size);
 }

@@ -1008,7 +1047,7 @@ static ssize_t i82596_receive_packet(I82596State *s, const uint8_t *buf,
     }


-    rfd_addr = get_uint32(s->scb + 8);
+    rfd_addr = s->rfd_p;

     if (rfd_addr == 0 || rfd_addr == I596_NULL) {
         i82596_update_rx_state(s, RX_NO_RESOURCES);
@@ -1383,23 +1422,19 @@ static void i82596_update_statistics(I82596State *s, bool is_tx,
             s->tx_collisions += collision_count;
             s->collision_events++;
             s->total_collisions += collision_count;
-            set_uint32(s->scb + 32, s->tx_collisions);
         }
         if (error_flags) {
             i82596_record_error(s, error_flags, true);
         }
         if (!(error_flags & (TX_ABORTED_ERRORS | TX_CARRIER_ERRORS))) {
             s->tx_good_frames++;
-            set_uint32(s->scb + 36, s->tx_good_frames);
         }
     } else {
         s->total_frames++;
-        set_uint32(s->scb + 40, s->total_frames);
         if (error_flags) {
             i82596_record_error(s, error_flags, false);
         } else {
             s->total_good_frames++;
-            set_uint32(s->scb + 44, s->total_good_frames);
         }
     }
 }
@@ -1557,7 +1592,7 @@ static void i82596_command_dump(I82596State *s, uint32_t cmd_addr)
     uint16_t cmd = get_uint16(cmd_addr + 2);
     uint16_t status;

-    dump_addr = get_uint32(cmd_addr + 8);
+    dump_addr = get_addr(s, cmd_addr + 8);

     i82596_init_dump_area(s, dump_buffer);
     address_space_write(&address_space_memory, dump_addr,
@@ -1648,19 +1683,8 @@ static void i82596_update_cu_status(I82596State *s, uint16_t cmd_status,
 static void update_scb_status(I82596State *s)
 {
     s->scb_status = (s->scb_status & 0xf000)
-        | (s->cu_status << 8) | (s->rx_status << 4) | (s->lnkst >> 8);
+        | (s->cu_status << 8) | (s->rx_status << 4);
     set_uint16(s->scb, s->scb_status);
-
-    set_uint32(s->scb + 28, s->tx_aborted_errors);
-    set_uint32(s->scb + 32, s->tx_collisions);
-    set_uint32(s->scb + 36, s->tx_good_frames);
-
-    set_uint32(s->scb + 16, s->crc_err);
-    set_uint32(s->scb + 18, s->align_err);
-    set_uint32(s->scb + 20, s->resource_err);
-    set_uint32(s->scb + 22, s->over_err);
-    set_uint32(s->scb + 24, s->rcvdt_err);
-    set_uint32(s->scb + 26, s->short_fr_error);
 }

 static void command_loop(I82596State *s)
@@ -1669,11 +1693,12 @@ static void command_loop(I82596State *s)
            s->cmd_p != 0) {
         uint16_t status = get_uint16(s->cmd_p);
         if (status & (STAT_C | STAT_B)) {
-            uint32_t next = get_uint32(s->cmd_p + 4);
+            uint32_t next = get_addr(s, s->cmd_p + 4);
             if (next == 0 || next == s->cmd_p) {
                 s->cmd_p = I596_NULL;
                 s->cu_status = CU_IDLE;
                 s->scb_status |= SCB_STATUS_CNA;
+                s->send_irq = 1;
                 break;
             }
             s->cmd_p = i82596_translate_address(s, next, false);
@@ -1681,7 +1706,7 @@ static void command_loop(I82596State *s)
         }
         set_uint16(s->cmd_p, STAT_B);
         uint16_t cmd = get_uint16(s->cmd_p + 2);
-        uint32_t next_addr = get_uint32(s->cmd_p + 4);
+        uint32_t next_addr = get_addr(s, s->cmd_p + 4);
         next_addr = (next_addr == 0) ? I596_NULL :
                     i82596_translate_address(s, next_addr, false);
         switch (cmd & CMD_MASK) {
@@ -1784,7 +1809,7 @@ static void examine_scb(I82596State *s)
         break;

     case SCB_CUC_START: {
-        uint32_t cmd_ptr = get_uint32(s->scb + 4);
+        uint32_t cmd_ptr = get_addr(s, s->scb + 4);
         s->cmd_p = i82596_translate_address(s, cmd_ptr, false);
         s->cu_status = CU_ACTIVE;
         break;
@@ -1823,7 +1848,7 @@ static void examine_scb(I82596State *s)
         break;

     case SCB_RUC_START: {
-        uint32_t rfd_log = get_uint32(s->scb + 8);
+        uint32_t rfd_log = get_addr(s, s->scb + 8);
         hwaddr rfd = i82596_translate_address(s, rfd_log, false);

         if (rfd == 0 || rfd == I596_NULL) {
@@ -1862,12 +1887,11 @@ static void examine_scb(I82596State *s)
         if (found) {
             s->current_rx_desc = first_usable_rfd;
             s->last_good_rfa = first_usable_rfd_log;
+            s->rbd_p = i82596_translate_address(s, test_rfd.rbd_addr, false);
+            s->rx_take_el = false;
+            s->rfd_p = first_usable_rfd;
             i82596_update_rx_state(s, RX_READY);

-            if (first_usable_rfd != rfd) {
-                set_uint32(s->scb + 8, first_usable_rfd_log);
-            }
-
             if (s->queue_count > 0) {
                 trace_i82596_flush_queue(s->queue_count);
                 i82596_flush_packet_queue(s);
@@ -1909,7 +1933,7 @@ static void examine_scb(I82596State *s)
     }
     if (s->cu_status == CU_ACTIVE) {
         if (s->cmd_p == I596_NULL) {
-            s->cmd_p = get_uint32(s->scb + 4);
+            s->cmd_p = get_addr(s, s->scb + 4);
         }
         update_scb_status(s);
         command_loop(s);
diff --git a/hw/net/i82596.h b/hw/net/i82596.h
index 67b18f957a..ec0b6a75ba 100644
--- a/hw/net/i82596.h
+++ b/hw/net/i82596.h
@@ -75,6 +75,9 @@ struct I82596State_st {
     hwaddr current_tx_desc;
     hwaddr current_rx_desc;
     uint32_t last_good_rfa;
+    uint32_t rbd_p;         /* next free RBD, loaded from the first RFD at RU start */
+    bool rx_take_el;        /* hold timed out: fill the EL RBD like the chip does */
+    uint32_t rfd_p;         /* next RFD, loaded from the SCB at RU start */
     uint8_t packet_queue[PACKET_QUEUE_SIZE][PKT_BUF_SZ];
     size_t packet_queue_len[PACKET_QUEUE_SIZE];
     int queue_head;