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/i82596.c b/hw/net/i82596.c index c1ff3e6c56..37dcc0387b 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,82 @@ #include "i82596.h" #include /* for crc32 */ -#if defined(ENABLE_DEBUG) -#define DBG(x) x -#else -#define DBG(x) do { } while (0) -#endif +#define USE_TIMER 1 -#define USE_TIMER 0 - -#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 +133,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 +221,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]; @@ -130,38 +272,996 @@ struct qemu_ether_header { be16_to_cpu(hdr->ether_type)); \ } while (0) +static void i82596_cleanup(I82596State *s) +{ + if (s->throttle_timer) { + timer_del(s->throttle_timer); + } + if (s->flush_queue_timer) { + timer_del(s->flush_queue_timer); + } + s->queue_head = 0; + s->queue_tail = 0; + s->queue_count = 0; +} + +static void i82596_s_reset(I82596State *s) +{ + trace_i82596_s_reset(s); + i82596_cleanup(s); + + /* Clearing config bits */ + memset(s->config, 0, sizeof(s->config)); + s->scp = 0x00FFFFF4; + s->scb = 0; + s->scb_base = 0; + s->scb_status = 0; + s->cu_status = CU_IDLE; + s->rx_status = RX_IDLE; + s->cmd_p = I596_NULL; + s->lnkst = 0x8000; + s->ca = s->ca_active = 0; + s->send_irq = 0; + + /* Statistical Counters */ + s->crc_err = 0; + s->align_err = 0; + s->resource_err = 0; + s->over_err = 0; + s->rcvdt_err = 0; + s->short_fr_error = 0; + s->total_frames = 0; + s->total_good_frames = 0; + s->collision_events = 0; + s->total_collisions = 0; + s->tx_good_frames = 0; + s->tx_collisions = 0; + s->tx_aborted_errors = 0; + s->last_tx_len = 0; + + s->last_good_rfa = 0; + s->queue_head = 0; + s->queue_tail = 0; + s->queue_count = 0; + + s->t_on = 0xFFFF; + s->t_off = 0; + s->throttle_state = true; + + if (!s->throttle_timer) { + s->throttle_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, + i82596_bus_throttle_timer, s); + } else { + timer_del(s->throttle_timer); + } + + if (!I596_FULL_DUPLEX && s->t_on != 0xFFFF) { + timer_mod(s->throttle_timer, + qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + + s->t_on * NANOSECONDS_PER_MICROSECOND); + } +} + +void i82596_h_reset(void *opaque) +{ + I82596State *s = opaque; + + i82596_s_reset(s); +} + +/* + * Mode Transition of address functionality. + * Note: As of now the 82596 is tested only for Linear Mode as it is most + * widely used by Linux and HPUX systems. This function is here for + * futureproofing our 82596 device model. + * According to the documentation the translation of addresses based on mode + * are done for the following cases: ISCP, SCB, CBP, RFD, TFD, + * RBD, TBD, Rx Buffers, Tx Buffers + * Please refer to the documentation for more details. + */ +static uint32_t i82596_translate_address(I82596State *s, + uint32_t addr, + bool is_data_buffer) +{ + if (addr == I596_NULL || addr == 0) { + return addr; + } + switch (s->mode) { + case I82586_MODE: + if (is_data_buffer) { + return addr & 0x00FFFFFF; + } else { + if (s->scb_base) { + return (s->scb_base & 0x00FFFFFF) + (addr & 0xFFFF); + } else { + return addr & 0x00FFFFFF; + } + } + break; + case I82596_MODE_SEGMENTED: + if (is_data_buffer) { + return addr; + } else { + if (s->scb_base) { + return s->scb_base + (addr & 0xFFFF); + } else { + return addr; + } + } + break; + case I82596_MODE_LINEAR: + default: + return addr; + } +} + +/* (TFD) Transmit Frame Descriptor */ +struct i82596_tx_descriptor { + uint16_t status_bits; + uint16_t command; + uint32_t link_addr; + uint32_t tbd_addr; + uint16_t tcb_count; + uint8_t dest_addr[6]; + uint16_t length_field; +}; + +/* (TBD) Transmit Buffer Descriptor */ +struct i82596_tx_buffer_desc { + uint16_t size; + uint32_t link; + uint32_t buffer; +}; + +/* (RFD) Receive Frame Descriptor */ +struct i82596_rx_descriptor { + uint16_t status_bits; + uint16_t command; + uint32_t link; + uint32_t rbd_addr; + uint16_t actual_count; + uint16_t size; + uint8_t dest_addr[6]; + uint8_t src_addr[6]; + uint16_t length_field; +}; + +/* (RBD) Receive Buffer Descriptor */ +struct i82596_rx_buffer_desc { + uint16_t actual_count; + uint32_t next_rbd_addr; + uint32_t buffer_addr; + uint16_t size; +}; + +static void i82596_tx_tfd_read(I82596State *s, hwaddr addr, + struct i82596_tx_descriptor *desc) +{ + desc->status_bits = get_uint16(addr + 0); + desc->command = get_uint16(addr + 2); + desc->link_addr = get_uint32(addr + 4); + desc->tbd_addr = get_uint32(addr + 8); + desc->tcb_count = get_uint16(addr + 12); + address_space_read(&address_space_memory, addr + 14, + MEMTXATTRS_UNSPECIFIED, desc->dest_addr, 6); + desc->length_field = get_uint16(addr + 20); +} + +static void i82596_tx_tfd_write(I82596State *s, hwaddr addr, + struct i82596_tx_descriptor *desc) +{ + set_uint16(addr + 0, desc->status_bits); + set_uint16(addr + 2, desc->command); + set_uint32(addr + 4, desc->link_addr); + set_uint32(addr + 8, desc->tbd_addr); + set_uint16(addr + 12, desc->tcb_count); + address_space_write(&address_space_memory, addr + 14, + MEMTXATTRS_UNSPECIFIED, desc->dest_addr, 6); + set_uint16(addr + 20, desc->length_field); +} + +static void i82596_tbd_read(I82596State *s, hwaddr addr, + struct i82596_tx_buffer_desc *tbd) +{ + tbd->size = get_uint16(addr + 0); + tbd->link = get_uint32(addr + 4); + tbd->buffer = get_uint32(addr + 8); +} + +static void i82596_rx_rfd_read(I82596State *s, hwaddr addr, + struct i82596_rx_descriptor *desc) +{ + desc->status_bits = get_uint16(addr + 0x0); + desc->command = get_uint16(addr + 0x2); + desc->link = get_uint32(addr + 0x4); + desc->rbd_addr = get_uint32(addr + 0x8); + desc->actual_count = get_uint16(addr + 0xC); + desc->size = get_uint16(addr + 0xE); + + address_space_read(&address_space_memory, addr + 0x10, + MEMTXATTRS_UNSPECIFIED, desc->dest_addr, 6); + address_space_read(&address_space_memory, addr + 0x16, + MEMTXATTRS_UNSPECIFIED, desc->src_addr, 6); + desc->length_field = get_uint16(addr + 28); +} + +static void i82596_rx_desc_write(I82596State *s, hwaddr addr, + struct i82596_rx_descriptor *desc, + bool write_full) +{ + set_uint16(addr + 0x0, desc->status_bits); + set_uint16(addr + 0xC, desc->actual_count); + + if (write_full) { + set_uint16(addr + 0x2, desc->command); + set_uint32(addr + 0x4, desc->link); + set_uint32(addr + 0x8, desc->rbd_addr); + set_uint16(addr + 0xE, desc->size); + + address_space_write(&address_space_memory, addr + 0x10, + MEMTXATTRS_UNSPECIFIED, desc->dest_addr, 6); + address_space_write(&address_space_memory, addr + 0x16, + MEMTXATTRS_UNSPECIFIED, desc->src_addr, 6); + set_uint16(addr + 0x1C, desc->length_field); + } +} + +static void i82596_rbd_read(I82596State *s, hwaddr addr, + struct i82596_rx_buffer_desc *rbd) +{ + rbd->actual_count = get_uint16(addr + 0x0); + rbd->next_rbd_addr = get_uint32(addr + 0x4); + rbd->buffer_addr = get_uint32(addr + 0x8); + rbd->size = get_uint16(addr + 0xC); +} + +static void i82596_rbd_write(I82596State *s, hwaddr addr, + struct i82596_rx_buffer_desc *rbd) +{ + set_uint16(addr + 0x0, rbd->actual_count); + set_uint32(addr + 0x4, rbd->next_rbd_addr); + set_uint32(addr + 0x8, rbd->buffer_addr); + set_uint16(addr + 0xC, rbd->size); +} + +static int i82596_tx_copy_buffers(I82596State *s, hwaddr tfd_addr, + struct i82596_tx_descriptor *desc) +{ + bool simplified_mode = !(desc->command & CMD_FLEX); + uint32_t total_len = 0; + uint32_t tbd_addr; + struct i82596_tx_buffer_desc tbd; + + s->tx_frame_len = 0; + + if (simplified_mode) { + uint16_t frame_len = desc->tcb_count & SIZE_MASK; + if (frame_len == 0 || frame_len > sizeof(s->tx_buffer)) { + return -1; + } + address_space_read(&address_space_memory, tfd_addr + 16, + MEMTXATTRS_UNSPECIFIED, s->tx_buffer, frame_len); + total_len = frame_len; + + } else { + tbd_addr = desc->tbd_addr; + while (tbd_addr != I596_NULL && tbd_addr != 0) { + uint16_t buf_size; + uint32_t buf_addr; + tbd_addr = i82596_translate_address(s, tbd_addr, false); + if (tbd_addr == 0 || tbd_addr == I596_NULL) { + return -1; + } + i82596_tbd_read(s, tbd_addr, &tbd); + trace_i82596_tx_tbd(tbd_addr, tbd.size, tbd.buffer); + buf_size = tbd.size & SIZE_MASK; + buf_addr = i82596_translate_address(s, tbd.buffer, true); + + if (total_len + buf_size > sizeof(s->tx_buffer)) { + return -1; + } + + if (buf_size > 0 && buf_addr != 0 && buf_addr != I596_NULL) { + address_space_read(&address_space_memory, buf_addr, + MEMTXATTRS_UNSPECIFIED, + s->tx_buffer + total_len, buf_size); + total_len += buf_size; + } + if (tbd.size & I596_EOF) { + break; + } + tbd_addr = tbd.link; + } + } + + s->tx_frame_len = total_len; + return total_len; +} + +static int i82596_tx_process_frame(I82596State *s, bool insert_crc) +{ + uint32_t total_len = s->tx_frame_len; + + if (total_len == 0) { + return 0; + } + + if (I596_NO_SRC_ADD_IN == 0 && total_len >= ETH_ALEN * 2) { + memcpy(&s->tx_buffer[ETH_ALEN], s->conf.macaddr.a, ETH_ALEN); + } + + if (I596_PADDING && total_len < I596_MIN_FRAME_LEN) { + size_t pad_len = I596_MIN_FRAME_LEN - total_len; + memset(s->tx_buffer + total_len, 0, pad_len); + total_len = I596_MIN_FRAME_LEN; + } + + if (insert_crc) { + total_len = i82596_append_crc(s, s->tx_buffer, total_len); + } + + s->tx_frame_len = total_len; + return total_len; +} + +static void i82596_tx_update_status(I82596State *s, hwaddr tfd_addr, + struct i82596_tx_descriptor *desc, + uint16_t tx_status, + uint16_t collision_count, + bool success) +{ + desc->status_bits = STAT_C; + + if (success) { + desc->status_bits |= STAT_OK; + } else { + desc->status_bits |= STAT_A; + } + + if (collision_count > 0) { + desc->status_bits |= (collision_count & 0x0F); + } + + i82596_tx_tfd_write(s, tfd_addr, desc); +} + +static int i82596_tx_csma_cd(I82596State *s, uint16_t *tx_status) +{ + int retry_count = 0; + bool medium_available; + + if (I596_FULL_DUPLEX || I596_LOOPBACK) { + return 0; + } + + while (retry_count < CSMA_MAX_RETRIES) { + medium_available = i82596_check_medium_status(s); + + if (medium_available) { + break; + } + i82596_csma_backoff(s, retry_count); + retry_count++; + s->total_collisions++; + } + if (retry_count >= CSMA_MAX_RETRIES) { + *tx_status |= TX_ABORTED_ERRORS; + return -1; + } + if (retry_count > 0) { + *tx_status |= TX_COLLISIONS; + s->collision_events++; + } + + return retry_count; +} + static void i82596_transmit(I82596State *s, uint32_t addr) { - 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) @@ -177,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; @@ -211,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)); @@ -223,50 +1301,394 @@ 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 i82596_check_medium_status(I82596State *s) +{ + if (I596_FULL_DUPLEX) { + return true; + } + + if (!s->throttle_state) { + return false; + } + + if (!I596_LOOPBACK && (qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) % 100 < 5)) { + s->collision_events++; + return false; + } + + return true; +} + +static int i82596_csma_backoff(I82596State *s, int retry_count) +{ + int backoff_factor, slot_count, backoff_time; + + backoff_factor = MIN(retry_count + 1, CSMA_BACKOFF_LIMIT); + slot_count = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) % (1 << backoff_factor); + backoff_time = slot_count * CSMA_SLOT_TIME; + + return backoff_time; +} + +static uint16_t i82596_calculate_crc16(const uint8_t *data, size_t len) +{ + uint16_t crc = 0xFFFF; + size_t i, j; + + for (i = 0; i < len; i++) { + crc ^= data[i] << 8; + for (j = 0; j < 8; j++) { + if (crc & 0x8000) { + crc = (crc << 1) ^ 0x1021; + } else { + crc <<= 1; + } + } + } + return crc; +} + +static size_t i82596_append_crc(I82596State *s, uint8_t *buffer, size_t len) +{ + if (len + 4 > PKT_BUF_SZ) { + return len; + } + + if (I596_CRC16_32) { + uint32_t crc = crc32(~0, buffer, len); + crc = cpu_to_be32(crc); + memcpy(&buffer[len], &crc, sizeof(crc)); + return len + sizeof(crc); + } else { + uint16_t crc = i82596_calculate_crc16(buffer, len); + crc = cpu_to_be16(crc); + memcpy(&buffer[len], &crc, sizeof(crc)); + return len + sizeof(crc); + } +} + +static void i82596_update_statistics(I82596State *s, bool is_tx, + uint16_t error_flags, + uint16_t collision_count) +{ + if (is_tx) { + if (collision_count > 0) { + s->tx_collisions += collision_count; + s->collision_events++; + s->total_collisions += collision_count; + set_uint32(s->scb + 32, s->tx_collisions); + } + if (error_flags) { + i82596_record_error(s, error_flags, true); + } + if (!(error_flags & (TX_ABORTED_ERRORS | TX_CARRIER_ERRORS))) { + s->tx_good_frames++; + set_uint32(s->scb + 36, s->tx_good_frames); + } + } else { + s->total_frames++; + set_uint32(s->scb + 40, s->total_frames); + if (error_flags) { + i82596_record_error(s, error_flags, false); + } else { + s->total_good_frames++; + set_uint32(s->scb + 44, s->total_good_frames); + } + } +} + +/* Bus Throttle Functionality */ +static void i82596_bus_throttle_timer(void *opaque) +{ + I82596State *s = opaque; + + if (s->throttle_state) { + s->throttle_state = false; + if (s->t_off > 0) { + timer_mod(s->throttle_timer, + qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + + s->t_off * NANOSECONDS_PER_MICROSECOND); + } else { + s->throttle_state = true; + } + } else { + s->throttle_state = true; + if (s->t_on > 0 && s->t_on != 0xFFFF) { + timer_mod(s->throttle_timer, + qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + + s->t_on * NANOSECONDS_PER_MICROSECOND); + } + } +} + +static void i82596_load_throttle_timers(I82596State *s, bool start_now) +{ + uint16_t previous_t_on = s->t_on; + uint16_t previous_t_off = s->t_off; + s->t_on = get_uint16(s->scb + 36); + s->t_off = get_uint16(s->scb + 38); + + bool values_changed = (s->t_on != previous_t_on || + s->t_off != previous_t_off); + if (start_now || (values_changed && s->throttle_timer)) { + timer_del(s->throttle_timer); + s->throttle_state = true; + if (s->t_on > 0 && s->t_on != 0xFFFF && !I596_FULL_DUPLEX) { + timer_mod(s->throttle_timer, + qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + + s->t_on * NANOSECONDS_PER_MICROSECOND); + } + } +} + +static void write_uint16(uint8_t *buffer, int offset, uint16_t value) +{ + buffer[offset] = value >> 8; + buffer[offset + 1] = value & 0xFF; +} +static void write_uint32(uint8_t *buffer, int offset, uint32_t value) +{ + write_uint16(buffer, offset, value >> 16); + write_uint16(buffer, offset + 2, value & 0xFFFF); +} + +static void i82596_init_dump_area(I82596State *s, uint8_t *buffer) +{ + memset(buffer, 0, DUMP_BUF_SZ); + + printf("This is the dump area function for i82596 QEMU side\n" + "If you are seeing this message, please contact:\n" + "Soumyajyotii Sarkar \n" + "With the process in which you encountered this issue:\n" + "This still needs developement so,\n" + "I will be more than delighted to help you out!\n" + ); + + write_uint16(buffer, 0x00, (s->config[5] << 8) | s->config[4]); + write_uint16(buffer, 0x02, (s->config[3] << 8) | s->config[2]); + write_uint16(buffer, 0x04, (s->config[9] << 8) | s->config[8]); + write_uint16(buffer, 0x06, (s->config[7] << 8) | s->config[6]); + write_uint16(buffer, 0x08, (s->config[13] << 8) | s->config[12]); + write_uint16(buffer, 0x0A, (s->config[11] << 8) | s->config[10]); + + buffer[0x0C] = s->conf.macaddr.a[0]; + buffer[0x0D] = s->conf.macaddr.a[1]; + buffer[0x10] = s->conf.macaddr.a[2]; + buffer[0x11] = s->conf.macaddr.a[3]; + buffer[0x12] = s->conf.macaddr.a[4]; + buffer[0x13] = s->conf.macaddr.a[5]; + + if (s->last_tx_len > 0) { + uint32_t tx_crc = crc32(~0, s->tx_buffer, s->last_tx_len); + write_uint16(buffer, 0x14, tx_crc & 0xFFFF); + write_uint16(buffer, 0x16, tx_crc >> 16); + } + + memcpy(&buffer[0x24], s->mult, sizeof(s->mult)); + + buffer[0xB0] = s->cu_status; + buffer[0xB1] = s->rx_status; + + write_uint32(buffer, 0xB4, s->crc_err); + write_uint32(buffer, 0xB8, s->align_err); + write_uint32(buffer, 0xBC, s->resource_err); + write_uint32(buffer, 0xC0, s->over_err); + + write_uint32(buffer, 0xC4, s->short_fr_error); + write_uint32(buffer, 0xC8, s->total_frames); + write_uint32(buffer, 0xCC, s->total_good_frames); + + buffer[0xD0] = I596_PROMISC ? 1 : 0; + buffer[0xD1] = I596_BC_DISABLE ? 1 : 0; + buffer[0xD2] = I596_FULL_DUPLEX ? 1 : 0; + buffer[0xD3] = I596_LOOPBACK; + + uint8_t mc_count = 0; + for (int i = 0; i < sizeof(s->mult); i++) { + uint8_t byte = s->mult[i]; + while (byte) { + if (byte & 0x01) { + mc_count++; + } + byte >>= 1; + } + } + buffer[0xD4] = mc_count; + buffer[0xD5] = I596_NOCRC_INS ? 1 : 0; + buffer[0xD6] = I596_CRC16_32 ? 1 : 0; + + write_uint16(buffer, 0xD8, s->lnkst); + buffer[0xDA] = I596_MONITOR_MODE; + write_uint32(buffer, 0xDC, s->collision_events); + + write_uint16(buffer, 0x110, s->t_on); + write_uint16(buffer, 0x112, s->t_off); + write_uint16(buffer, 0x114, s->throttle_state ? 0x0001 : 0x0000); + write_uint16(buffer, 0x120, s->sysbus); + write_uint16(buffer, 0x128, s->scb_status); + write_uint32(buffer, 0, 0xFFFF0000); +} + +static void i82596_port_dump(I82596State *s, uint32_t dump_addr) +{ + uint8_t dump_buffer[DUMP_BUF_SZ]; + + i82596_init_dump_area(s, dump_buffer); + + address_space_write(&address_space_memory, dump_addr, + MEMTXATTRS_UNSPECIFIED, dump_buffer, sizeof(dump_buffer)); + + set_uint32(dump_addr, 0xFFFF0000); + s->scb_status |= SCB_STATUS_CX; + s->send_irq = 1; +} + +static void i82596_command_dump(I82596State *s, uint32_t cmd_addr) +{ + uint32_t dump_addr; + uint8_t dump_buffer[DUMP_BUF_SZ]; + uint16_t cmd = get_uint16(cmd_addr + 2); + uint16_t status; + + dump_addr = get_uint32(cmd_addr + 8); + + i82596_init_dump_area(s, dump_buffer); + address_space_write(&address_space_memory, dump_addr, + MEMTXATTRS_UNSPECIFIED, dump_buffer, sizeof(dump_buffer)); + status = STAT_C | STAT_OK; + set_uint16(cmd_addr, status); + if (cmd & CMD_INTR) { + s->scb_status |= SCB_STATUS_CX; + s->send_irq = 1; + } + if (cmd & CMD_SUSP) { + s->cu_status = CU_SUSPENDED; + s->scb_status |= SCB_STATUS_CNA; + } +} + +static void i82596_configure(I82596State *s, uint32_t addr) +{ + uint8_t byte_cnt; + byte_cnt = get_byte(addr + 8) & 0x0f; + byte_cnt = MAX(byte_cnt, 4); + byte_cnt = MIN(byte_cnt, sizeof(s->config)); + s->config[2] &= 0x82; /* mask valid bits */ + s->config[2] |= 0x40; + s->config[7] &= 0xf7; /* clear zero bit */ + + address_space_read(&address_space_memory, addr + 8, + MEMTXATTRS_UNSPECIFIED, s->config, byte_cnt); + + if (byte_cnt > 12) { + s->config[12] &= 0x40; + + if (byte_cnt > 11) { + uint8_t monitor_mode = I596_MONITOR_MODE; + s->config[11] &= ~0xC0; /* Clear bits 6-7 */ + s->config[11] |= (monitor_mode << 6); /* Set monitor mode */ + } + } + + if (s->rx_status == RX_READY) { + timer_mod(s->flush_queue_timer, + qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 100000000); + } + + s->scb_status |= SCB_STATUS_CNA; + s->config[13] |= 0x3f; + qemu_set_irq(s->irq, 1); +} + +static void i82596_update_scb_irq(I82596State *s, bool trigger) +{ + update_scb_status(s); + + if (trigger) { + s->send_irq = 1; + qemu_set_irq(s->irq, 1); + } +} + +static void i82596_update_cu_status(I82596State *s, uint16_t cmd_status, + bool generate_interrupt) +{ + if (cmd_status & STAT_C) { + if (cmd_status & STAT_OK) { + if (s->cu_status == CU_ACTIVE && s->cmd_p == I596_NULL) { + s->cu_status = CU_IDLE; + s->scb_status |= SCB_STATUS_CNA; + } + } else { + s->cu_status = CU_IDLE; + s->scb_status |= SCB_STATUS_CNA; + } + + if (generate_interrupt) { + s->scb_status |= SCB_STATUS_CX; + i82596_update_scb_irq(s, true); + } + } + + update_scb_status(s); +} + +/** + * Update SCB Status + * Synchronizes device state with SCB status word and statistics counters. + * This function is called frequently to keep the kernel driver updated. + */ static void update_scb_status(I82596State *s) { s->scb_status = (s->scb_status & 0xf000) - | (s->cu_status << 8) | (s->rx_status << 4); + | (s->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; - 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: @@ -276,166 +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)); -} -static void i82596_flush_queue_timer(void *opaque) -{ - I82596State *s = opaque; - if (0) { - timer_del(s->flush_queue_timer); + update_scb_status(s); + + if (s->rx_status == RX_READY && s->nic) { 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; + 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); - s->scb_status &= ~(command & SCB_COMMAND_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; @@ -443,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) @@ -465,267 +2002,162 @@ uint32_t i82596_ioport_readw(void *opaque, uint32_t addr) return -1; } -void i82596_h_reset(void *opaque) +void i82596_ioport_writew(void *opaque, uint32_t addr, uint32_t val) { I82596State *s = opaque; - - i82596_s_reset(s); + 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; + } } -bool i82596_can_receive(NetClientState *nc) +void i82596_poll(NetClientState *nc, bool enable) { I82596State *s = qemu_get_nic_opaque(nc); - if (s->rx_status == RX_SUSPENDED) { - return false; + if (!enable) { + return; } - if (!s->lnkst) { - return false; + if (s->send_irq) { + qemu_set_irq(s->irq, 1); } - if (USE_TIMER && !timer_pending(s->flush_queue_timer)) { - return true; + if (s->rx_status == RX_NO_RESOURCES) { + if (s->cmd_p != I596_NULL) { + i82596_update_rx_state(s, RX_READY); + update_scb_status(s); + } } - return true; + if (s->cu_status == CU_ACTIVE && s->cmd_p != I596_NULL) { + examine_scb(s); + } + qemu_set_irq(s->irq, 0); } -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); - - trace_i82596_receive_packet(len); - /* 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; -} - - 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() } }; +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) { @@ -736,8 +2168,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 9e1dd21546..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, }; @@ -115,32 +121,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 +138,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/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)" 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