/*
 * QEMU Intel i82596 (Apricot) emulation
 *
 * Copyright (c) 2019 Helge Deller <deller@gmx.de>
 *
 * Additional functionality added by:
 * Soumyajyotii Ssarkar <soumyajyotisarkar23@gmail.com>
 * During GSOC 2025 under mentorship of Helge Deller.
 *
 * This work is licensed under the GNU GPL license version 2 or later.
 * This software was written to be compatible with the specification:
 * https://parisc.docs.kernel.org/en/latest/_downloads/96672be0650d9fc046bbcea40b92482f/82596CA.pdf
 *
 * INDEX:
 * 1.  Reset
 * 2.  Address Translation
 * 3.  Transmit functions
 * 4.  Receive Helper functions
 * 5.  Receive functions
 * 6.  Misc Functionality Functions
 * 6.1 Individual Address
 * 6.2 Multicast Address List
 * 6.3 Link Status
 * 6.4 CSMA/CD functions
 * 6.5 Unified CRC Calculation
 * 6.6 Unified Statistics Update
 * 7.  Bus Throttling Timer
 * 8.  Dump functions
 * 9.  Configure
 * 10. Command Loop
 * 11. Examine SCB
 * 12. Channel attention (CA)
 * 13. LASI interface
 * 14. Polling functions
 * 15. QOM and interface functions
 *
 */

#include "qemu/osdep.h"
#include "qemu/timer.h"
#include "net/net.h"
#include "net/eth.h"
#include "hw/core/irq.h"
#include "hw/core/qdev-properties.h"
#include "migration/vmstate.h"
#include "system/address-spaces.h"
#include "qemu/module.h"
#include "trace.h"
#include "i82596.h"
#include <zlib.h> /* for crc32 */

#define USE_TIMER       1

#define MAX_MC_CNT      64
#define I596_NULL       ((uint32_t)0xffffffff)
#define BITS(n, m)      (((0xffffffffU << (31 - n)) >> (31 - n + m)) << m)

#define SCB_STATUS_CX   0x8000  /* CU finished command with I bit */
#define SCB_STATUS_FR   0x4000  /* RU finished receiving a frame */
#define SCB_STATUS_CNA  0x2000  /* CU left active state */
#define SCB_STATUS_RNR  0x1000  /* RU left active state */
#define SCB_ACK_MASK    0xF000  /* All interrupt acknowledge bits */

/* 82596 Operational Modes */
#define I82586_MODE                 0x00
#define I82596_MODE_SEGMENTED       0x01
#define I82596_MODE_LINEAR          0x02

/* Monitor Options */
#define MONITOR_NORMAL      0x00
#define MONITOR_FILTERED    0x01
#define MONITOR_ALL         0x02
#define MONITOR_DISABLED    0x03

/* Operation mode flags from SYSBUS byte */
#define SYSBUS_LOCK_EN         0x08
#define SYSBUS_INT_ACTIVE_LOW  0x10
#define SYSBUS_BIG_ENDIAN_32   0x80
#define SYSBUS_THROTTLE_MASK   0x60

/* SCB commands - Command Unit (CU) */
#define SCB_CUC_NOP            0x00
#define SCB_CUC_START          0x01
#define SCB_CUC_RESUME         0x02
#define SCB_CUC_SUSPEND        0x03
#define SCB_CUC_ABORT          0x04
#define SCB_CUC_LOAD_THROTTLE  0x05
#define SCB_CUC_LOAD_START     0x06

/* SCB commands - Receive Unit (RU) */
#define SCB_RUC_NOP            0x00
#define SCB_RUC_START          0x01
#define SCB_RUC_RESUME         0x02
#define SCB_RUC_SUSPEND        0x03
#define SCB_RUC_ABORT          0x04

/* SCB statuses - Command Unit (CU) */
#define CU_IDLE         0
#define CU_SUSPENDED    1
#define CU_ACTIVE       2

/* SCB statuses - Receive Unit (RU) */
#define RX_IDLE         0x00
#define RX_SUSPENDED    0x01
#define RX_NO_RESOURCES 0x02
#define RX_READY        0x04
#define RX_NO_RESO_RBD  0x0A
#define RX_NO_MORE_RBD  0x0C

#define CMD_FLEX        0x0008
#define CMD_MASK        0x0007

#define CMD_EOL         0x8000
#define CMD_SUSP        0x4000
#define CMD_INTR        0x2000

#define ISCP_BUSY                   0x01
#define NANOSECONDS_PER_MICROSECOND 1000

#define DUMP_BUF_SZ                 304

enum commands {
        CmdNOp = 0, CmdSASetup = 1, CmdConfigure = 2, CmdMulticastList = 3,
        CmdTx = 4, CmdTDR = 5, CmdDump = 6, CmdDiagnose = 7
};


#define STAT_C          0x8000  /* Set to 0 after execution */
#define STAT_B          0x4000  /* Command being executed */
#define STAT_OK         0x2000  /* Command executed ok */
#define STAT_A          0x1000  /* Command aborted */

#define I596_EOF        0x8000
#define SIZE_MASK       0x3fff

#define CSMA_SLOT_TIME         51
#define CSMA_MAX_RETRIES       16
#define CSMA_BACKOFF_LIMIT     10

/* Global Flags fetched from config bytes */
#define I596_PREFETCH       (s->config[0] & 0x80)
#define SAVE_BAD_FRAMES     (s->config[2] & 0x80)
#define I596_NO_SRC_ADD_IN  (s->config[3] & 0x08)
#define I596_LOOPBACK       (s->config[3] >> 6)
#define I596_PROMISC        (s->config[8] & 0x01)
#define I596_BC_DISABLE     (s->config[8] & 0x02)
#define I596_NOCRC_INS      (s->config[8] & 0x08)
#define I596_CRC16_32       (s->config[8] & 0x10)
#define I596_PADDING        (s->config[8] & 0x80)
#define I596_MIN_FRAME_LEN  (s->config[10])
#define I596_CRCINM         (s->config[11] & 0x04)
#define I596_MONITOR_MODE   ((s->config[11] >> 6) & 0x03)
#define I596_MC_ALL         (s->config[11] & 0x20)
#define I596_FULL_DUPLEX    (s->config[12] & 0x40)
#define I596_MULTIIA        (s->config[13] & 0x40)

/* RX Error flags */
#define RX_COLLISIONS         0x0001
#define RX_LENGTH_ERRORS      0x0080
#define RX_OVER_ERRORS        0x0100
#define RX_FIFO_ERRORS        0x0400
#define RX_FRAME_ERRORS       0x0800
#define RX_CRC_ERRORS         0x1000
#define RX_LENGTH_ERRORS_ALT  0x2000
#define RFD_STATUS_TRUNC      0x0020
#define RFD_STATUS_NOBUFS     0x0200

/* TX Error flags */
#define TX_COLLISIONS       0x0020
#define TX_HEARTBEAT_ERRORS 0x0040
#define TX_CARRIER_ERRORS   0x0400
#define TX_COLLISIONS_ALT   0x0800
#define TX_ABORTED_ERRORS   0x1000

static void i82596_update_scb_irq(I82596State *s, bool trigger);
static void i82596_update_cu_status(I82596State *s, uint16_t cmd_status,
                                     bool generate_interrupt);
static void update_scb_status(I82596State *s);
static void examine_scb(I82596State *s);
static bool i82596_check_medium_status(I82596State *s);
static int i82596_csma_backoff(I82596State *s, int retry_count);
static uint16_t i82596_calculate_crc16(const uint8_t *data, size_t len);
static size_t i82596_append_crc(I82596State *s, uint8_t *buffer, size_t len);
static void i82596_bus_throttle_timer(void *opaque);
static void i82596_flush_queue_timer(void *opaque);
static int i82596_flush_packet_queue(I82596State *s);
static void i82596_update_statistics(I82596State *s, bool is_tx,
                                      uint16_t error_flags,
                                      uint16_t collision_count);

static uint8_t get_byte(uint32_t addr)
{
    return ldub_phys(&address_space_memory, addr);
}

static void set_byte(uint32_t addr, uint8_t c)
{
    return stb_phys(&address_space_memory, addr, c);
}

static uint16_t get_uint16(uint32_t addr)
{
    return lduw_be_phys(&address_space_memory, addr);
}

static void set_uint16(uint32_t addr, uint16_t w)
{
    return stw_be_phys(&address_space_memory, addr, w);
}

static uint32_t get_uint32(uint32_t addr)
{
    uint32_t lo = lduw_be_phys(&address_space_memory, addr);
    uint32_t hi = lduw_be_phys(&address_space_memory, addr + 2);
    return (hi << 16) | lo;
}

static void set_uint32(uint32_t addr, uint32_t val)
{
    set_uint16(addr, (uint16_t) val);
    set_uint16(addr + 2, val >> 16);
}

/* 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);
        }

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

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

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

        if (error_type & RFD_STATUS_TRUNC) {
            s->short_fr_error++;
            set_uint32(s->scb + 26, s->short_fr_error);
        }
    }
}

/* Packet Header Debugger */
struct qemu_ether_header {
    uint8_t ether_dhost[6];
    uint8_t ether_shost[6];
    uint16_t ether_type;
};

#define PRINT_PKTHDR(txt, BUF) do {                  \
    struct qemu_ether_header *hdr = (void *)(BUF); \
    printf(txt ": packet dhost=" MAC_FMT ", shost=" MAC_FMT ", type=0x%04x\n",\
           MAC_ARG(hdr->ether_dhost), MAC_ARG(hdr->ether_shost),        \
           be16_to_cpu(hdr->ether_type));       \
} while (0)

static void i82596_cleanup(I82596State *s)
{
    if (s->throttle_timer) {
        timer_del(s->throttle_timer);
    }
    if (s->flush_queue_timer) {
        timer_del(s->flush_queue_timer);
    }
    s->queue_head = 0;
    s->queue_tail = 0;
    s->queue_count = 0;
}

static void i82596_s_reset(I82596State *s)
{
    trace_i82596_s_reset(s);
    i82596_cleanup(s);

    /* Clearing config bits */
    memset(s->config, 0, sizeof(s->config));
    s->scp = 0x00FFFFF4;
    s->scb = 0;
    s->scb_base = 0;
    s->scb_status = 0;
    s->cu_status = CU_IDLE;
    s->rx_status = RX_IDLE;
    s->cmd_p = I596_NULL;
    s->lnkst = 0x8000;
    s->ca = s->ca_active = 0;
    s->send_irq = 0;

    /* Statistical Counters */
    s->crc_err = 0;
    s->align_err = 0;
    s->resource_err = 0;
    s->over_err = 0;
    s->rcvdt_err = 0;
    s->short_fr_error = 0;
    s->total_frames = 0;
    s->total_good_frames = 0;
    s->collision_events = 0;
    s->total_collisions = 0;
    s->tx_good_frames = 0;
    s->tx_collisions = 0;
    s->tx_aborted_errors = 0;
    s->last_tx_len = 0;

    s->last_good_rfa = 0;
    s->queue_head = 0;
    s->queue_tail = 0;
    s->queue_count = 0;

    s->t_on = 0xFFFF;
    s->t_off = 0;
    s->throttle_state = true;

    if (!s->throttle_timer) {
        s->throttle_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
                                       i82596_bus_throttle_timer, s);
    } else {
        timer_del(s->throttle_timer);
    }

    if (!I596_FULL_DUPLEX && s->t_on != 0xFFFF) {
        timer_mod(s->throttle_timer,
                 qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
                 s->t_on * NANOSECONDS_PER_MICROSECOND);
    }
}

void i82596_h_reset(void *opaque)
{
    I82596State *s = opaque;

    i82596_s_reset(s);
}

/*
 * Mode Transition of address functionality.
 * Note: As of now the 82596 is tested only for Linear Mode as it is most
 * widely used by Linux and HPUX systems. This function is here for
 * futureproofing our 82596 device model.
 * According to the documentation the translation of addresses based on mode
 * are done for the following cases: ISCP, SCB, CBP, RFD, TFD,
 * RBD, TBD, Rx Buffers, Tx Buffers
 * Please refer to the documentation for more details.
 */
static uint32_t i82596_translate_address(I82596State *s,
                                         uint32_t addr,
                                         bool is_data_buffer)
{
    if (addr == I596_NULL || addr == 0) {
        return addr;
    }
    switch (s->mode) {
    case I82586_MODE:
        if (is_data_buffer) {
            return addr & 0x00FFFFFF;
        } else {
            if (s->scb_base) {
                return (s->scb_base & 0x00FFFFFF) + (addr & 0xFFFF);
            } else {
                return addr & 0x00FFFFFF;
            }
        }
        break;
    case I82596_MODE_SEGMENTED:
        if (is_data_buffer) {
            return addr;
        } else {
            if (s->scb_base) {
                return s->scb_base + (addr & 0xFFFF);
            } else {
                return addr;
            }
        }
        break;
    case I82596_MODE_LINEAR:
    default:
        return addr;
    }
}

/* (TFD) Transmit Frame Descriptor  */
struct i82596_tx_descriptor {
    uint16_t status_bits;
    uint16_t command;
    uint32_t link_addr;
    uint32_t tbd_addr;
    uint16_t tcb_count;
    uint8_t  dest_addr[6];
    uint16_t length_field;
};

/* (TBD) Transmit Buffer Descriptor */
struct i82596_tx_buffer_desc {
    uint16_t size;
    uint32_t link;
    uint32_t buffer;
};

/* (RFD) Receive Frame Descriptor  */
struct i82596_rx_descriptor {
    uint16_t status_bits;
    uint16_t command;
    uint32_t link;
    uint32_t rbd_addr;
    uint16_t actual_count;
    uint16_t size;
    uint8_t  dest_addr[6];
    uint8_t  src_addr[6];
    uint16_t length_field;
};

/* (RBD) Receive Buffer Descriptor */
struct i82596_rx_buffer_desc {
    uint16_t actual_count;
    uint32_t next_rbd_addr;
    uint32_t buffer_addr;
    uint16_t size;
};

static void i82596_tx_tfd_read(I82596State *s, hwaddr addr,
                                struct i82596_tx_descriptor *desc)
{
    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->tcb_count = get_uint16(addr + 12);
    address_space_read(&address_space_memory, addr + 14,
                      MEMTXATTRS_UNSPECIFIED, desc->dest_addr, 6);
    desc->length_field = get_uint16(addr + 20);
}

static void i82596_tx_tfd_write(I82596State *s, hwaddr addr,
                                 struct i82596_tx_descriptor *desc)
{
    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_uint16(addr + 12, desc->tcb_count);
    address_space_write(&address_space_memory, addr + 14,
                       MEMTXATTRS_UNSPECIFIED, desc->dest_addr, 6);
    set_uint16(addr + 20, desc->length_field);
}

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);
}

static void i82596_rx_rfd_read(I82596State *s, hwaddr addr,
                                struct i82596_rx_descriptor *desc)
{
    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->actual_count = get_uint16(addr + 0xC);
    desc->size = get_uint16(addr + 0xE);

    address_space_read(&address_space_memory, addr + 0x10,
                      MEMTXATTRS_UNSPECIFIED, desc->dest_addr, 6);
    address_space_read(&address_space_memory, addr + 0x16,
                      MEMTXATTRS_UNSPECIFIED, desc->src_addr, 6);
    desc->length_field = get_uint16(addr + 28);
}

static void i82596_rx_desc_write(I82596State *s, hwaddr addr,
                                  struct i82596_rx_descriptor *desc,
                                  bool write_full)
{
    set_uint16(addr + 0x0, desc->status_bits);
    set_uint16(addr + 0xC, desc->actual_count);

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

        address_space_write(&address_space_memory, addr + 0x10,
                          MEMTXATTRS_UNSPECIFIED, desc->dest_addr, 6);
        address_space_write(&address_space_memory, addr + 0x16,
                          MEMTXATTRS_UNSPECIFIED, desc->src_addr, 6);
        set_uint16(addr + 0x1C, desc->length_field);
    }
}

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->size = get_uint16(addr + 0xC);
}

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_uint16(addr + 0xC, rbd->size);
}

static int i82596_tx_copy_buffers(I82596State *s, hwaddr tfd_addr,
                                  struct i82596_tx_descriptor *desc)
{
    bool simplified_mode = !(desc->command & CMD_FLEX);
    uint32_t total_len = 0;
    uint32_t tbd_addr;
    struct i82596_tx_buffer_desc tbd;

    s->tx_frame_len = 0;

    if (simplified_mode) {
        uint16_t frame_len = desc->tcb_count & SIZE_MASK;
        if (frame_len == 0 || frame_len > sizeof(s->tx_buffer)) {
            return -1;
        }
        address_space_read(&address_space_memory, tfd_addr + 16,
                          MEMTXATTRS_UNSPECIFIED, s->tx_buffer, frame_len);
        total_len = frame_len;

    } else {
        tbd_addr = desc->tbd_addr;
        while (tbd_addr != I596_NULL && tbd_addr != 0) {
            uint16_t buf_size;
            uint32_t buf_addr;
            tbd_addr = i82596_translate_address(s, tbd_addr, false);
            if (tbd_addr == 0 || tbd_addr == I596_NULL) {
                return -1;
            }
            i82596_tbd_read(s, tbd_addr, &tbd);
            trace_i82596_tx_tbd(tbd_addr, tbd.size, tbd.buffer);
            buf_size = tbd.size & SIZE_MASK;
            buf_addr = i82596_translate_address(s, tbd.buffer, true);

            if (total_len + buf_size > sizeof(s->tx_buffer)) {
                return -1;
            }

            if (buf_size > 0 && buf_addr != 0 && buf_addr != I596_NULL) {
                address_space_read(&address_space_memory, buf_addr,
                                  MEMTXATTRS_UNSPECIFIED,
                                  s->tx_buffer + total_len, buf_size);
                total_len += buf_size;
            }
            if (tbd.size & I596_EOF) {
                break;
            }
            tbd_addr = tbd.link;
        }
    }

    s->tx_frame_len = total_len;
    return total_len;
}

static int i82596_tx_process_frame(I82596State *s, bool insert_crc)
{
    uint32_t total_len = s->tx_frame_len;

    if (total_len == 0) {
        return 0;
    }

    if (I596_NO_SRC_ADD_IN == 0 && total_len >= ETH_ALEN * 2) {
        memcpy(&s->tx_buffer[ETH_ALEN], s->conf.macaddr.a, ETH_ALEN);
    }

    if (I596_PADDING && total_len < I596_MIN_FRAME_LEN) {
        size_t pad_len = I596_MIN_FRAME_LEN - total_len;
        memset(s->tx_buffer + total_len, 0, pad_len);
        total_len = I596_MIN_FRAME_LEN;
    }

    if (insert_crc) {
        total_len = i82596_append_crc(s, s->tx_buffer, total_len);
    }

    s->tx_frame_len = total_len;
    return total_len;
}

static void i82596_tx_update_status(I82596State *s, hwaddr tfd_addr,
                                    struct i82596_tx_descriptor *desc,
                                    uint16_t tx_status,
                                    uint16_t collision_count,
                                    bool success)
{
    desc->status_bits = STAT_C;

    if (success) {
        desc->status_bits |= STAT_OK;
    } else {
        desc->status_bits |= STAT_A;
    }

    if (collision_count > 0) {
        desc->status_bits |= (collision_count & 0x0F);
    }

    i82596_tx_tfd_write(s, tfd_addr, desc);
}

static int i82596_tx_csma_cd(I82596State *s, uint16_t *tx_status)
{
    int retry_count = 0;
    bool medium_available;

    if (I596_FULL_DUPLEX || I596_LOOPBACK) {
        return 0;
    }

    while (retry_count < CSMA_MAX_RETRIES) {
        medium_available = i82596_check_medium_status(s);

        if (medium_available) {
            break;
        }
        i82596_csma_backoff(s, retry_count);
        retry_count++;
        s->total_collisions++;
    }
    if (retry_count >= CSMA_MAX_RETRIES) {
        *tx_status |= TX_ABORTED_ERRORS;
        return -1;
    }
    if (retry_count > 0) {
        *tx_status |= TX_COLLISIONS;
        s->collision_events++;
    }

    return retry_count;
}

static void i82596_transmit(I82596State *s, uint32_t addr)
{
    struct i82596_tx_descriptor tfd;
    hwaddr tfd_addr = addr;
    uint16_t tx_status = 0;
    int collision_count = 0;
    int frame_len;
    bool success = true;
    bool insert_crc;

    i82596_tx_tfd_read(s, tfd_addr, &tfd);
    trace_i82596_tx_tfd(tfd_addr, tfd.status_bits, tfd.command,
                        tfd.link_addr, tfd.tbd_addr);

    s->current_tx_desc = tfd_addr;
    insert_crc = (I596_NOCRC_INS == 0) && ((tfd.command & 0x10) == 0) &&
                 !I596_LOOPBACK;
    collision_count = i82596_tx_csma_cd(s, &tx_status);
    if (collision_count < 0) {
        success = false;
        goto tx_complete;
    }
    frame_len = i82596_tx_copy_buffers(s, tfd_addr, &tfd);
    if (frame_len < 0) {
        tx_status |= TX_ABORTED_ERRORS;
        success = false;
        goto tx_complete;
    }
    frame_len = i82596_tx_process_frame(s, insert_crc);
    if (frame_len <= 0) {
        tx_status |= TX_ABORTED_ERRORS;
        success = false;
        goto tx_complete;
    }
    s->last_tx_len = frame_len;
    trace_i82596_transmit(frame_len, addr);

    if (I596_LOOPBACK) {
        i82596_receive(qemu_get_queue(s->nic), s->tx_buffer, frame_len);
    } else {
        if (s->nic) {
            qemu_send_packet_raw(qemu_get_queue(s->nic), s->tx_buffer,
                                 frame_len);
        }
    }

tx_complete:
    i82596_tx_update_status(s, tfd_addr, &tfd, tx_status, collision_count,
                            success);
    i82596_update_statistics(s, true, tx_status, collision_count);
    if (tfd.command & CMD_INTR) {
        i82596_update_cu_status(s, tfd.status_bits, true);
    }
}

bool i82596_can_receive(NetClientState *nc)
{
    I82596State *s = qemu_get_nic_opaque(nc);

    if (I596_LOOPBACK || !s->lnkst) {
        return false;
    }

    if (s->rx_status == RX_SUSPENDED || s->rx_status == RX_IDLE ||
        s->rx_status == RX_NO_RESOURCES) {
        return true;
    }

    if (!s->throttle_state && !I596_FULL_DUPLEX) {
        return (s->queue_count < PACKET_QUEUE_SIZE);
    }

    return true;
}

static int i82596_validate_receive_state(I82596State *s, size_t *sz,
                                          bool from_queue)
{
    if (*sz < 14 || *sz > PKT_BUF_SZ - 4) {
        trace_i82596_receive_analysis(">>> Packet size invalid");
        return -1;
    }

    if (!from_queue && s->rx_status == RX_SUSPENDED) {
        trace_i82596_receive_analysis(">>> Receiving is suspended");
        return -1;
    }

    if (s->rx_status != RX_READY && s->rx_status != RX_SUSPENDED) {
        trace_i82596_receive_analysis(">>> RU not ready");
        return -1;
    }

    if (!s->lnkst) {
        trace_i82596_receive_analysis(">>> Link is down");
        return -1;
    }

    return 1;
}

static bool i82596_check_packet_filter(I82596State *s, const uint8_t *buf,
                                        uint16_t *is_broadcast)
{
    static const uint8_t broadcast_macaddr[6] = {
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff };

    if (I596_PROMISC || I596_LOOPBACK) {
        trace_i82596_receive_analysis(
            ">>> packet received in promiscuous mode");
        return true;
    } else {
        if (!memcmp(buf, broadcast_macaddr, 6)) {
            /* broadcast address */
            if (I596_BC_DISABLE) {
                trace_i82596_receive_analysis(">>> broadcast packet rejected");
                return false;
            }
            trace_i82596_receive_analysis(">>> broadcast packet received");
            *is_broadcast = 1;
            return true;
        } else if (buf[0] & 0x01) {
            /* multicast */
            if (!I596_MC_ALL) {
                trace_i82596_receive_analysis(">>> multicast packet rejected");
                return false;
            }

            int mcast_idx = (net_crc32(buf, ETH_ALEN) & BITS(7, 2)) >> 2;
            assert(mcast_idx < 8 * sizeof(s->mult));

            if (!(s->mult[mcast_idx >> 3] & (1 << (mcast_idx & 7)))) {
                trace_i82596_receive_analysis(">>> multicast address mismatch");
                return false;
            }

            trace_i82596_receive_analysis(">>> multicast packet received");
            *is_broadcast = 1;
            return true;
        } else if (!memcmp(s->conf.macaddr.a, buf, 6)) {
            /* match */
            trace_i82596_receive_analysis(
                ">>> physical address matching packet received");
            return true;
        } else {
            trace_i82596_receive_analysis(">>> unknown packet");
            return false;
        }
    }
}

/* MONITOR MODE */
static bool i82596_monitor(I82596State *s, const uint8_t *buf, size_t sz,
                           bool packet_passes_filter)
{
    if (I596_MONITOR_MODE == MONITOR_DISABLED) {
        return true;
    }
    if (sz < I596_MIN_FRAME_LEN) {
        s->short_fr_error++;
    }
    if ((sz % 2) != 0) {
        s->align_err++;
    }

    switch (I596_MONITOR_MODE) {
    case MONITOR_NORMAL: /* No monitor, just add to total frames */
        if (packet_passes_filter) {
            s->total_good_frames++;
            return true;
        } else {
            return false;
        }
        break;
    case MONITOR_FILTERED: /* Monitor only filtered packets */
        s->total_frames++;
        if (packet_passes_filter) {
            s->total_good_frames++;
        }
        return false;
    case MONITOR_ALL: /* Monitor all packets */
        s->total_frames++;
        if (packet_passes_filter) {
            s->total_good_frames++;
        }
        return false;

    default:
        return true;
    }
}

static void i82596_update_rx_state(I82596State *s, int new_state)
{
    if (s->rx_status != new_state) {
        trace_i82596_rx_state_change(s->rx_status, new_state);
    }

    s->rx_status = new_state;

    switch (new_state) {
    case RX_NO_RESOURCES:
        if (!s->rnr_signaled) {
            s->scb_status |= SCB_STATUS_RNR;
            s->rnr_signaled = true;
        }
        break;
    case RX_SUSPENDED:
        if (!s->rnr_signaled) {
            s->scb_status |= SCB_STATUS_RNR;
            s->rnr_signaled = true;
        }

        if (s->queue_count > 0 && !s->flushing_queue) {
            i82596_flush_packet_queue(s);
            if (s->queue_count > 0) {
                timer_mod(s->flush_queue_timer,
                          qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 50000);
            }
        }
        break;
    case RX_READY:
        /* When RU becomes ready, flush buffered packets */
        if (s->queue_count > 0) {
            i82596_flush_packet_queue(s);
        }
        break;
    default:
        break;
    }
}

static void i82596_rx_store_frame_header(I82596State *s,
                                         struct i82596_rx_descriptor *rfd,
                                         const uint8_t *buf, size_t size)
{
    memcpy(rfd->dest_addr, buf, 6);
    if (size >= 12) {
        memcpy(rfd->src_addr, buf + 6, 6);
    }
    if (size >= 14) {
        rfd->length_field = (buf[12] << 8) | buf[13];
    }
}

static size_t i82596_rx_copy_to_rfd(I82596State *s, hwaddr rfd_addr,
                                    const uint8_t *buf, size_t size,
                                    size_t rfd_size)
{
    size_t to_copy = MIN(size, rfd_size);
    size_t data_offset = 0x1E; /* Bypassing the header */

    if (to_copy > 0) {
        address_space_write(&address_space_memory, rfd_addr + data_offset,
                           MEMTXATTRS_UNSPECIFIED, buf, to_copy);
    }
    return to_copy;
}

static size_t i82596_rx_copy_to_rbds(I82596State *s, hwaddr rbd_addr,
                                     const uint8_t *buf, size_t size,
                                     bool *out_of_resources,
                                     hwaddr *remaining_rbd)
{
    size_t bytes_copied = 0;
    hwaddr current_rbd = rbd_addr;
    *out_of_resources = false;
    *remaining_rbd = I596_NULL;

    while (bytes_copied < size && current_rbd != I596_NULL &&
           current_rbd != 0) {
        struct i82596_rx_buffer_desc rbd;
        i82596_rbd_read(s, current_rbd, &rbd);
        trace_i82596_rx_rbd(current_rbd, rbd.actual_count, rbd.buffer_addr,
                            rbd.size);
        if (rbd.size & 0x4000) {  /* P bit set */
            break;
        }

        uint16_t buf_size = rbd.size & 0x3FFF;

        if (buf_size == 0) {
            current_rbd = i82596_translate_address(s, rbd.next_rbd_addr, false);
            continue;
        }

        hwaddr buf_addr = i82596_translate_address(s, rbd.buffer_addr, true);
        if (buf_addr == 0 || buf_addr == I596_NULL) {
            *out_of_resources = true;
            break;
        }
        size_t remaining = size - bytes_copied;
        size_t to_copy = MIN(remaining, buf_size);
        if (to_copy > 0) {
            address_space_write(&address_space_memory, buf_addr,
                               MEMTXATTRS_UNSPECIFIED,
                               buf + bytes_copied, to_copy);
            bytes_copied += to_copy;
        }
        rbd.actual_count = to_copy | 0x4000;  /* Set F (filled) bit */
        if (bytes_copied >= size) {
            rbd.actual_count |= 0x8000;  /* Set EOF bit (bit 15) */
        }
        i82596_rbd_write(s, current_rbd, &rbd);
        if (rbd.size & CMD_EOL) {  /* EL bit */
            if (bytes_copied < size) {
                *out_of_resources = true;
            }
            current_rbd = I596_NULL;
            break;
        }
        current_rbd = i82596_translate_address(s, rbd.next_rbd_addr, false);
    }

    *remaining_rbd = current_rbd;
    return bytes_copied;
}

static inline size_t i82596_get_crc_size(I82596State *s)
{
    return I596_CRC16_32 ? 4 : 2;
}

static ssize_t i82596_receive_packet(I82596State *s, const uint8_t *buf,
                                      size_t size, bool from_queue)
{
    struct i82596_rx_descriptor rfd;
    uint32_t rfd_addr, rbd_addr;
    uint16_t rx_status = 0;
    uint16_t is_broadcast = 0;
    bool packet_completed = true;
    bool simplified_mode = false;
    size_t frame_size = size;
    size_t payload_size = 0;
    size_t bytes_copied = 0;
    const uint8_t *packet_data = buf;
    bool out_of_resources = false;
    size_t crc_size = i82596_get_crc_size(s);

    trace_i82596_receive_packet(buf, size);

    if (i82596_validate_receive_state(s, &size, from_queue) < 0) {
        return -1;
    }

    bool passes_filter = i82596_check_packet_filter(s, buf, &is_broadcast);

    if (!i82596_monitor(s, buf, size, passes_filter) && (!passes_filter)) {
        return size;
    }


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

    if (rfd_addr == 0 || rfd_addr == I596_NULL) {
        i82596_update_rx_state(s, RX_NO_RESOURCES);
        s->resource_err++;
        set_uint16(s->scb, get_uint16(s->scb) | SCB_STATUS_RNR);
        i82596_update_scb_irq(s, true);
        return -1;
    }

    i82596_rx_rfd_read(s, rfd_addr, &rfd);
    trace_i82596_rx_rfd(rfd_addr, rfd.status_bits, rfd.command,
                        rfd.link, rfd.rbd_addr);

    s->current_rx_desc = rfd_addr;

    if (rfd.status_bits & STAT_C) {
        return -1;
    }

    /* 0: Simplified Mode 1: Flexible Mode */
    simplified_mode = !(rfd.command & CMD_FLEX);

    set_uint16(rfd_addr, STAT_B);

    if (frame_size < 14) {
        trace_i82596_rx_short_frame(frame_size);
        rx_status |= RX_LENGTH_ERRORS;
        i82596_record_error(s, RX_LENGTH_ERRORS, false);
        s->short_fr_error++;
        packet_completed = false;
        goto rx_complete;
    }

    payload_size = frame_size;
    do {
        if (simplified_mode && I596_LOOPBACK) {
            uint16_t rfd_size = rfd.size & 0x3FFF;

            if (rfd_size % 2 != 0) {
                rx_status |= RX_LENGTH_ERRORS;
                i82596_record_error(s, RX_LENGTH_ERRORS, false);
                s->align_err++;
                packet_completed = false;
                goto rx_complete;
            }

            if (payload_size > rfd_size) {
                rx_status |= RFD_STATUS_TRUNC;
                payload_size = rfd_size;
                packet_completed = !SAVE_BAD_FRAMES ? false : true;
            }

            if (payload_size > 0) {
                bytes_copied = i82596_rx_copy_to_rfd(s, rfd_addr, packet_data,
                                                      payload_size, rfd_size);
            }

            i82596_rx_store_frame_header(s, &rfd, packet_data, frame_size);

        } else {
            uint16_t rfd_size = rfd.size & 0x3FFF; /* SIZE_MASK */
            size_t rfd_frame_size = 0;
            size_t remaining_to_copy = payload_size - bytes_copied;
            if (rfd_size > 0 && remaining_to_copy > 0) {
                size_t data_offset = 0x10;

                rfd_frame_size = MIN(remaining_to_copy, rfd_size);
                address_space_write(&address_space_memory,
                                    rfd_addr + data_offset,
                                    MEMTXATTRS_UNSPECIFIED,
                                    packet_data + bytes_copied,
                                    rfd_frame_size);
                bytes_copied += 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);

                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;
                    break;
                } else {
                    hwaddr remaining_rbd = I596_NULL;
                    size_t rbd_bytes = i82596_rx_copy_to_rbds(
                                            s, rbd_addr,
                                            packet_data + bytes_copied,
                                            remaining,
                                            &out_of_resources,
                                            &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);
                        }
                    }

                    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;
                        break;
                    }

                    if (bytes_copied < payload_size) {
                        trace_i82596_rx_incomplete(bytes_copied, payload_size);
                        rx_status |= RFD_STATUS_TRUNC;
                        packet_completed = true;
                        break;
                    }
                }
            }
        }

    } while (bytes_copied < payload_size);

rx_complete:
    if (I596_CRCINM && !I596_LOOPBACK && packet_completed) {
        uint8_t crc_data[4];
        size_t crc_len = crc_size;

        if (I596_CRC16_32) {
            uint32_t crc = crc32(~0, packet_data, frame_size);
            crc = cpu_to_be32(crc);
            memcpy(crc_data, &crc, 4);
        } else {
            uint16_t crc = i82596_calculate_crc16(packet_data, frame_size);
            crc = cpu_to_be16(crc);
            memcpy(crc_data, &crc, 2);
        }

        if (simplified_mode) {
            address_space_write(&address_space_memory,
                                rfd_addr + 0x1E + bytes_copied,
                                MEMTXATTRS_UNSPECIFIED, crc_data, crc_len);
        }
    }

    if (packet_completed) {
        rx_status |= STAT_C | STAT_OK;
        if (is_broadcast) {
            rx_status |= 0x0001;
        }
    } else {
        rx_status |= STAT_B;
    }

    rfd.status_bits = rx_status & ~STAT_B;
    rfd.actual_count = (bytes_copied & 0x3FFF) | 0x4000;
    if (packet_completed) {
        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 (rfd.command & CMD_EOL) {
        i82596_update_rx_state(s, RX_SUSPENDED);
        return size;
    }

    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;
        i82596_update_scb_irq(s, true);
    }
    trace_i82596_rx_complete(s->crc_err, s->align_err, s->resource_err);
    return size;
}

ssize_t i82596_receive(NetClientState *nc, const uint8_t *buf, size_t size)
{
    I82596State *s = qemu_get_nic_opaque(nc);

    if (!I596_FULL_DUPLEX && !s->throttle_state) {
        if (s->queue_count < PACKET_QUEUE_SIZE) {
            goto queue_packet;
        }
        trace_i82596_receive_suspended();
        return 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;
        }
queue_packet:
        if (size <= PKT_BUF_SZ) {
            memcpy(s->packet_queue[s->queue_head], buf, size);
            s->packet_queue_len[s->queue_head] = size;
            s->queue_head = (s->queue_head + 1) % PACKET_QUEUE_SIZE;
            s->queue_count++;
        }
        return size;
    }

    return i82596_receive_packet(s, buf, size, false);
}

ssize_t i82596_receive_iov(NetClientState *nc, const struct iovec *iov,
                            int iovcnt)
{
    size_t sz = 0;
    uint8_t *buf;
    int i;
    for (i = 0; i < iovcnt; i++) {
        sz += iov[i].iov_len;
    }
    trace_i82596_receive_iov(sz, iovcnt);
    if (sz == 0) {
        return -1;
    }
    buf = g_malloc(sz);
    if (!buf) {
        return -1;
    }
    size_t offset = 0;
    for (i = 0; i < iovcnt; i++) {
        if (iov[i].iov_base == NULL) {
            g_free(buf);
            return -1;
        }
        memcpy(buf + offset, iov[i].iov_base, iov[i].iov_len);
        offset += iov[i].iov_len;
    }
    i82596_receive(nc, buf, sz);
    g_free(buf);
    return sz;
}

static void set_individual_address(I82596State *s, uint32_t addr)
{
    NetClientState *nc;
    uint8_t *m;

    nc = qemu_get_queue(s->nic);
    m = s->conf.macaddr.a;
    address_space_read(&address_space_memory, addr + 8,
                       MEMTXATTRS_UNSPECIFIED, m, ETH_ALEN);
    qemu_format_nic_info_str(nc, m);
    trace_i82596_new_mac(nc->info_str);
}

static void set_multicast_list(I82596State *s, uint32_t addr)
{
    uint16_t mc_count, i;

    memset(&s->mult[0], 0, sizeof(s->mult));
    mc_count = get_uint16(addr + 8) / ETH_ALEN;
    addr += 10;
    if (mc_count > MAX_MC_CNT) {
        mc_count = MAX_MC_CNT;
    }
    for (i = 0; i < mc_count; i++) {
        uint8_t multicast_addr[ETH_ALEN];
        address_space_read(&address_space_memory, addr + i * ETH_ALEN,
                           MEMTXATTRS_UNSPECIFIED, multicast_addr, ETH_ALEN);
        unsigned mcast_idx = (net_crc32(multicast_addr, ETH_ALEN) &
                              BITS(7, 2)) >> 2;
        assert(mcast_idx < 8 * sizeof(s->mult));
        s->mult[mcast_idx >> 3] |= (1 << (mcast_idx & 7));
    }
    trace_i82596_set_multicast(mc_count);
}

void i82596_set_link_status(NetClientState *nc)
{
    I82596State *s = qemu_get_nic_opaque(nc);
    bool was_up = s->lnkst != 0;

    s->lnkst = nc->link_down ? 0 : 0x8000;
    bool is_up = s->lnkst != 0;

    if (!was_up && is_up && s->rx_status == RX_READY) {
        qemu_flush_queued_packets(qemu_get_queue(s->nic));
    }
}

static bool i82596_check_medium_status(I82596State *s)
{
    if (I596_FULL_DUPLEX) {
        return true;
    }

    if (!s->throttle_state) {
        return false;
    }

    if (!I596_LOOPBACK && (qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) % 100 < 5)) {
        s->collision_events++;
        return false;
    }

    return true;
}

static int i82596_csma_backoff(I82596State *s, int retry_count)
{
    int backoff_factor, slot_count, backoff_time;

    backoff_factor = MIN(retry_count + 1, CSMA_BACKOFF_LIMIT);
    slot_count = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) % (1 << backoff_factor);
    backoff_time = slot_count * CSMA_SLOT_TIME;

    return backoff_time;
}

static uint16_t i82596_calculate_crc16(const uint8_t *data, size_t len)
{
    uint16_t crc = 0xFFFF;
    size_t i, j;

    for (i = 0; i < len; i++) {
        crc ^= data[i] << 8;
        for (j = 0; j < 8; j++) {
            if (crc & 0x8000) {
                crc = (crc << 1) ^ 0x1021;
            } else {
                crc <<= 1;
            }
        }
    }
    return crc;
}

static size_t i82596_append_crc(I82596State *s, uint8_t *buffer, size_t len)
{
    if (len + 4 > PKT_BUF_SZ) {
        return len;
    }

    if (I596_CRC16_32) {
        uint32_t crc = crc32(~0, buffer, len);
        crc = cpu_to_be32(crc);
        memcpy(&buffer[len], &crc, sizeof(crc));
        return len + sizeof(crc);
    } else {
        uint16_t crc = i82596_calculate_crc16(buffer, len);
        crc = cpu_to_be16(crc);
        memcpy(&buffer[len], &crc, sizeof(crc));
        return len + sizeof(crc);
    }
}

static void i82596_update_statistics(I82596State *s, bool is_tx,
                                      uint16_t error_flags,
                                      uint16_t collision_count)
{
    if (is_tx) {
        if (collision_count > 0) {
            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);
        }
    }
}

/* Bus Throttle Functionality */
static void i82596_bus_throttle_timer(void *opaque)
{
    I82596State *s = opaque;

    if (s->throttle_state) {
        s->throttle_state = false;
        if (s->t_off > 0) {
            timer_mod(s->throttle_timer,
                      qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
                      s->t_off * NANOSECONDS_PER_MICROSECOND);
        } else {
            s->throttle_state = true;
        }
    } else {
        s->throttle_state = true;
        if (s->t_on > 0 && s->t_on != 0xFFFF) {
            timer_mod(s->throttle_timer,
                      qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
                      s->t_on * NANOSECONDS_PER_MICROSECOND);
        }
    }
}

static void i82596_load_throttle_timers(I82596State *s, bool start_now)
{
    uint16_t previous_t_on = s->t_on;
    uint16_t previous_t_off = s->t_off;
    s->t_on = get_uint16(s->scb + 36);
    s->t_off = get_uint16(s->scb + 38);

    bool values_changed = (s->t_on != previous_t_on ||
                           s->t_off != previous_t_off);
    if (start_now || (values_changed && s->throttle_timer)) {
        timer_del(s->throttle_timer);
        s->throttle_state = true;
        if (s->t_on > 0 && s->t_on != 0xFFFF && !I596_FULL_DUPLEX) {
            timer_mod(s->throttle_timer,
                      qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
                      s->t_on * NANOSECONDS_PER_MICROSECOND);
        }
    }
}

static void write_uint16(uint8_t *buffer, int offset, uint16_t value)
{
    buffer[offset] = value >> 8;
    buffer[offset + 1] = value & 0xFF;
}
static void write_uint32(uint8_t *buffer, int offset, uint32_t value)
{
    write_uint16(buffer, offset, value >> 16);
    write_uint16(buffer, offset + 2, value & 0xFFFF);
}

static void i82596_init_dump_area(I82596State *s, uint8_t *buffer)
{
    memset(buffer, 0, DUMP_BUF_SZ);

    printf("This is the dump area function for i82596 QEMU side\n"
            "If you are seeing this message, please contact:\n"
            "Soumyajyotii Sarkar <soumyajyotisarkar23@gmail.com>\n"
            "With the process in which you encountered this issue:\n"
            "This still needs developement so,\n"
            "I will be more than delighted to help you out!\n"
        );

    write_uint16(buffer, 0x00, (s->config[5] << 8) | s->config[4]);
    write_uint16(buffer, 0x02, (s->config[3] << 8) | s->config[2]);
    write_uint16(buffer, 0x04, (s->config[9] << 8) | s->config[8]);
    write_uint16(buffer, 0x06, (s->config[7] << 8) | s->config[6]);
    write_uint16(buffer, 0x08, (s->config[13] << 8) | s->config[12]);
    write_uint16(buffer, 0x0A, (s->config[11] << 8) | s->config[10]);

    buffer[0x0C] = s->conf.macaddr.a[0];
    buffer[0x0D] = s->conf.macaddr.a[1];
    buffer[0x10] = s->conf.macaddr.a[2];
    buffer[0x11] = s->conf.macaddr.a[3];
    buffer[0x12] = s->conf.macaddr.a[4];
    buffer[0x13] = s->conf.macaddr.a[5];

    if (s->last_tx_len > 0) {
        uint32_t tx_crc = crc32(~0, s->tx_buffer, s->last_tx_len);
        write_uint16(buffer, 0x14, tx_crc & 0xFFFF);
        write_uint16(buffer, 0x16, tx_crc >> 16);
    }

    memcpy(&buffer[0x24], s->mult, sizeof(s->mult));

    buffer[0xB0] = s->cu_status;
    buffer[0xB1] = s->rx_status;

    write_uint32(buffer, 0xB4, s->crc_err);
    write_uint32(buffer, 0xB8, s->align_err);
    write_uint32(buffer, 0xBC, s->resource_err);
    write_uint32(buffer, 0xC0, s->over_err);

    write_uint32(buffer, 0xC4, s->short_fr_error);
    write_uint32(buffer, 0xC8, s->total_frames);
    write_uint32(buffer, 0xCC, s->total_good_frames);

    buffer[0xD0] = I596_PROMISC ? 1 : 0;
    buffer[0xD1] = I596_BC_DISABLE ? 1 : 0;
    buffer[0xD2] = I596_FULL_DUPLEX ? 1 : 0;
    buffer[0xD3] = I596_LOOPBACK;

    uint8_t mc_count = 0;
    for (int i = 0; i < sizeof(s->mult); i++) {
        uint8_t byte = s->mult[i];
        while (byte) {
            if (byte & 0x01) {
                mc_count++;
            }
            byte >>= 1;
        }
    }
    buffer[0xD4] = mc_count;
    buffer[0xD5] = I596_NOCRC_INS ? 1 : 0;
    buffer[0xD6] = I596_CRC16_32 ? 1 : 0;

    write_uint16(buffer, 0xD8, s->lnkst);
    buffer[0xDA] = I596_MONITOR_MODE;
    write_uint32(buffer, 0xDC, s->collision_events);

    write_uint16(buffer, 0x110, s->t_on);
    write_uint16(buffer, 0x112, s->t_off);
    write_uint16(buffer, 0x114, s->throttle_state ? 0x0001 : 0x0000);
    write_uint16(buffer, 0x120, s->sysbus);
    write_uint16(buffer, 0x128, s->scb_status);
    write_uint32(buffer, 0, 0xFFFF0000);
}

static void i82596_port_dump(I82596State *s, uint32_t dump_addr)
{
    uint8_t dump_buffer[DUMP_BUF_SZ];

    i82596_init_dump_area(s, dump_buffer);

    address_space_write(&address_space_memory, dump_addr,
                      MEMTXATTRS_UNSPECIFIED, dump_buffer, sizeof(dump_buffer));

    set_uint32(dump_addr, 0xFFFF0000);
    s->scb_status |= SCB_STATUS_CX;
    s->send_irq = 1;
}

static void i82596_command_dump(I82596State *s, uint32_t cmd_addr)
{
    uint32_t dump_addr;
    uint8_t dump_buffer[DUMP_BUF_SZ];
    uint16_t cmd = get_uint16(cmd_addr + 2);
    uint16_t status;

    dump_addr = get_uint32(cmd_addr + 8);

    i82596_init_dump_area(s, dump_buffer);
    address_space_write(&address_space_memory, dump_addr,
                      MEMTXATTRS_UNSPECIFIED, dump_buffer, sizeof(dump_buffer));
    status = STAT_C | STAT_OK;
    set_uint16(cmd_addr, status);
    if (cmd & CMD_INTR) {
        s->scb_status |= SCB_STATUS_CX;
        s->send_irq = 1;
    }
    if (cmd & CMD_SUSP) {
        s->cu_status = CU_SUSPENDED;
        s->scb_status |= SCB_STATUS_CNA;
    }
}

static void i82596_configure(I82596State *s, uint32_t addr)
{
    uint8_t byte_cnt;
    byte_cnt = get_byte(addr + 8) & 0x0f;
    byte_cnt = MAX(byte_cnt, 4);
    byte_cnt = MIN(byte_cnt, sizeof(s->config));
    s->config[2] &= 0x82; /* mask valid bits */
    s->config[2] |= 0x40;
    s->config[7]  &= 0xf7; /* clear zero bit */

    address_space_read(&address_space_memory, addr + 8,
                       MEMTXATTRS_UNSPECIFIED, s->config, byte_cnt);

    if (byte_cnt > 12) {
        s->config[12] &= 0x40;

        if (byte_cnt > 11) {
            uint8_t monitor_mode = I596_MONITOR_MODE;
            s->config[11] &= ~0xC0; /* Clear bits 6-7 */
            s->config[11] |= (monitor_mode << 6); /* Set monitor mode */
        }
    }

    if (s->rx_status == RX_READY) {
        timer_mod(s->flush_queue_timer,
            qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 100000000);
    }

    s->scb_status |= SCB_STATUS_CNA;
    s->config[13] |= 0x3f;
    qemu_set_irq(s->irq, 1);
}

static void i82596_update_scb_irq(I82596State *s, bool trigger)
{
    update_scb_status(s);

    if (trigger) {
        s->send_irq = 1;
        qemu_set_irq(s->irq, 1);
    }
}

static void i82596_update_cu_status(I82596State *s, uint16_t cmd_status,
                                     bool generate_interrupt)
{
    if (cmd_status & STAT_C) {
        if (cmd_status & STAT_OK) {
            if (s->cu_status == CU_ACTIVE && s->cmd_p == I596_NULL) {
                s->cu_status = CU_IDLE;
                s->scb_status |= SCB_STATUS_CNA;
            }
        } else {
            s->cu_status = CU_IDLE;
            s->scb_status |= SCB_STATUS_CNA;
        }

        if (generate_interrupt) {
            s->scb_status |= SCB_STATUS_CX;
            i82596_update_scb_irq(s, true);
        }
    }

    update_scb_status(s);
}

/**
 * Update SCB Status
 * Synchronizes device state with SCB status word and statistics counters.
 * This function is called frequently to keep the kernel driver updated.
 */
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);
    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)
{
    while (s->cu_status == CU_ACTIVE && s->cmd_p != I596_NULL &&
           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);
            if (next == 0 || next == s->cmd_p) {
                s->cmd_p = I596_NULL;
                s->cu_status = CU_IDLE;
                s->scb_status |= SCB_STATUS_CNA;
                break;
            }
            s->cmd_p = i82596_translate_address(s, next, false);
            continue;
        }
        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);
        next_addr = (next_addr == 0) ? I596_NULL :
                    i82596_translate_address(s, next_addr, false);
        switch (cmd & CMD_MASK) {
        case CmdNOp:
            break;
        case CmdSASetup:
            set_individual_address(s, s->cmd_p);
            break;
        case CmdConfigure:
            i82596_configure(s, s->cmd_p);
            break;
        case CmdTDR:
            set_uint32(s->cmd_p + 8, s->lnkst);
            break;
        case CmdTx:
            i82596_transmit(s, s->cmd_p);
            goto skip_status_update;
        case CmdMulticastList:
            set_multicast_list(s, s->cmd_p);
            break;
        case CmdDump:
            i82596_command_dump(s, s->cmd_p);
            break;
        case CmdDiagnose:
            break;
        default:
            printf("CMD_LOOP: Unknown command %d\n", cmd & CMD_MASK);
            break;
        }

        status = get_uint16(s->cmd_p);
        if (!(status & STAT_C)) {
            set_uint16(s->cmd_p, STAT_C | STAT_OK);
        }

skip_status_update:
        if (cmd & CMD_INTR) {
            s->scb_status |= SCB_STATUS_CX;
            s->send_irq = 1;
        }

        bool stop = false;

        if (cmd & CMD_SUSP) {
            s->cu_status = CU_SUSPENDED;
            s->scb_status |= SCB_STATUS_CNA;
            stop = true;
        }

        if (cmd & CMD_EOL) {
            s->cmd_p = I596_NULL;
            s->cu_status = CU_IDLE;
            s->scb_status |= SCB_STATUS_CNA;
            stop = true;
        } else if (!stop) {
            if (next_addr == 0 || next_addr == I596_NULL ||
                next_addr == s->cmd_p) {
                s->cmd_p = I596_NULL;
                s->cu_status = CU_IDLE;
                s->scb_status |= SCB_STATUS_CNA;
                stop = true;
            } else {
                s->cmd_p = next_addr;
            }
        }

        update_scb_status(s);

        if (stop || s->cu_status != CU_ACTIVE) {
            break;
        }
    }

    update_scb_status(s);

    if (s->rx_status == RX_READY && s->nic) {
        qemu_flush_queued_packets(qemu_get_queue(s->nic));
    }

}

static void examine_scb(I82596State *s)
{
    uint16_t command = get_uint16(s->scb + 2);
    uint8_t cuc = (command >> 8) & 0x7;
    uint8_t ruc = (command >> 4) & 0x7;

    trace_i82596_scb_command(cuc, ruc);

    set_uint16(s->scb + 2, 0);
    s->scb_status &= ~(command & SCB_ACK_MASK);

    if (command & SCB_STATUS_RNR) {
        s->rnr_signaled = false;
    }

    /* Process Command Unit (CU) commands */
    switch (cuc) {
    case SCB_CUC_NOP:
        break;

    case SCB_CUC_START: {
        uint32_t cmd_ptr = get_uint32(s->scb + 4);
        s->cmd_p = i82596_translate_address(s, cmd_ptr, false);
        s->cu_status = CU_ACTIVE;
        break;
    }

    case SCB_CUC_RESUME:
        if (s->cu_status == CU_SUSPENDED) {
            s->cu_status = CU_ACTIVE;
        }
        break;

    case SCB_CUC_SUSPEND:
        s->cu_status = CU_SUSPENDED;
        s->scb_status |= SCB_STATUS_CNA;
        break;

    case SCB_CUC_ABORT:
        s->cu_status = CU_IDLE;
        s->scb_status |= SCB_STATUS_CNA;
        break;

    case SCB_CUC_LOAD_THROTTLE: {
        bool external_trigger = (s->sysbus & I82586_MODE);
        i82596_load_throttle_timers(s, !external_trigger);
        break;
    }

    case SCB_CUC_LOAD_START:
        i82596_load_throttle_timers(s, true);
        break;
    }

    /* Process Receive Unit (RU) commands */
    switch (ruc) {
    case SCB_RUC_NOP:
        break;

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

        if (rfd == 0 || rfd == I596_NULL) {
            s->rx_status = RX_NO_RESOURCES;
            s->scb_status |= SCB_STATUS_RNR;
            break;
        }

        /* Find first usable RFD with valid RBD */
        struct i82596_rx_descriptor test_rfd;
        hwaddr test_rfd_addr = rfd;
        uint32_t test_rfd_log = rfd_log;
        hwaddr first_usable_rfd = 0;
        uint32_t first_usable_rfd_log = 0;
        bool found = false;

        for (int i = 0; i < 10 && test_rfd_addr != 0 &&
             test_rfd_addr != I596_NULL; i++) {
            i82596_rx_rfd_read(s, test_rfd_addr, &test_rfd);

            if (test_rfd.command & CMD_FLEX) {
                hwaddr rbd = i82596_translate_address(s, test_rfd.rbd_addr,
                                                      false);
                if (rbd != I596_NULL && rbd != 0) {
                    first_usable_rfd = test_rfd_addr;
                    first_usable_rfd_log = test_rfd_log;
                    found = true;
                    break;
                }
            }

            test_rfd_log = test_rfd.link;
            test_rfd_addr = i82596_translate_address(s, test_rfd.link, false);
        }

        if (found) {
            s->current_rx_desc = first_usable_rfd;
            s->last_good_rfa = first_usable_rfd_log;
            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);
            }

            qemu_flush_queued_packets(qemu_get_queue(s->nic));
        } else {
            s->rx_status = RX_NO_RESOURCES;
            s->scb_status |= SCB_STATUS_RNR;
        }
        break;
    }

    case SCB_RUC_RESUME:
        if (s->rx_status == RX_SUSPENDED) {
            i82596_update_rx_state(s, RX_READY);
            if (s->queue_count > 0) {
                trace_i82596_flush_queue(s->queue_count);
                i82596_flush_packet_queue(s);
            }
            qemu_flush_queued_packets(qemu_get_queue(s->nic));
        }
        break;

    case SCB_RUC_SUSPEND:
        s->rx_status = RX_SUSPENDED;
        s->scb_status |= SCB_STATUS_RNR;
        break;

    case SCB_RUC_ABORT:
        s->rx_status = RX_IDLE;
        s->scb_status |= SCB_STATUS_RNR;
        break;
    }

    if (command & 0x80) {
        i82596_s_reset(s);
        return;
    }
    if (s->cu_status == CU_ACTIVE) {
        if (s->cmd_p == I596_NULL) {
            s->cmd_p = get_uint32(s->scb + 4);
        }
        update_scb_status(s);
        command_loop(s);
    } else {
        update_scb_status(s);
    }
}

static void signal_ca(I82596State *s)
{
    if (s->scp) {
        /* CA after reset -> initialize with new SCP */
        s->sysbus = get_byte(s->scp + 3);
        s->mode = (s->sysbus >> 1) & 0x03;  /* Extract mode bits (m0, m1) */
        s->iscp = get_uint32(s->scp + 8);

        s->scb = get_uint32(s->iscp + 4);

        s->scb_base = (s->mode == I82596_MODE_LINEAR) ? 0 :
                      get_uint32(s->iscp + 8);
        s->scb = i82596_translate_address(s, s->scb, false);
        trace_i82596_ca_init(s->scb, s->mode, s->scb_base);

        /*
         * Complete initialization sequence:
         * - Clear BUSY flag in ISCP
         * - Set CX and CNA in SCB status
         * - Clear SCB command word
         * - Signal interrupt
         */
        set_byte(s->iscp + 1, 0);
        s->scb_status |= SCB_STATUS_CX | SCB_STATUS_CNA;
        update_scb_status(s);
        set_uint16(s->scb + 2, 0);
        s->scp = 0;
        qemu_set_irq(s->irq, 1);
        return;
    }

    if (s->ca_active) {
        s->ca++;
        return;
    }
    s->ca_active = 1;
    s->ca++;

    while (s->ca > 0) {
        s->ca--;
        examine_scb(s);
    }

    s->ca_active = 0;

    if (s->send_irq) {
        s->send_irq = 0;
        qemu_set_irq(s->irq, 1);
    }
}

static void i82596_self_test(I82596State *s, uint32_t val)
{
    /*
     * The documentation for the self test is a bit unclear,
     * we are currently doing this and it seems to work.
     */
    set_uint32(val, 0xFFC00000);
    set_uint32(val + 4, 0);

    s->scb_status &= ~SCB_STATUS_CNA;
    s->scb_status |= SCB_STATUS_CNA;

    qemu_set_irq(s->irq, 1);
    update_scb_status(s);
}

/*
 * LASI specific interfaces
 */
static uint32_t bit_align_16(uint32_t val)
{
    return val & ~0x0f;
}

uint32_t i82596_ioport_readw(void *opaque, uint32_t addr)
{
    return -1;
}

void i82596_ioport_writew(void *opaque, uint32_t addr, uint32_t val)
{
    I82596State *s = opaque;
    trace_i82596_ioport_write(addr, val);
    switch (addr) {
    case PORT_RESET:
        i82596_s_reset(s);
        break;
    case PORT_SELFTEST:
        val = bit_align_16(val);
        i82596_self_test(s, val);
        break;
    case PORT_ALTSCP:
        s->scp = bit_align_16(val);
        break;
    case PORT_ALTDUMP:
        trace_i82596_dump(val);
        i82596_port_dump(s, bit_align_16(val));
        break;
    case PORT_CA:
        signal_ca(s);
        break;
    }
}

void i82596_poll(NetClientState *nc, bool enable)
{
    I82596State *s = qemu_get_nic_opaque(nc);

    if (!enable) {
        return;
    }

    if (s->send_irq) {
        qemu_set_irq(s->irq, 1);
    }

    if (s->rx_status == RX_NO_RESOURCES) {
        if (s->cmd_p != I596_NULL) {
            i82596_update_rx_state(s, RX_READY);
            update_scb_status(s);
        }
    }

    if (s->cu_status == CU_ACTIVE && s->cmd_p != I596_NULL) {
        examine_scb(s);
    }
    qemu_set_irq(s->irq, 0);
}

const VMStateDescription vmstate_i82596 = {
    .name = "i82596",
    .version_id = 1,
    .minimum_version_id = 1,
    .fields = (VMStateField[]) {
        VMSTATE_UINT8(mode, I82596State),
        VMSTATE_UINT16(t_on, I82596State),
        VMSTATE_UINT16(t_off, I82596State),
        VMSTATE_BOOL(throttle_state, I82596State),
        VMSTATE_UINT32(iscp, I82596State),
        VMSTATE_UINT8(sysbus, I82596State),
        VMSTATE_UINT32(scb, I82596State),
        VMSTATE_UINT32(scb_base, I82596State),
        VMSTATE_UINT16(scb_status, I82596State),
        VMSTATE_UINT8(cu_status, I82596State),
        VMSTATE_UINT8(rx_status, I82596State),
        VMSTATE_UINT16(lnkst, I82596State),
        VMSTATE_UINT32(cmd_p, I82596State),
        VMSTATE_INT32(ca, I82596State),
        VMSTATE_INT32(ca_active, I82596State),
        VMSTATE_INT32(send_irq, I82596State),
        VMSTATE_BUFFER(mult, I82596State),
        VMSTATE_BUFFER(config, I82596State),
        VMSTATE_BUFFER(tx_buffer, I82596State),
        VMSTATE_UINT32(tx_retry_addr, I82596State),
        VMSTATE_INT32(tx_retry_count, I82596State),
        VMSTATE_UINT32(tx_good_frames, I82596State),
        VMSTATE_UINT32(tx_collisions, I82596State),
        VMSTATE_UINT32(tx_aborted_errors, I82596State),
        VMSTATE_UINT32(last_tx_len, I82596State),
        VMSTATE_UINT32(collision_events, I82596State),
        VMSTATE_UINT32(total_collisions, I82596State),
        VMSTATE_UINT32(crc_err, I82596State),
        VMSTATE_UINT32(align_err, I82596State),
        VMSTATE_UINT32(resource_err, I82596State),
        VMSTATE_UINT32(over_err, I82596State),
        VMSTATE_UINT32(rcvdt_err, I82596State),
        VMSTATE_UINT32(short_fr_error, I82596State),
        VMSTATE_UINT32(total_frames, I82596State),
        VMSTATE_UINT32(total_good_frames, I82596State),
        VMSTATE_BUFFER(rx_buffer, I82596State),
        VMSTATE_UINT16(tx_frame_len, I82596State),
        VMSTATE_UINT16(rx_frame_len, I82596State),
        VMSTATE_UINT64(current_tx_desc, I82596State),
        VMSTATE_UINT64(current_rx_desc, I82596State),
        VMSTATE_UINT32(last_good_rfa, I82596State),
        VMSTATE_INT32(queue_head, I82596State),
        VMSTATE_INT32(queue_tail, I82596State),
        VMSTATE_INT32(queue_count, I82596State),
        VMSTATE_BOOL(rnr_signaled, I82596State),
        VMSTATE_BOOL(flushing_queue, I82596State),
        VMSTATE_END_OF_LIST()
    }
};

static int i82596_flush_packet_queue(I82596State *s)
{
    if (s->flushing_queue) {
        return 0;
    }

    s->flushing_queue = true;
    int processed = 0;

    while (s->queue_count > 0) {
        int tail = s->queue_tail;
        size_t len = s->packet_queue_len[tail];

        ssize_t ret = i82596_receive_packet(s, s->packet_queue[tail], len,
                                             true);

        if (ret < 0) {
            break;
        }

        s->queue_tail = (s->queue_tail + 1) % PACKET_QUEUE_SIZE;
        s->queue_count--;
        processed++;
    }

    s->flushing_queue = false;
    trace_i82596_flush_queue(processed);

    return processed;
}

static void i82596_flush_queue_timer(void *opaque)
{
    I82596State *s = opaque;

    if (s->queue_count == 0) {
        return;
    }

    int processed = i82596_flush_packet_queue(s);

    if (processed > 0 && s->rx_status == RX_READY) {
        qemu_flush_queued_packets(qemu_get_queue(s->nic));
    }

    if (s->queue_count > 0 && s->rx_status != RX_READY) {
        timer_mod(s->flush_queue_timer,
                  qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 50000);
    }
}

void i82596_common_init(DeviceState *dev, I82596State *s, NetClientInfo *info)
{
    if (s->conf.macaddr.a[0] == 0) {
        qemu_macaddr_default_if_unset(&s->conf.macaddr);
    }
    s->nic = qemu_new_nic(info, &s->conf, object_get_typename(OBJECT(dev)),
                dev->id, &dev->mem_reentrancy_guard, s);
    qemu_format_nic_info_str(qemu_get_queue(s->nic), s->conf.macaddr.a);

    if (USE_TIMER) {
        if (!s->flush_queue_timer) {
            s->flush_queue_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
                                        i82596_flush_queue_timer, s);
        }
        if (!s->throttle_timer) {
            s->throttle_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
                                        i82596_bus_throttle_timer, s);
        }
    }

    s->lnkst = 0x8000; /* initial link state: up */
}
