From b2d0bf872d4a40c5f1e326b4bd5aa42ba98567f0 Mon Sep 17 00:00:00 2001 From: Helge Deller Date: Mon, 3 Nov 2025 16:57:21 +0530 Subject: [PATCH 1/3] hw/hppa: Enable LASI i82596 network on 715 machine Enable the i82596 network chip which is included in the LASI multi I/O chip. Since LASI has different start addresses on the various machines, always initialize the LASI components by their offsets. Tested-by: Guenter Roeck Signed-off-by: Soumyajyotii Ssarkar Signed-off-by: Helge Deller --- hw/hppa/hppa_hardware.h | 10 ++-------- hw/hppa/machine.c | 31 +++++++++++++++++-------------- hw/net/lasi_i82596.c | 30 +++++++----------------------- include/hw/net/lasi_82596.h | 3 --- 4 files changed, 26 insertions(+), 48 deletions(-) diff --git a/hw/hppa/hppa_hardware.h b/hw/hppa/hppa_hardware.h index d422af0429..0a89c3ed52 100644 --- a/hw/hppa/hppa_hardware.h +++ b/hw/hppa/hppa_hardware.h @@ -21,14 +21,6 @@ #define DINO_SCSI_HPA 0xfff8c000 #define LASI_HPA_715 0xf0100000 #define LASI_HPA 0xffd00000 -#define LASI_UART_HPA 0xffd05000 -#define LASI_SCSI_HPA 0xffd06000 -#define LASI_LAN_HPA 0xffd07000 -#define LASI_RTC_HPA 0xffd09000 -#define LASI_LPT_HPA 0xffd02000 -#define LASI_AUDIO_HPA 0xffd04000 -#define LASI_PS2KBD_HPA 0xffd08000 -#define LASI_PS2MOU_HPA 0xffd08100 #define LASI_GFX_HPA 0xf8000000 #define ARTIST_FB_ADDR 0xf9000000 #define CPU_HPA 0xfffb0000 @@ -44,6 +36,8 @@ #define SCSI_HPA 0xf1040000 /* emulated SCSI, needs to be in f region */ +#define HPA_DISABLED_DEVICE 1 /* add to HPA to disable */ + /* offsets to DINO HPA: */ #define DINO_PCI_ADDR 0x064 #define DINO_CONFIG_DATA 0x068 diff --git a/hw/hppa/machine.c b/hw/hppa/machine.c index cbca0026ab..8c66eed5a2 100644 --- a/hw/hppa/machine.c +++ b/hw/hppa/machine.c @@ -50,8 +50,6 @@ struct HppaMachineState { #define HPA_POWER_BUTTON (FIRMWARE_END - 0x10) static hwaddr soft_power_reg; -#define enable_lasi_lan() 0 - static DeviceState *lasi_dev; static void hppa_powerdown_req(Notifier *n, void *opaque) @@ -376,13 +374,6 @@ static void machine_HP_common_init_tail(MachineState *machine, PCIBus *pci_bus, } } - /* Network setup. */ - if (lasi_dev) { - lasi_82596_init(addr_space, translate(NULL, LASI_LAN_HPA), - qdev_get_gpio_in(lasi_dev, LASI_IRQ_LAN_HPA), - enable_lasi_lan()); - } - if (pci_bus) { pci_init_nic_devices(pci_bus, mc->default_nic); @@ -595,6 +586,17 @@ static void machine_HP_715_init(MachineState *machine) lasi_ncr710_handle_legacy_cmdline(dev); } + /* LASI i82596 network */ + dev = qemu_create_nic_device(TYPE_LASI_82596, true, "lasi"); + if (dev) { + sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); + sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, + qdev_get_gpio_in(lasi_dev, LASI_IRQ_LAN_HPA)); + memory_region_add_subregion(addr_space, + translate(NULL, LASI_HPA_715 + LASI_LAN), + sysbus_mmio_get_region(SYS_BUS_DEVICE(dev), 0)); + } + /* Add NICs, graphics & load firmware */ machine_HP_common_init_tail(machine, NULL, translate); } @@ -638,7 +640,7 @@ static void machine_HP_B160L_init(MachineState *machine) assert(isa_bus); /* Serial ports: Lasi and Dino use a 7.272727 MHz clock. */ - serial_mm_init(addr_space, translate(NULL, LASI_UART_HPA + 0x800), 0, + serial_mm_init(addr_space, translate(NULL, LASI_HPA + LASI_UART + 0x800), 0, qdev_get_gpio_in(lasi_dev, LASI_IRQ_UART_HPA), 7272727 / 16, serial_hd(0), DEVICE_BIG_ENDIAN); @@ -647,7 +649,8 @@ static void machine_HP_B160L_init(MachineState *machine) serial_hd(1), DEVICE_BIG_ENDIAN); /* Parallel port */ - parallel_mm_init(addr_space, translate(NULL, LASI_LPT_HPA + 0x800), 0, + parallel_mm_init(addr_space, + translate(NULL, LASI_HPA + LASI_LPT + 0x800), 0, qdev_get_gpio_in(lasi_dev, LASI_IRQ_LPT_HPA), parallel_hds[0]); @@ -657,11 +660,11 @@ static void machine_HP_B160L_init(MachineState *machine) sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, qdev_get_gpio_in(lasi_dev, LASI_IRQ_PS2KBD_HPA)); memory_region_add_subregion(addr_space, - translate(NULL, LASI_PS2KBD_HPA), + translate(NULL, LASI_HPA + LASI_PS2), sysbus_mmio_get_region(SYS_BUS_DEVICE(dev), 0)); memory_region_add_subregion(addr_space, - translate(NULL, LASI_PS2KBD_HPA + 0x100), + translate(NULL, LASI_HPA + LASI_PS2 + 0x100), sysbus_mmio_get_region(SYS_BUS_DEVICE(dev), 1)); @@ -832,7 +835,7 @@ static void HP_715_machine_init_class_init(ObjectClass *oc, const void *data) /* can only support up to max. 8 CPUs due inventory major numbers */ mc->max_cpus = MIN_CONST(HPPA_MAX_CPUS, 8); mc->default_ram_size = 256 * MiB; - mc->default_nic = NULL; + mc->default_nic = TYPE_LASI_82596; } diff --git a/hw/net/lasi_i82596.c b/hw/net/lasi_i82596.c index 9e1dd21546..b87cea411a 100644 --- a/hw/net/lasi_i82596.c +++ b/hw/net/lasi_i82596.c @@ -115,32 +115,12 @@ static void lasi_82596_realize(DeviceState *dev, Error **errp) memory_region_init_io(&s->mmio, OBJECT(d), &lasi_82596_mem_ops, d, "lasi_82596-mmio", PA_GET_MACADDR + 4); + sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq); + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mmio); + i82596_common_init(dev, s, &net_lasi_82596_info); } -SysBusI82596State *lasi_82596_init(MemoryRegion *addr_space, hwaddr hpa, - qemu_irq lan_irq, gboolean match_default) -{ - DeviceState *dev; - SysBusI82596State *s; - static const MACAddr HP_MAC = { - .a = { 0x08, 0x00, 0x09, 0xef, 0x34, 0xf6 } }; - - dev = qemu_create_nic_device(TYPE_LASI_82596, match_default, "lasi"); - if (!dev) { - return NULL; - } - - s = SYSBUS_I82596(dev); - s->state.irq = lan_irq; - sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); - s->state.conf.macaddr = HP_MAC; /* set HP MAC prefix */ - - /* LASI 82596 ports in main memory. */ - memory_region_add_subregion(addr_space, hpa, &s->state.mmio); - return s; -} - static void lasi_82596_reset(DeviceState *dev) { SysBusI82596State *d = SYSBUS_I82596(dev); @@ -152,6 +132,10 @@ static void lasi_82596_instance_init(Object *obj) { SysBusI82596State *d = SYSBUS_I82596(obj); I82596State *s = &d->state; + static const MACAddr HP_MAC = { + .a = { 0x08, 0x00, 0x09, 0xef, 0x34, 0xf6 } }; + + s->conf.macaddr = HP_MAC; device_add_bootindex_property(obj, &s->conf.bootindex, "bootindex", "/ethernet-phy@0", diff --git a/include/hw/net/lasi_82596.h b/include/hw/net/lasi_82596.h index 439356ec19..c46a4a137e 100644 --- a/include/hw/net/lasi_82596.h +++ b/include/hw/net/lasi_82596.h @@ -25,7 +25,4 @@ struct SysBusI82596State { int val_index:1; }; -SysBusI82596State *lasi_82596_init(MemoryRegion *addr_space, hwaddr hpa, - qemu_irq irq, gboolean match_default); - #endif From cbf62eef84ab6bd1be3a03269b4a4e9df529985f Mon Sep 17 00:00:00 2001 From: Soumyajyotii Ssarkar Date: Mon, 3 Nov 2025 16:57:22 +0530 Subject: [PATCH 2/3] i82596: Added core infrastructure and helper functions As a part of GSOC 2025 I have done a massive rewrite of what was the 82596 NIC. This has been done to add the missing functionality according to the 82596 Manual and making the code production ready. This patch adds: - comprehensive 82596 constants and configuration macros - address translation for segmented/linear memory modes - error recording and statistics tracking infrastructure - CRC-16/32 calculation and appending functions - CSMA/CD collision detection and backoff logic - bus throttle timer framework - enhanced reset with full state initialization - receive_iov and polling support functions - updated VMState for migration of all new fields Note: This patch primarily includes placeholder code. To achieve full 82596 emulation, the complete 82596 patch series is required. Nevertheless, QEMU is able to load and boot successfully with this patch. Signed-off-by: Soumyajyotii Ssarkar Tested-by: Helge Deller Tested-by: Guenter Roeck Signed-off-by: Helge Deller --- hw/net/i82596.c | 663 ++++++++++++++++++++++++++++++++++++++----- hw/net/i82596.h | 74 ++++- hw/net/lasi_i82596.c | 6 + 3 files changed, 653 insertions(+), 90 deletions(-) diff --git a/hw/net/i82596.c b/hw/net/i82596.c index c1ff3e6c56..859cc88b2e 100644 --- a/hw/net/i82596.c +++ b/hw/net/i82596.c @@ -2,10 +2,38 @@ * QEMU Intel i82596 (Apricot) emulation * * Copyright (c) 2019 Helge Deller - * This work is licensed under the GNU GPL license version 2 or later. * + * Additional functionality added by: + * Soumyajyotii Ssarkar + * 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" @@ -21,50 +49,90 @@ #include "i82596.h" #include /* for crc32 */ +#define ENABLE_DEBUG 0 + #if defined(ENABLE_DEBUG) #define DBG(x) x #else #define DBG(x) do { } while (0) #endif -#define USE_TIMER 0 +#define USE_TIMER 1 -#define BITS(n, m) (((0xffffffffU << (31 - n)) >> (31 - n + m)) << m) - -#define PKT_BUF_SZ 1536 #define MAX_MC_CNT 64 - -#define ISCP_BUSY 0x0001 - #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_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 */ -#define SCB_COMMAND_ACK_MASK \ - (SCB_STATUS_CX | SCB_STATUS_FR | SCB_STATUS_CNA | SCB_STATUS_RNR) +/* 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 -#define RX_IDLE 0 -#define RX_SUSPENDED 1 -#define RX_READY 4 +/* 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_EOL 0x8000 /* The last command of the list, stop. */ -#define CMD_SUSP 0x4000 /* Suspend after doing cmd. */ -#define CMD_INTR 0x2000 /* Interrupt after doing cmd. */ +#define CMD_FLEX 0x0008 +#define CMD_MASK 0x0007 -#define CMD_FLEX 0x0008 /* Enable flexible memory model */ +#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 */ @@ -73,15 +141,60 @@ enum commands { #define I596_EOF 0x8000 #define SIZE_MASK 0x3fff -/* various flags in the chip config registers */ -#define I596_PREFETCH (s->config[0] & 0x80) -#define I596_PROMISC (s->config[8] & 0x01) -#define I596_BC_DISABLE (s->config[8] & 0x02) /* broadcast disable */ -#define I596_NOCRC_INS (s->config[8] & 0x08) -#define I596_CRCINM (s->config[11] & 0x04) /* CRC appended */ -#define I596_MC_ALL (s->config[11] & 0x20) -#define I596_MULTIIA (s->config[13] & 0x40) +#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) { @@ -116,7 +229,44 @@ static void set_uint32(uint32_t addr, uint32_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]; @@ -124,12 +274,122 @@ struct qemu_ether_header { }; #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->current_rx_desc = 0; + s->current_tx_desc = 0; + s->tx_retry_addr = 0; + s->tx_retry_count = 0; + + s->rnr_signaled = false; + s->flushing_queue = false; + + memset(s->tx_buffer, 0, sizeof(s->tx_buffer)); + memset(s->rx_buffer, 0, sizeof(s->rx_buffer)); + s->tx_frame_len = 0; + s->rx_frame_len = 0; +} + +void i82596_h_reset(void *opaque) +{ + I82596State *s = opaque; + + i82596_s_reset(s); +} + +/* + * Address Translation Implementation + * Handles segmented and linear memory modes for i82596. + * Returns physical address for DMA operations. + * Returns I596_NULL (0xffffffff) on invalid addresses. + */ +static inline uint32_t i82596_translate_address(I82596State *s, + uint32_t logical_addr, + bool is_data_buffer) +{ + if (logical_addr == I596_NULL || logical_addr == 0) { + return logical_addr; + } + + switch (s->mode) { + case I82596_MODE_LINEAR: + return logical_addr; + + case I82596_MODE_SEGMENTED: { + uint32_t base = (logical_addr >> 16) & 0xFFFF; + uint32_t offset = logical_addr & 0xFFFF; + + if (is_data_buffer) { + return (base << 4) + offset; + } else { + if (base == 0xFFFF && offset == 0xFFFF) { + return I596_NULL; + } + return s->scb_base + ((base << 4) + offset); + } + } + + case I82586_MODE: + default: + if (is_data_buffer) { + return logical_addr; + } else { + if ((logical_addr & 0xFFFF0000) == 0xFFFF0000) { + return I596_NULL; + } + return s->scb_base + logical_addr; + } + } +} + static void i82596_transmit(I82596State *s, uint32_t addr) { uint32_t tdb_p; /* Transmit Buffer Descriptor */ @@ -223,33 +483,199 @@ static void set_multicast_list(I82596State *s, uint32_t addr) void i82596_set_link_status(NetClientState *nc) { - I82596State *d = qemu_get_nic_opaque(nc); + I82596State *s = qemu_get_nic_opaque(nc); + bool was_up = s->lnkst != 0; - d->lnkst = nc->link_down ? 0 : 0x8000; + 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 G_GNUC_UNUSED 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 G_GNUC_UNUSED 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 G_GNUC_UNUSED 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 G_GNUC_UNUSED 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 G_GNUC_UNUSED 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; + if (s->t_on > 0) { + timer_mod(s->throttle_timer, + qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + + (s->t_on * NANOSECONDS_PER_MICROSECOND)); + } + + if (s->cu_status == CU_ACTIVE || s->rx_status == RX_READY) { + examine_scb(s); + } + } +} + +static int G_GNUC_UNUSED i82596_flush_packet_queue(I82596State *s) +{ + /* Stub for now - will be implemented in Patch 2 */ + return 0; +} + +static void G_GNUC_UNUSED i82596_flush_queue_timer(void *opaque) +{ + /* Stub for now - will be implemented in Patch 2 */ +} + +static void i82596_update_scb_irq(I82596State *s, bool trigger) +{ + if (trigger) { + s->send_irq = 1; + qemu_set_irq(s->irq, 1); + } +} + +static void G_GNUC_UNUSED 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); } static void update_scb_status(I82596State *s) { s->scb_status = (s->scb_status & 0xf000) - | (s->cu_status << 8) | (s->rx_status << 4); + | (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 i82596_s_reset(I82596State *s) -{ - trace_i82596_s_reset(s); - s->scp = 0; - s->scb_status = 0; - s->cu_status = CU_IDLE; - s->rx_status = RX_SUSPENDED; - s->cmd_p = I596_NULL; - s->lnkst = 0x8000; /* initial link state: up */ - s->ca = s->ca_active = 0; - s->send_irq = 0; -} - - static void command_loop(I82596State *s) { uint16_t cmd; @@ -330,17 +756,6 @@ static void command_loop(I82596State *s) qemu_flush_queued_packets(qemu_get_queue(s->nic)); } -static void i82596_flush_queue_timer(void *opaque) -{ - I82596State *s = opaque; - if (0) { - timer_del(s->flush_queue_timer); - qemu_flush_queued_packets(qemu_get_queue(s->nic)); - timer_mod(s->flush_queue_timer, - qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + 1000); - } -} - static void examine_scb(I82596State *s) { uint16_t command, cuc, ruc; @@ -353,7 +768,7 @@ static void examine_scb(I82596State *s) /* and clear the scb command word */ set_uint16(s->scb + 2, 0); - s->scb_status &= ~(command & SCB_COMMAND_ACK_MASK); + s->scb_status &= ~(command & SCB_ACK_MASK); switch (cuc) { case 0: /* no change */ @@ -465,13 +880,6 @@ uint32_t i82596_ioport_readw(void *opaque, uint32_t addr) return -1; } -void i82596_h_reset(void *opaque) -{ - I82596State *s = opaque; - - i82596_s_reset(s); -} - bool i82596_can_receive(NetClientState *nc) { I82596State *s = qemu_get_nic_opaque(nc); @@ -595,7 +1003,6 @@ ssize_t i82596_receive(NetClientState *nc, const uint8_t *buf, size_t sz) rbd = get_uint32(rfd_p + 8); assert(rbd && rbd != I596_NULL); - trace_i82596_receive_packet(len); /* PRINT_PKTHDR("Receive", buf); */ while (len) { @@ -714,14 +1121,113 @@ ssize_t i82596_receive(NetClientState *nc, const uint8_t *buf, size_t sz) return sz; } +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; + } + 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; + } + DBG(PRINT_PKTHDR("Receive IOV:", buf)); + i82596_receive(nc, buf, sz); + g_free(buf); + return sz; +} + +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) { + s->rx_status = 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 = (const VMStateField[]) { + .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_TIMER_PTR(flush_queue_timer, 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() } }; @@ -736,8 +1242,15 @@ void i82596_common_init(DeviceState *dev, I82596State *s, NetClientInfo *info) qemu_format_nic_info_str(qemu_get_queue(s->nic), s->conf.macaddr.a); if (USE_TIMER) { - s->flush_queue_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, - i82596_flush_queue_timer, s); + 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 */ } diff --git a/hw/net/i82596.h b/hw/net/i82596.h index dc1fa1a1dc..67b18f957a 100644 --- a/hw/net/i82596.h +++ b/hw/net/i82596.h @@ -6,11 +6,15 @@ #include "system/memory.h" #include "system/address-spaces.h" -#define PORT_RESET 0x00 /* reset 82596 */ -#define PORT_SELFTEST 0x01 /* selftest */ -#define PORT_ALTSCP 0x02 /* alternate SCB address */ -#define PORT_ALTDUMP 0x03 /* Alternate DUMP address */ -#define PORT_CA 0x10 /* QEMU-internal CA signal */ +#define PACKET_QUEUE_SIZE 64 +#define RX_RING_SIZE 16 +#define PKT_BUF_SZ 1536 + +#define PORT_RESET 0x00 +#define PORT_SELFTEST 0x01 +#define PORT_ALTSCP 0x02 +#define PORT_ALTDUMP 0x03 +#define PORT_CA 0x10 typedef struct I82596State_st I82596State; @@ -21,35 +25,75 @@ struct I82596State_st { NICState *nic; NICConf conf; QEMUTimer *flush_queue_timer; + uint8_t mode; - hwaddr scp; /* pointer to SCP */ + QEMUTimer *throttle_timer; + uint16_t t_on; + uint16_t t_off; + bool throttle_state; + + hwaddr scp; + uint32_t iscp; uint8_t sysbus; - uint32_t scb; /* SCB */ + uint32_t scb; + uint32_t scb_base; uint16_t scb_status; uint8_t cu_status, rx_status; uint16_t lnkst; + uint32_t last_tx_len; + uint32_t collision_events; + uint32_t total_collisions; - uint32_t cmd_p; /* addr of current command */ + uint32_t tx_retry_addr; + int tx_retry_count; + uint32_t tx_good_frames; + uint32_t tx_collisions; + uint32_t tx_aborted_errors; + + uint32_t cmd_p; int ca; int ca_active; int send_irq; - /* Hash register (multicast mask array, multiple individual addresses). */ uint8_t mult[8]; - uint8_t config[14]; /* config bytes from CONFIGURE command */ + uint8_t config[14]; - uint8_t tx_buffer[0x4000]; + uint32_t crc_err; + uint32_t align_err; + uint32_t resource_err; + uint32_t over_err; + uint32_t rcvdt_err; + uint32_t short_fr_error; + uint32_t total_frames; + uint32_t total_good_frames; + + uint8_t tx_buffer[PKT_BUF_SZ]; + uint8_t rx_buffer[PKT_BUF_SZ]; + uint16_t tx_frame_len; + uint16_t rx_frame_len; + + hwaddr current_tx_desc; + hwaddr current_rx_desc; + uint32_t last_good_rfa; + uint8_t packet_queue[PACKET_QUEUE_SIZE][PKT_BUF_SZ]; + size_t packet_queue_len[PACKET_QUEUE_SIZE]; + int queue_head; + int queue_tail; + int queue_count; + bool rnr_signaled; + bool flushing_queue; }; void i82596_h_reset(void *opaque); void i82596_ioport_writew(void *opaque, uint32_t addr, uint32_t val); uint32_t i82596_ioport_readw(void *opaque, uint32_t addr); -void i82596_ioport_writel(void *opaque, uint32_t addr, uint32_t val); -uint32_t i82596_ioport_readl(void *opaque, uint32_t addr); -uint32_t i82596_bcr_readw(I82596State *s, uint32_t rap); ssize_t i82596_receive(NetClientState *nc, const uint8_t *buf, size_t size_); +ssize_t i82596_receive_iov(NetClientState *nc, const struct iovec *iov, + int iovcnt); bool i82596_can_receive(NetClientState *nc); void i82596_set_link_status(NetClientState *nc); -void i82596_common_init(DeviceState *dev, I82596State *s, NetClientInfo *info); +void i82596_poll(NetClientState *nc, bool enable); +void i82596_common_init(DeviceState *dev, I82596State *s, + NetClientInfo *info); extern const VMStateDescription vmstate_i82596; #endif diff --git a/hw/net/lasi_i82596.c b/hw/net/lasi_i82596.c index b87cea411a..2f212a7ed3 100644 --- a/hw/net/lasi_i82596.c +++ b/hw/net/lasi_i82596.c @@ -86,6 +86,10 @@ static const MemoryRegionOps lasi_82596_mem_ops = { .min_access_size = 4, .max_access_size = 4, }, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + }, }; static NetClientInfo net_lasi_82596_info = { @@ -93,6 +97,8 @@ static NetClientInfo net_lasi_82596_info = { .size = sizeof(NICState), .can_receive = i82596_can_receive, .receive = i82596_receive, + .receive_iov = i82596_receive_iov, + .poll = i82596_poll, .link_status_changed = i82596_set_link_status, }; From fec69f17be25db71f9cd4001b920da21cc71b283 Mon Sep 17 00:00:00 2001 From: Soumyajyotii Ssarkar Date: Mon, 3 Nov 2025 16:57:23 +0530 Subject: [PATCH 3/3] i82596: Implement enhanced TX/RX with packet queuing and filtering In this patch I have added the following: - Rewrote transmit path with CSMA/CD collision handling and retry logic - Implemented flexible TX buffer descriptor (TBD) chain processing - Rewrote receive path with packet filtering and monitor mode support - Added RX packet queue for handling resource exhaustion - Implemented queue flush timer and management - Added RX state machine with proper state transitions - Implemented packet filtering (unicast, broadcast, multicast, promiscuous) - Added SCB RU_START enhancement to find usable RFDs - Implemented dump command support - Added bus throttle timer loading (LOAD_THROTTLE/LOAD_START commands) - Enhanced signal_ca with proper initialization sequence - Finally, adding self-test functionality Note: With this patch, and the previous ones in the patch series, we are able to achive proper 82596 NIC emulation. Signed-off-by: Soumyajyotii Ssarkar Tested-by: Helge Deller Tested-by: Guenter Roeck Signed-off-by: Helge Deller --- hw/net/i82596.c | 1932 ++++++++++++++++++++++++++++++++----------- hw/net/trace-events | 21 +- 2 files changed, 1448 insertions(+), 505 deletions(-) diff --git a/hw/net/i82596.c b/hw/net/i82596.c index 859cc88b2e..37dcc0387b 100644 --- a/hw/net/i82596.c +++ b/hw/net/i82596.c @@ -49,14 +49,6 @@ #include "i82596.h" #include /* for crc32 */ -#define ENABLE_DEBUG 0 - -#if defined(ENABLE_DEBUG) -#define DBG(x) x -#else -#define DBG(x) do { } while (0) -#endif - #define USE_TIMER 1 #define MAX_MC_CNT 64 @@ -274,6 +266,10 @@ struct qemu_ether_header { }; #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) @@ -324,18 +320,26 @@ static void i82596_s_reset(I82596State *s) s->last_tx_len = 0; s->last_good_rfa = 0; - s->current_rx_desc = 0; - s->current_tx_desc = 0; - s->tx_retry_addr = 0; - s->tx_retry_count = 0; + s->queue_head = 0; + s->queue_tail = 0; + s->queue_count = 0; - s->rnr_signaled = false; - s->flushing_queue = false; + s->t_on = 0xFFFF; + s->t_off = 0; + s->throttle_state = true; - memset(s->tx_buffer, 0, sizeof(s->tx_buffer)); - memset(s->rx_buffer, 0, sizeof(s->rx_buffer)); - s->tx_frame_len = 0; - s->rx_frame_len = 0; + 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) @@ -346,82 +350,918 @@ void i82596_h_reset(void *opaque) } /* - * Address Translation Implementation - * Handles segmented and linear memory modes for i82596. - * Returns physical address for DMA operations. - * Returns I596_NULL (0xffffffff) on invalid addresses. + * 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 inline uint32_t i82596_translate_address(I82596State *s, - uint32_t logical_addr, - bool is_data_buffer) +static uint32_t i82596_translate_address(I82596State *s, + uint32_t addr, + bool is_data_buffer) { - if (logical_addr == I596_NULL || logical_addr == 0) { - return logical_addr; + if (addr == I596_NULL || addr == 0) { + return addr; } - switch (s->mode) { - case I82596_MODE_LINEAR: - return logical_addr; - - case I82596_MODE_SEGMENTED: { - uint32_t base = (logical_addr >> 16) & 0xFFFF; - uint32_t offset = logical_addr & 0xFFFF; - - if (is_data_buffer) { - return (base << 4) + offset; - } else { - if (base == 0xFFFF && offset == 0xFFFF) { - return I596_NULL; - } - return s->scb_base + ((base << 4) + offset); - } - } - case I82586_MODE: - default: if (is_data_buffer) { - return logical_addr; + return addr & 0x00FFFFFF; } else { - if ((logical_addr & 0xFFFF0000) == 0xFFFF0000) { - return I596_NULL; + if (s->scb_base) { + return (s->scb_base & 0x00FFFFFF) + (addr & 0xFFFF); + } else { + return addr & 0x00FFFFFF; } - return s->scb_base + logical_addr; + } + 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) { - uint32_t tdb_p; /* Transmit Buffer Descriptor */ + 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; - /* TODO: Check flexible mode */ - tdb_p = get_uint32(addr + 8); - while (tdb_p != I596_NULL) { - uint16_t size, len; - uint32_t tba; + 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); - size = get_uint16(tdb_p); - len = size & SIZE_MASK; - tba = get_uint32(tdb_p + 8); - trace_i82596_transmit(len, tba); + 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 (s->nic && len) { - assert(len <= sizeof(s->tx_buffer)); - address_space_read(&address_space_memory, tba, - MEMTXATTRS_UNSPECIFIED, s->tx_buffer, len); - DBG(PRINT_PKTHDR("Send", &s->tx_buffer)); - DBG(printf("Sending %d bytes\n", len)); - qemu_send_packet(qemu_get_queue(s->nic), s->tx_buffer, len); + 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; } - /* was this the last package? */ - if (size & I596_EOF) { + 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; } - /* get next buffer pointer */ - tdb_p = get_uint32(tdb_p + 4); + 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 crc_valid = true; + 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; + } 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; + } + + 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 && crc_valid) { + rx_status |= STAT_C | STAT_OK; + if (is_broadcast) { + rx_status |= 0x0001; + } + } else if (packet_completed) { + rx_status |= STAT_C; + if (!crc_valid) { + rx_status |= RX_CRC_ERRORS; + } + } 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 && crc_valid && 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) @@ -437,26 +1277,6 @@ static void set_individual_address(I82596State *s, uint32_t addr) trace_i82596_new_mac(nc->info_str); } -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)); - /* copy byte_cnt max. */ - address_space_read(&address_space_memory, addr + 8, - MEMTXATTRS_UNSPECIFIED, s->config, byte_cnt); - /* config byte according to page 35ff */ - s->config[2] &= 0x82; /* mask valid bits */ - s->config[2] |= 0x40; - s->config[7] &= 0xf7; /* clear zero bit */ - assert(I596_NOCRC_INS == 0); /* do CRC insertion */ - s->config[10] = MAX(s->config[10], 5); /* min frame length */ - s->config[12] &= 0x40; /* only full duplex field valid */ - s->config[13] |= 0x3f; /* set ones in byte 13 */ -} - static void set_multicast_list(I82596State *s, uint32_t addr) { uint16_t mc_count, i; @@ -471,8 +1291,6 @@ static void set_multicast_list(I82596State *s, uint32_t addr) uint8_t multicast_addr[ETH_ALEN]; address_space_read(&address_space_memory, addr + i * ETH_ALEN, MEMTXATTRS_UNSPECIFIED, multicast_addr, ETH_ALEN); - DBG(printf("Add multicast entry " MAC_FMT "\n", - MAC_ARG(multicast_addr))); unsigned mcast_idx = (net_crc32(multicast_addr, ETH_ALEN) & BITS(7, 2)) >> 2; assert(mcast_idx < 8 * sizeof(s->mult)); @@ -494,7 +1312,7 @@ void i82596_set_link_status(NetClientState *nc) } } -static bool G_GNUC_UNUSED i82596_check_medium_status(I82596State *s) +static bool i82596_check_medium_status(I82596State *s) { if (I596_FULL_DUPLEX) { return true; @@ -512,7 +1330,7 @@ static bool G_GNUC_UNUSED i82596_check_medium_status(I82596State *s) return true; } -static int G_GNUC_UNUSED i82596_csma_backoff(I82596State *s, int retry_count) +static int i82596_csma_backoff(I82596State *s, int retry_count) { int backoff_factor, slot_count, backoff_time; @@ -541,7 +1359,7 @@ static uint16_t i82596_calculate_crc16(const uint8_t *data, size_t len) return crc; } -static size_t G_GNUC_UNUSED i82596_append_crc(I82596State *s, uint8_t *buffer, size_t len) +static size_t i82596_append_crc(I82596State *s, uint8_t *buffer, size_t len) { if (len + 4 > PKT_BUF_SZ) { return len; @@ -560,7 +1378,7 @@ static size_t G_GNUC_UNUSED i82596_append_crc(I82596State *s, uint8_t *buffer, s } } -static void G_GNUC_UNUSED i82596_update_statistics(I82596State *s, bool is_tx, +static void i82596_update_statistics(I82596State *s, bool is_tx, uint16_t error_flags, uint16_t collision_count) { @@ -591,7 +1409,7 @@ static void G_GNUC_UNUSED i82596_update_statistics(I82596State *s, bool is_tx, } /* Bus Throttle Functionality */ -static void G_GNUC_UNUSED i82596_bus_throttle_timer(void *opaque) +static void i82596_bus_throttle_timer(void *opaque) { I82596State *s = opaque; @@ -600,42 +1418,210 @@ static void G_GNUC_UNUSED i82596_bus_throttle_timer(void *opaque) if (s->t_off > 0) { timer_mod(s->throttle_timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + - (s->t_off * NANOSECONDS_PER_MICROSECOND)); + s->t_off * NANOSECONDS_PER_MICROSECOND); + } else { + s->throttle_state = true; } } else { s->throttle_state = true; - if (s->t_on > 0) { + 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)); - } - - if (s->cu_status == CU_ACTIVE || s->rx_status == RX_READY) { - examine_scb(s); + s->t_on * NANOSECONDS_PER_MICROSECOND); } } } -static int G_GNUC_UNUSED i82596_flush_packet_queue(I82596State *s) +static void i82596_load_throttle_timers(I82596State *s, bool start_now) { - /* Stub for now - will be implemented in Patch 2 */ - return 0; + 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 G_GNUC_UNUSED i82596_flush_queue_timer(void *opaque) +static void write_uint16(uint8_t *buffer, int offset, uint16_t value) { - /* Stub for now - will be implemented in Patch 2 */ + 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 \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 G_GNUC_UNUSED i82596_update_cu_status(I82596State *s, uint16_t cmd_status, +static void i82596_update_cu_status(I82596State *s, uint16_t cmd_status, bool generate_interrupt) { if (cmd_status & STAT_C) { @@ -658,6 +1644,11 @@ static void G_GNUC_UNUSED i82596_update_cu_status(I82596State *s, uint16_t cmd_s 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) @@ -678,21 +1669,26 @@ static void update_scb_status(I82596State *s) static void command_loop(I82596State *s) { - uint16_t cmd; - uint16_t status; - - DBG(printf("STARTING COMMAND LOOP cmd_p=%08x\n", s->cmd_p)); - - while (s->cmd_p != I596_NULL) { - /* set status */ - status = STAT_B; - set_uint16(s->cmd_p, status); - status = STAT_C | STAT_OK; /* update, but write later */ - - cmd = get_uint16(s->cmd_p + 2); - DBG(printf("Running command %04x at %08x\n", cmd, s->cmd_p)); - - switch (cmd & 0x07) { + 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: @@ -702,155 +1698,274 @@ static void command_loop(I82596State *s) i82596_configure(s, s->cmd_p); break; case CmdTDR: - /* get signal LINK */ set_uint32(s->cmd_p + 8, s->lnkst); break; case CmdTx: i82596_transmit(s, s->cmd_p); - break; + 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: - printf("FIXME Command %d !!\n", cmd & 7); - g_assert_not_reached(); + break; + default: + printf("CMD_LOOP: Unknown command %d\n", cmd & CMD_MASK); + break; } - /* update status */ - set_uint16(s->cmd_p, status); - - s->cmd_p = get_uint32(s->cmd_p + 4); /* get link address */ - DBG(printf("NEXT addr would be %08x\n", s->cmd_p)); - if (s->cmd_p == 0) { - s->cmd_p = I596_NULL; + status = get_uint16(s->cmd_p); + if (!(status & STAT_C)) { + set_uint16(s->cmd_p, STAT_C | STAT_OK); } - /* Stop when last command of the list. */ - if (cmd & CMD_EOL) { - s->cmd_p = I596_NULL; - } - /* Suspend after doing cmd? */ - if (cmd & CMD_SUSP) { - s->cu_status = CU_SUSPENDED; - printf("FIXME SUSPEND !!\n"); - } - /* Interrupt after doing cmd? */ +skip_status_update: if (cmd & CMD_INTR) { s->scb_status |= SCB_STATUS_CX; - } else { - s->scb_status &= ~SCB_STATUS_CX; - } - update_scb_status(s); - - /* Interrupt after doing cmd? */ - if (cmd & CMD_INTR) { s->send_irq = 1; } - if (s->cu_status != CU_ACTIVE) { + 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; } } - DBG(printf("FINISHED COMMAND LOOP\n")); - qemu_flush_queued_packets(qemu_get_queue(s->nic)); + + 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, cuc, ruc; + 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); - /* get the scb command word */ - command = get_uint16(s->scb + 2); - cuc = (command >> 8) & 0x7; - ruc = (command >> 4) & 0x7; - DBG(printf("MAIN COMMAND %04x cuc %02x ruc %02x\n", command, cuc, ruc)); - /* and clear the scb command word */ 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 0: /* no change */ + case SCB_CUC_NOP: break; - case 1: /* CUC_START */ + + 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 4: /* CUC_ABORT */ - s->cu_status = CU_SUSPENDED; - s->scb_status |= SCB_STATUS_CNA; /* CU left active state */ - break; - default: - printf("WARNING: Unknown CUC %d!\n", cuc); } - switch (ruc) { - case 0: /* no change */ - break; - case 1: /* RX_START */ - case 2: /* RX_RESUME */ - s->rx_status = RX_IDLE; - if (USE_TIMER) { - timer_mod(s->flush_queue_timer, qemu_clock_get_ms( - QEMU_CLOCK_VIRTUAL) + 1000); + case SCB_CUC_RESUME: + if (s->cu_status == CU_SUSPENDED) { + s->cu_status = CU_ACTIVE; } break; - case 3: /* RX_SUSPEND */ - case 4: /* RX_ABORT */ - s->rx_status = RX_SUSPENDED; - s->scb_status |= SCB_STATUS_RNR; /* RU left active state */ + + 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; - default: - printf("WARNING: Unknown RUC %d!\n", ruc); } - if (command & 0x80) { /* reset bit set? */ + 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; } - - /* execute commands from SCBL */ - if (s->cu_status != CU_SUSPENDED) { + 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); } - - /* update scb status */ - update_scb_status(s); - - command_loop(s); } static void signal_ca(I82596State *s) { - uint32_t iscp = 0; - - /* trace_i82596_channel_attention(s); */ if (s->scp) { - /* CA after reset -> do init with new scp. */ - s->sysbus = get_byte(s->scp + 3); /* big endian */ - DBG(printf("SYSBUS = %08x\n", s->sysbus)); - if (((s->sysbus >> 1) & 0x03) != 2) { - printf("WARNING: NO LINEAR MODE !!\n"); - } - if ((s->sysbus >> 7)) { - printf("WARNING: 32BIT LINMODE IN B-STEPPING NOT SUPPORTED !!\n"); - } - iscp = get_uint32(s->scp + 8); - s->scb = get_uint32(iscp + 4); - set_byte(iscp + 1, 0); /* clear BUSY flag in iscp */ + /* 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; } - s->ca++; /* count ca() */ - if (!s->ca_active) { - s->ca_active = 1; - while (s->ca) { - examine_scb(s); - s->ca--; - } - s->ca_active = 0; + 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; @@ -858,21 +1973,28 @@ static void signal_ca(I82596State *s) } } -void i82596_ioport_writew(void *opaque, uint32_t addr, uint32_t val) +static void i82596_self_test(I82596State *s, uint32_t val) { - I82596State *s = opaque; - /* printf("i82596_ioport_writew addr=0x%08x val=0x%04x\n", addr, val); */ - switch (addr) { - case PORT_RESET: /* Reset */ - i82596_s_reset(s); - break; - case PORT_ALTSCP: - s->scp = val; - break; - case PORT_CA: - signal_ca(s); - break; - } + /* + * 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) @@ -880,276 +2002,29 @@ uint32_t i82596_ioport_readw(void *opaque, uint32_t addr) return -1; } -bool i82596_can_receive(NetClientState *nc) +void i82596_ioport_writew(void *opaque, uint32_t addr, uint32_t val) { - I82596State *s = qemu_get_nic_opaque(nc); - - if (s->rx_status == RX_SUSPENDED) { - return false; + 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; } - - if (!s->lnkst) { - return false; - } - - if (USE_TIMER && !timer_pending(s->flush_queue_timer)) { - return true; - } - - return true; -} - -ssize_t i82596_receive(NetClientState *nc, const uint8_t *buf, size_t sz) -{ - I82596State *s = qemu_get_nic_opaque(nc); - uint32_t rfd_p; - uint32_t rbd; - uint16_t is_broadcast = 0; - size_t len = sz; /* length of data for guest (including CRC) */ - size_t bufsz = sz; /* length of data in buf */ - uint32_t crc; - uint8_t *crc_ptr; - static const uint8_t broadcast_macaddr[6] = { - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; - - DBG(printf("i82596_receive() start\n")); - - if (USE_TIMER && timer_pending(s->flush_queue_timer)) { - return 0; - } - - /* first check if receiver is enabled */ - if (s->rx_status == RX_SUSPENDED) { - trace_i82596_receive_analysis(">>> Receiving suspended"); - return -1; - } - - if (!s->lnkst) { - trace_i82596_receive_analysis(">>> Link down"); - return -1; - } - - /* Received frame smaller than configured "min frame len"? */ - if (sz < s->config[10]) { - printf("Received frame too small, %zu vs. %u bytes\n", - sz, s->config[10]); - return -1; - } - - DBG(printf("Received %lu bytes\n", sz)); - - if (I596_PROMISC) { - - /* promiscuous: receive all */ - trace_i82596_receive_analysis( - ">>> packet received in promiscuous mode"); - - } else { - - if (!memcmp(buf, broadcast_macaddr, 6)) { - /* broadcast address */ - if (I596_BC_DISABLE) { - trace_i82596_receive_analysis(">>> broadcast packet rejected"); - - return len; - } - - trace_i82596_receive_analysis(">>> broadcast packet received"); - is_broadcast = 1; - - } else if (buf[0] & 0x01) { - /* multicast */ - if (!I596_MC_ALL) { - trace_i82596_receive_analysis(">>> multicast packet rejected"); - - return len; - } - - 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 len; - } - - trace_i82596_receive_analysis(">>> multicast packet received"); - is_broadcast = 1; - - } else if (!memcmp(s->conf.macaddr.a, buf, 6)) { - - /* match */ - trace_i82596_receive_analysis( - ">>> physical address matching packet received"); - - } else { - - trace_i82596_receive_analysis(">>> unknown packet"); - - return len; - } - } - - /* Calculate the ethernet checksum (4 bytes) */ - len += 4; - crc = cpu_to_be32(crc32(~0, buf, sz)); - crc_ptr = (uint8_t *) &crc; - - rfd_p = get_uint32(s->scb + 8); /* get Receive Frame Descriptor */ - assert(rfd_p && rfd_p != I596_NULL); - - /* get first Receive Buffer Descriptor Address */ - rbd = get_uint32(rfd_p + 8); - assert(rbd && rbd != I596_NULL); - - /* PRINT_PKTHDR("Receive", buf); */ - - while (len) { - uint16_t command, status; - uint32_t next_rfd; - - command = get_uint16(rfd_p + 2); - assert(command & CMD_FLEX); /* assert Flex Mode */ - /* get first Receive Buffer Descriptor Address */ - rbd = get_uint32(rfd_p + 8); - assert(get_uint16(rfd_p + 14) == 0); - - /* printf("Receive: rfd is %08x\n", rfd_p); */ - - while (len) { - uint16_t buffer_size, num; - uint32_t rba; - size_t bufcount, crccount; - - /* printf("Receive: rbd is %08x\n", rbd); */ - buffer_size = get_uint16(rbd + 12); - /* printf("buffer_size is 0x%x\n", buffer_size); */ - assert(buffer_size != 0); - - num = buffer_size & SIZE_MASK; - if (num > len) { - num = len; - } - rba = get_uint32(rbd + 8); - /* printf("rba is 0x%x\n", rba); */ - /* - * Calculate how many bytes we want from buf[] and how many - * from the CRC. - */ - if ((len - num) >= 4) { - /* The whole guest buffer, we haven't hit the CRC yet */ - bufcount = num; - } else { - /* All that's left of buf[] */ - bufcount = len - 4; - } - crccount = num - bufcount; - - if (bufcount > 0) { - /* Still some of the actual data buffer to transfer */ - assert(bufsz >= bufcount); - bufsz -= bufcount; - address_space_write(&address_space_memory, rba, - MEMTXATTRS_UNSPECIFIED, buf, bufcount); - rba += bufcount; - buf += bufcount; - len -= bufcount; - } - - /* Write as much of the CRC as fits */ - if (crccount > 0) { - address_space_write(&address_space_memory, rba, - MEMTXATTRS_UNSPECIFIED, crc_ptr, crccount); - rba += crccount; - crc_ptr += crccount; - len -= crccount; - } - - num |= 0x4000; /* set F BIT */ - if (len == 0) { - num |= I596_EOF; /* set EOF BIT */ - } - set_uint16(rbd + 0, num); /* write actual count with flags */ - - /* get next rbd */ - rbd = get_uint32(rbd + 4); - /* printf("Next Receive: rbd is %08x\n", rbd); */ - - if (buffer_size & I596_EOF) /* last entry */ - break; - } - - /* Housekeeping, see pg. 18 */ - next_rfd = get_uint32(rfd_p + 4); - set_uint32(next_rfd + 8, rbd); - - status = STAT_C | STAT_OK | is_broadcast; - set_uint16(rfd_p, status); - - if (command & CMD_SUSP) { /* suspend after command? */ - s->rx_status = RX_SUSPENDED; - s->scb_status |= SCB_STATUS_RNR; /* RU left active state */ - break; - } - if (command & CMD_EOL) /* was it last Frame Descriptor? */ - break; - - assert(len == 0); - } - - assert(len == 0); - - s->scb_status |= SCB_STATUS_FR; /* set "RU finished receiving frame" bit. */ - update_scb_status(s); - - /* send IRQ that we received data */ - qemu_set_irq(s->irq, 1); - /* s->send_irq = 1; */ - - if (0) { - DBG(printf("Checking:\n")); - rfd_p = get_uint32(s->scb + 8); /* get Receive Frame Descriptor */ - DBG(printf("Next Receive: rfd is %08x\n", rfd_p)); - rfd_p = get_uint32(rfd_p + 4); /* get Next Receive Frame Descriptor */ - DBG(printf("Next Receive: rfd is %08x\n", rfd_p)); - /* get first Receive Buffer Descriptor Address */ - rbd = get_uint32(rfd_p + 8); - DBG(printf("Next Receive: rbd is %08x\n", rbd)); - } - - return sz; -} - -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; - } - 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; - } - DBG(PRINT_PKTHDR("Receive IOV:", buf)); - i82596_receive(nc, buf, sz); - g_free(buf); - return sz; } void i82596_poll(NetClientState *nc, bool enable) @@ -1166,7 +2041,7 @@ void i82596_poll(NetClientState *nc, bool enable) if (s->rx_status == RX_NO_RESOURCES) { if (s->cmd_p != I596_NULL) { - s->rx_status = RX_READY; + i82596_update_rx_state(s, RX_READY); update_scb_status(s); } } @@ -1232,6 +2107,57 @@ const VMStateDescription vmstate_i82596 = { } }; +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) { diff --git a/hw/net/trace-events b/hw/net/trace-events index e82d7490c3..23efa91d05 100644 --- a/hw/net/trace-events +++ b/hw/net/trace-events @@ -421,10 +421,27 @@ lasi_82596_mem_writew(uint64_t addr, uint32_t val) "addr=0x%"PRIx64" val=0x%04x" i82596_s_reset(void *s) "%p Reset chip" i82596_transmit(uint32_t size, uint32_t addr) "size %u from addr 0x%04x" i82596_receive_analysis(const char *s) "%s" -i82596_receive_packet(size_t sz) "len=%zu" +i82596_receive_queue_full(void) "Packet queue full, dropping packet" +i82596_receive_suspended(void) "RX unit suspended, queueing packet" +i82596_rx_state_change(uint8_t old_state, uint8_t new_state) "RX state changed from %d to %d" i82596_new_mac(const char *id_with_mac) "New MAC for: %s" i82596_set_multicast(uint16_t count) "Added %d multicast entries" -i82596_channel_attention(void *s) "%p: Received CHANNEL ATTENTION" +i82596_scb_command(uint8_t cuc, uint8_t ruc) "SCB command CUC=0x%02x RUC=0x%02x" +i82596_dump(uint32_t addr) "Dump command to addr 0x%08x" +i82596_flush_queue(int count) "Flushing %d packets from queue" +i82596_tx_tfd(uint64_t addr, uint16_t status, uint16_t cmd, uint32_t link, uint32_t tbd) "TFD @0x%"PRIx64": status=0x%04x cmd=0x%04x link=0x%08x tbd=0x%08x" +i82596_tx_tbd(uint64_t addr, uint16_t size, uint32_t buf) "TBD @0x%"PRIx64": size=%d buf=0x%08x" +i82596_rx_rfd(uint64_t addr, uint16_t status, uint16_t cmd, uint32_t link, uint32_t rbd) "RFD @0x%"PRIx64": status=0x%04x cmd=0x%04x link=0x%08x rbd=0x%08x" +i82596_rx_rbd(uint64_t addr, uint16_t count, uint32_t buf, uint16_t size) "RBD @0x%"PRIx64": count=%d buf=0x%08x size=%d" +i82596_receive_packet(const uint8_t *buf, size_t size) "RX packet %p size %zu" +i82596_rx_short_frame(size_t size) "RX: Frame too short (%zu bytes)" +i82596_rx_rfd_update(uint32_t addr, uint32_t next_rfd) "RX: Updating RFD 0x%08x to point to 0x%08x" +i82596_rx_out_of_rbds(void) "RX: Out of RBDs mid-frame" +i82596_rx_incomplete(size_t copied, size_t total) "RX: Incomplete copy (%zu/%zu bytes)" +i82596_rx_complete(uint32_t crc, uint32_t align, uint32_t resource) "RX: Complete (errors: CRC=%d align=%d res=%d)" +i82596_receive_iov(size_t size, int iovcnt) "RX IOV: %zu bytes in %d vectors" +i82596_ca_init(uint32_t scb, uint8_t mode, uint32_t base) "CA: Init - SCB=0x%08x mode=%d base=0x%08x" +i82596_ioport_write(uint32_t addr, uint32_t val) "IO write addr=0x%08x val=0x%08x" # imx_fec.c imx_phy_read_num(int phy, int configured) "read request from unconfigured phy %d (configured %d)"