mirror of
https://github.com/izzy2lost/xemu.git
synced 2026-07-06 00:20:22 -07:00
Fix coverty reported logic error CID 1642873. The initial issue was the break statement exited out of the for loop unconditionally. Now, the break only happens if rfd is null, out_of_resources or bytes_copied < payload_size. Reported-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Soumyajyotii Ssarkar <soumyajyotisarkar23@gmail.com> Reviewed-by: Helge Deller <deller@gmx.de> Signed-off-by: Helge Deller <deller@gmx.de>
2179 lines
64 KiB
C
2179 lines
64 KiB
C
/*
|
|
* QEMU Intel i82596 (Apricot) emulation
|
|
*
|
|
* Copyright (c) 2019 Helge Deller <deller@gmx.de>
|
|
*
|
|
* Additional functionality added by:
|
|
* Soumyajyotii Ssarkar <soumyajyotisarkar23@gmail.com>
|
|
* During GSOC 2025 under mentorship of Helge Deller.
|
|
*
|
|
* This work is licensed under the GNU GPL license version 2 or later.
|
|
* This software was written to be compatible with the specification:
|
|
* https://parisc.docs.kernel.org/en/latest/_downloads/96672be0650d9fc046bbcea40b92482f/82596CA.pdf
|
|
*
|
|
* INDEX:
|
|
* 1. Reset
|
|
* 2. Address Translation
|
|
* 3. Transmit functions
|
|
* 4. Receive Helper functions
|
|
* 5. Receive functions
|
|
* 6. Misc Functionality Functions
|
|
* 6.1 Individual Address
|
|
* 6.2 Multicast Address List
|
|
* 6.3 Link Status
|
|
* 6.4 CSMA/CD functions
|
|
* 6.5 Unified CRC Calculation
|
|
* 6.6 Unified Statistics Update
|
|
* 7. Bus Throttling Timer
|
|
* 8. Dump functions
|
|
* 9. Configure
|
|
* 10. Command Loop
|
|
* 11. Examine SCB
|
|
* 12. Channel attention (CA)
|
|
* 13. LASI interface
|
|
* 14. Polling functions
|
|
* 15. QOM and interface functions
|
|
*
|
|
*/
|
|
|
|
#include "qemu/osdep.h"
|
|
#include "qemu/timer.h"
|
|
#include "net/net.h"
|
|
#include "net/eth.h"
|
|
#include "hw/irq.h"
|
|
#include "hw/qdev-properties.h"
|
|
#include "migration/vmstate.h"
|
|
#include "system/address-spaces.h"
|
|
#include "qemu/module.h"
|
|
#include "trace.h"
|
|
#include "i82596.h"
|
|
#include <zlib.h> /* for crc32 */
|
|
|
|
#define USE_TIMER 1
|
|
|
|
#define MAX_MC_CNT 64
|
|
#define I596_NULL ((uint32_t)0xffffffff)
|
|
#define BITS(n, m) (((0xffffffffU << (31 - n)) >> (31 - n + m)) << m)
|
|
|
|
#define SCB_STATUS_CX 0x8000 /* CU finished command with I bit */
|
|
#define SCB_STATUS_FR 0x4000 /* RU finished receiving a frame */
|
|
#define SCB_STATUS_CNA 0x2000 /* CU left active state */
|
|
#define SCB_STATUS_RNR 0x1000 /* RU left active state */
|
|
#define SCB_ACK_MASK 0xF000 /* All interrupt acknowledge bits */
|
|
|
|
/* 82596 Operational Modes */
|
|
#define I82586_MODE 0x00
|
|
#define I82596_MODE_SEGMENTED 0x01
|
|
#define I82596_MODE_LINEAR 0x02
|
|
|
|
/* Monitor Options */
|
|
#define MONITOR_NORMAL 0x00
|
|
#define MONITOR_FILTERED 0x01
|
|
#define MONITOR_ALL 0x02
|
|
#define MONITOR_DISABLED 0x03
|
|
|
|
/* Operation mode flags from SYSBUS byte */
|
|
#define SYSBUS_LOCK_EN 0x08
|
|
#define SYSBUS_INT_ACTIVE_LOW 0x10
|
|
#define SYSBUS_BIG_ENDIAN_32 0x80
|
|
#define SYSBUS_THROTTLE_MASK 0x60
|
|
|
|
/* SCB commands - Command Unit (CU) */
|
|
#define SCB_CUC_NOP 0x00
|
|
#define SCB_CUC_START 0x01
|
|
#define SCB_CUC_RESUME 0x02
|
|
#define SCB_CUC_SUSPEND 0x03
|
|
#define SCB_CUC_ABORT 0x04
|
|
#define SCB_CUC_LOAD_THROTTLE 0x05
|
|
#define SCB_CUC_LOAD_START 0x06
|
|
|
|
/* SCB commands - Receive Unit (RU) */
|
|
#define SCB_RUC_NOP 0x00
|
|
#define SCB_RUC_START 0x01
|
|
#define SCB_RUC_RESUME 0x02
|
|
#define SCB_RUC_SUSPEND 0x03
|
|
#define SCB_RUC_ABORT 0x04
|
|
|
|
/* SCB statuses - Command Unit (CU) */
|
|
#define CU_IDLE 0
|
|
#define CU_SUSPENDED 1
|
|
#define CU_ACTIVE 2
|
|
|
|
/* SCB statuses - Receive Unit (RU) */
|
|
#define RX_IDLE 0x00
|
|
#define RX_SUSPENDED 0x01
|
|
#define RX_NO_RESOURCES 0x02
|
|
#define RX_READY 0x04
|
|
#define RX_NO_RESO_RBD 0x0A
|
|
#define RX_NO_MORE_RBD 0x0C
|
|
|
|
#define CMD_FLEX 0x0008
|
|
#define CMD_MASK 0x0007
|
|
|
|
#define CMD_EOL 0x8000
|
|
#define CMD_SUSP 0x4000
|
|
#define CMD_INTR 0x2000
|
|
|
|
#define ISCP_BUSY 0x01
|
|
#define NANOSECONDS_PER_MICROSECOND 1000
|
|
|
|
#define DUMP_BUF_SZ 304
|
|
|
|
enum commands {
|
|
CmdNOp = 0, CmdSASetup = 1, CmdConfigure = 2, CmdMulticastList = 3,
|
|
CmdTx = 4, CmdTDR = 5, CmdDump = 6, CmdDiagnose = 7
|
|
};
|
|
|
|
|
|
#define STAT_C 0x8000 /* Set to 0 after execution */
|
|
#define STAT_B 0x4000 /* Command being executed */
|
|
#define STAT_OK 0x2000 /* Command executed ok */
|
|
#define STAT_A 0x1000 /* Command aborted */
|
|
|
|
#define I596_EOF 0x8000
|
|
#define SIZE_MASK 0x3fff
|
|
|
|
#define CSMA_SLOT_TIME 51
|
|
#define CSMA_MAX_RETRIES 16
|
|
#define CSMA_BACKOFF_LIMIT 10
|
|
|
|
/* Global Flags fetched from config bytes */
|
|
#define I596_PREFETCH (s->config[0] & 0x80)
|
|
#define SAVE_BAD_FRAMES (s->config[2] & 0x80)
|
|
#define I596_NO_SRC_ADD_IN (s->config[3] & 0x08)
|
|
#define I596_LOOPBACK (s->config[3] >> 6)
|
|
#define I596_PROMISC (s->config[8] & 0x01)
|
|
#define I596_BC_DISABLE (s->config[8] & 0x02)
|
|
#define I596_NOCRC_INS (s->config[8] & 0x08)
|
|
#define I596_CRC16_32 (s->config[8] & 0x10)
|
|
#define I596_PADDING (s->config[8] & 0x80)
|
|
#define I596_MIN_FRAME_LEN (s->config[10])
|
|
#define I596_CRCINM (s->config[11] & 0x04)
|
|
#define I596_MONITOR_MODE ((s->config[11] >> 6) & 0x03)
|
|
#define I596_MC_ALL (s->config[11] & 0x20)
|
|
#define I596_FULL_DUPLEX (s->config[12] & 0x40)
|
|
#define I596_MULTIIA (s->config[13] & 0x40)
|
|
|
|
/* RX Error flags */
|
|
#define RX_COLLISIONS 0x0001
|
|
#define RX_LENGTH_ERRORS 0x0080
|
|
#define RX_OVER_ERRORS 0x0100
|
|
#define RX_FIFO_ERRORS 0x0400
|
|
#define RX_FRAME_ERRORS 0x0800
|
|
#define RX_CRC_ERRORS 0x1000
|
|
#define RX_LENGTH_ERRORS_ALT 0x2000
|
|
#define RFD_STATUS_TRUNC 0x0020
|
|
#define RFD_STATUS_NOBUFS 0x0200
|
|
|
|
/* TX Error flags */
|
|
#define TX_COLLISIONS 0x0020
|
|
#define TX_HEARTBEAT_ERRORS 0x0040
|
|
#define TX_CARRIER_ERRORS 0x0400
|
|
#define TX_COLLISIONS_ALT 0x0800
|
|
#define TX_ABORTED_ERRORS 0x1000
|
|
|
|
static void i82596_update_scb_irq(I82596State *s, bool trigger);
|
|
static void i82596_update_cu_status(I82596State *s, uint16_t cmd_status,
|
|
bool generate_interrupt);
|
|
static void update_scb_status(I82596State *s);
|
|
static void examine_scb(I82596State *s);
|
|
static bool i82596_check_medium_status(I82596State *s);
|
|
static int i82596_csma_backoff(I82596State *s, int retry_count);
|
|
static uint16_t i82596_calculate_crc16(const uint8_t *data, size_t len);
|
|
static size_t i82596_append_crc(I82596State *s, uint8_t *buffer, size_t len);
|
|
static void i82596_bus_throttle_timer(void *opaque);
|
|
static void i82596_flush_queue_timer(void *opaque);
|
|
static int i82596_flush_packet_queue(I82596State *s);
|
|
static void i82596_update_statistics(I82596State *s, bool is_tx,
|
|
uint16_t error_flags,
|
|
uint16_t collision_count);
|
|
|
|
static uint8_t get_byte(uint32_t addr)
|
|
{
|
|
return ldub_phys(&address_space_memory, addr);
|
|
}
|
|
|
|
static void set_byte(uint32_t addr, uint8_t c)
|
|
{
|
|
return stb_phys(&address_space_memory, addr, c);
|
|
}
|
|
|
|
static uint16_t get_uint16(uint32_t addr)
|
|
{
|
|
return lduw_be_phys(&address_space_memory, addr);
|
|
}
|
|
|
|
static void set_uint16(uint32_t addr, uint16_t w)
|
|
{
|
|
return stw_be_phys(&address_space_memory, addr, w);
|
|
}
|
|
|
|
static uint32_t get_uint32(uint32_t addr)
|
|
{
|
|
uint32_t lo = lduw_be_phys(&address_space_memory, addr);
|
|
uint32_t hi = lduw_be_phys(&address_space_memory, addr + 2);
|
|
return (hi << 16) | lo;
|
|
}
|
|
|
|
static void set_uint32(uint32_t addr, uint32_t val)
|
|
{
|
|
set_uint16(addr, (uint16_t) val);
|
|
set_uint16(addr + 2, val >> 16);
|
|
}
|
|
|
|
/* Centralized error detection and update mechanism */
|
|
static void i82596_record_error(I82596State *s, uint16_t error_type, bool is_tx)
|
|
{
|
|
if (is_tx) {
|
|
if (error_type & TX_ABORTED_ERRORS) {
|
|
s->tx_aborted_errors++;
|
|
set_uint32(s->scb + 28, s->tx_aborted_errors);
|
|
}
|
|
} else {
|
|
if (error_type & RX_CRC_ERRORS) {
|
|
s->crc_err++;
|
|
set_uint32(s->scb + 16, s->crc_err);
|
|
}
|
|
|
|
if (error_type & (RX_LENGTH_ERRORS | RX_LENGTH_ERRORS_ALT |
|
|
RX_FRAME_ERRORS)) {
|
|
s->align_err++;
|
|
set_uint32(s->scb + 18, s->align_err);
|
|
}
|
|
|
|
if (error_type & RFD_STATUS_NOBUFS) {
|
|
s->resource_err++;
|
|
set_uint32(s->scb + 20, s->resource_err);
|
|
}
|
|
|
|
if (error_type & (RX_OVER_ERRORS | RX_FIFO_ERRORS)) {
|
|
s->over_err++;
|
|
set_uint32(s->scb + 22, s->over_err);
|
|
}
|
|
|
|
if (error_type & RFD_STATUS_TRUNC) {
|
|
s->short_fr_error++;
|
|
set_uint32(s->scb + 26, s->short_fr_error);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Packet Header Debugger */
|
|
struct qemu_ether_header {
|
|
uint8_t ether_dhost[6];
|
|
uint8_t ether_shost[6];
|
|
uint16_t ether_type;
|
|
};
|
|
|
|
#define PRINT_PKTHDR(txt, BUF) do { \
|
|
struct qemu_ether_header *hdr = (void *)(BUF); \
|
|
printf(txt ": packet dhost=" MAC_FMT ", shost=" MAC_FMT ", type=0x%04x\n",\
|
|
MAC_ARG(hdr->ether_dhost), MAC_ARG(hdr->ether_shost), \
|
|
be16_to_cpu(hdr->ether_type)); \
|
|
} while (0)
|
|
|
|
static void i82596_cleanup(I82596State *s)
|
|
{
|
|
if (s->throttle_timer) {
|
|
timer_del(s->throttle_timer);
|
|
}
|
|
if (s->flush_queue_timer) {
|
|
timer_del(s->flush_queue_timer);
|
|
}
|
|
s->queue_head = 0;
|
|
s->queue_tail = 0;
|
|
s->queue_count = 0;
|
|
}
|
|
|
|
static void i82596_s_reset(I82596State *s)
|
|
{
|
|
trace_i82596_s_reset(s);
|
|
i82596_cleanup(s);
|
|
|
|
/* Clearing config bits */
|
|
memset(s->config, 0, sizeof(s->config));
|
|
s->scp = 0x00FFFFF4;
|
|
s->scb = 0;
|
|
s->scb_base = 0;
|
|
s->scb_status = 0;
|
|
s->cu_status = CU_IDLE;
|
|
s->rx_status = RX_IDLE;
|
|
s->cmd_p = I596_NULL;
|
|
s->lnkst = 0x8000;
|
|
s->ca = s->ca_active = 0;
|
|
s->send_irq = 0;
|
|
|
|
/* Statistical Counters */
|
|
s->crc_err = 0;
|
|
s->align_err = 0;
|
|
s->resource_err = 0;
|
|
s->over_err = 0;
|
|
s->rcvdt_err = 0;
|
|
s->short_fr_error = 0;
|
|
s->total_frames = 0;
|
|
s->total_good_frames = 0;
|
|
s->collision_events = 0;
|
|
s->total_collisions = 0;
|
|
s->tx_good_frames = 0;
|
|
s->tx_collisions = 0;
|
|
s->tx_aborted_errors = 0;
|
|
s->last_tx_len = 0;
|
|
|
|
s->last_good_rfa = 0;
|
|
s->queue_head = 0;
|
|
s->queue_tail = 0;
|
|
s->queue_count = 0;
|
|
|
|
s->t_on = 0xFFFF;
|
|
s->t_off = 0;
|
|
s->throttle_state = true;
|
|
|
|
if (!s->throttle_timer) {
|
|
s->throttle_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
|
|
i82596_bus_throttle_timer, s);
|
|
} else {
|
|
timer_del(s->throttle_timer);
|
|
}
|
|
|
|
if (!I596_FULL_DUPLEX && s->t_on != 0xFFFF) {
|
|
timer_mod(s->throttle_timer,
|
|
qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
|
|
s->t_on * NANOSECONDS_PER_MICROSECOND);
|
|
}
|
|
}
|
|
|
|
void i82596_h_reset(void *opaque)
|
|
{
|
|
I82596State *s = opaque;
|
|
|
|
i82596_s_reset(s);
|
|
}
|
|
|
|
/*
|
|
* Mode Transition of address functionality.
|
|
* Note: As of now the 82596 is tested only for Linear Mode as it is most
|
|
* widely used by Linux and HPUX systems. This function is here for
|
|
* futureproofing our 82596 device model.
|
|
* According to the documentation the translation of addresses based on mode
|
|
* are done for the following cases: ISCP, SCB, CBP, RFD, TFD,
|
|
* RBD, TBD, Rx Buffers, Tx Buffers
|
|
* Please refer to the documentation for more details.
|
|
*/
|
|
static uint32_t i82596_translate_address(I82596State *s,
|
|
uint32_t addr,
|
|
bool is_data_buffer)
|
|
{
|
|
if (addr == I596_NULL || addr == 0) {
|
|
return addr;
|
|
}
|
|
switch (s->mode) {
|
|
case I82586_MODE:
|
|
if (is_data_buffer) {
|
|
return addr & 0x00FFFFFF;
|
|
} else {
|
|
if (s->scb_base) {
|
|
return (s->scb_base & 0x00FFFFFF) + (addr & 0xFFFF);
|
|
} else {
|
|
return addr & 0x00FFFFFF;
|
|
}
|
|
}
|
|
break;
|
|
case I82596_MODE_SEGMENTED:
|
|
if (is_data_buffer) {
|
|
return addr;
|
|
} else {
|
|
if (s->scb_base) {
|
|
return s->scb_base + (addr & 0xFFFF);
|
|
} else {
|
|
return addr;
|
|
}
|
|
}
|
|
break;
|
|
case I82596_MODE_LINEAR:
|
|
default:
|
|
return addr;
|
|
}
|
|
}
|
|
|
|
/* (TFD) Transmit Frame Descriptor */
|
|
struct i82596_tx_descriptor {
|
|
uint16_t status_bits;
|
|
uint16_t command;
|
|
uint32_t link_addr;
|
|
uint32_t tbd_addr;
|
|
uint16_t tcb_count;
|
|
uint8_t dest_addr[6];
|
|
uint16_t length_field;
|
|
};
|
|
|
|
/* (TBD) Transmit Buffer Descriptor */
|
|
struct i82596_tx_buffer_desc {
|
|
uint16_t size;
|
|
uint32_t link;
|
|
uint32_t buffer;
|
|
};
|
|
|
|
/* (RFD) Receive Frame Descriptor */
|
|
struct i82596_rx_descriptor {
|
|
uint16_t status_bits;
|
|
uint16_t command;
|
|
uint32_t link;
|
|
uint32_t rbd_addr;
|
|
uint16_t actual_count;
|
|
uint16_t size;
|
|
uint8_t dest_addr[6];
|
|
uint8_t src_addr[6];
|
|
uint16_t length_field;
|
|
};
|
|
|
|
/* (RBD) Receive Buffer Descriptor */
|
|
struct i82596_rx_buffer_desc {
|
|
uint16_t actual_count;
|
|
uint32_t next_rbd_addr;
|
|
uint32_t buffer_addr;
|
|
uint16_t size;
|
|
};
|
|
|
|
static void i82596_tx_tfd_read(I82596State *s, hwaddr addr,
|
|
struct i82596_tx_descriptor *desc)
|
|
{
|
|
desc->status_bits = get_uint16(addr + 0);
|
|
desc->command = get_uint16(addr + 2);
|
|
desc->link_addr = get_uint32(addr + 4);
|
|
desc->tbd_addr = get_uint32(addr + 8);
|
|
desc->tcb_count = get_uint16(addr + 12);
|
|
address_space_read(&address_space_memory, addr + 14,
|
|
MEMTXATTRS_UNSPECIFIED, desc->dest_addr, 6);
|
|
desc->length_field = get_uint16(addr + 20);
|
|
}
|
|
|
|
static void i82596_tx_tfd_write(I82596State *s, hwaddr addr,
|
|
struct i82596_tx_descriptor *desc)
|
|
{
|
|
set_uint16(addr + 0, desc->status_bits);
|
|
set_uint16(addr + 2, desc->command);
|
|
set_uint32(addr + 4, desc->link_addr);
|
|
set_uint32(addr + 8, desc->tbd_addr);
|
|
set_uint16(addr + 12, desc->tcb_count);
|
|
address_space_write(&address_space_memory, addr + 14,
|
|
MEMTXATTRS_UNSPECIFIED, desc->dest_addr, 6);
|
|
set_uint16(addr + 20, desc->length_field);
|
|
}
|
|
|
|
static void i82596_tbd_read(I82596State *s, hwaddr addr,
|
|
struct i82596_tx_buffer_desc *tbd)
|
|
{
|
|
tbd->size = get_uint16(addr + 0);
|
|
tbd->link = get_uint32(addr + 4);
|
|
tbd->buffer = get_uint32(addr + 8);
|
|
}
|
|
|
|
static void i82596_rx_rfd_read(I82596State *s, hwaddr addr,
|
|
struct i82596_rx_descriptor *desc)
|
|
{
|
|
desc->status_bits = get_uint16(addr + 0x0);
|
|
desc->command = get_uint16(addr + 0x2);
|
|
desc->link = get_uint32(addr + 0x4);
|
|
desc->rbd_addr = get_uint32(addr + 0x8);
|
|
desc->actual_count = get_uint16(addr + 0xC);
|
|
desc->size = get_uint16(addr + 0xE);
|
|
|
|
address_space_read(&address_space_memory, addr + 0x10,
|
|
MEMTXATTRS_UNSPECIFIED, desc->dest_addr, 6);
|
|
address_space_read(&address_space_memory, addr + 0x16,
|
|
MEMTXATTRS_UNSPECIFIED, desc->src_addr, 6);
|
|
desc->length_field = get_uint16(addr + 28);
|
|
}
|
|
|
|
static void i82596_rx_desc_write(I82596State *s, hwaddr addr,
|
|
struct i82596_rx_descriptor *desc,
|
|
bool write_full)
|
|
{
|
|
set_uint16(addr + 0x0, desc->status_bits);
|
|
set_uint16(addr + 0xC, desc->actual_count);
|
|
|
|
if (write_full) {
|
|
set_uint16(addr + 0x2, desc->command);
|
|
set_uint32(addr + 0x4, desc->link);
|
|
set_uint32(addr + 0x8, desc->rbd_addr);
|
|
set_uint16(addr + 0xE, desc->size);
|
|
|
|
address_space_write(&address_space_memory, addr + 0x10,
|
|
MEMTXATTRS_UNSPECIFIED, desc->dest_addr, 6);
|
|
address_space_write(&address_space_memory, addr + 0x16,
|
|
MEMTXATTRS_UNSPECIFIED, desc->src_addr, 6);
|
|
set_uint16(addr + 0x1C, desc->length_field);
|
|
}
|
|
}
|
|
|
|
static void i82596_rbd_read(I82596State *s, hwaddr addr,
|
|
struct i82596_rx_buffer_desc *rbd)
|
|
{
|
|
rbd->actual_count = get_uint16(addr + 0x0);
|
|
rbd->next_rbd_addr = get_uint32(addr + 0x4);
|
|
rbd->buffer_addr = get_uint32(addr + 0x8);
|
|
rbd->size = get_uint16(addr + 0xC);
|
|
}
|
|
|
|
static void i82596_rbd_write(I82596State *s, hwaddr addr,
|
|
struct i82596_rx_buffer_desc *rbd)
|
|
{
|
|
set_uint16(addr + 0x0, rbd->actual_count);
|
|
set_uint32(addr + 0x4, rbd->next_rbd_addr);
|
|
set_uint32(addr + 0x8, rbd->buffer_addr);
|
|
set_uint16(addr + 0xC, rbd->size);
|
|
}
|
|
|
|
static int i82596_tx_copy_buffers(I82596State *s, hwaddr tfd_addr,
|
|
struct i82596_tx_descriptor *desc)
|
|
{
|
|
bool simplified_mode = !(desc->command & CMD_FLEX);
|
|
uint32_t total_len = 0;
|
|
uint32_t tbd_addr;
|
|
struct i82596_tx_buffer_desc tbd;
|
|
|
|
s->tx_frame_len = 0;
|
|
|
|
if (simplified_mode) {
|
|
uint16_t frame_len = desc->tcb_count & SIZE_MASK;
|
|
if (frame_len == 0 || frame_len > sizeof(s->tx_buffer)) {
|
|
return -1;
|
|
}
|
|
address_space_read(&address_space_memory, tfd_addr + 16,
|
|
MEMTXATTRS_UNSPECIFIED, s->tx_buffer, frame_len);
|
|
total_len = frame_len;
|
|
|
|
} else {
|
|
tbd_addr = desc->tbd_addr;
|
|
while (tbd_addr != I596_NULL && tbd_addr != 0) {
|
|
uint16_t buf_size;
|
|
uint32_t buf_addr;
|
|
tbd_addr = i82596_translate_address(s, tbd_addr, false);
|
|
if (tbd_addr == 0 || tbd_addr == I596_NULL) {
|
|
return -1;
|
|
}
|
|
i82596_tbd_read(s, tbd_addr, &tbd);
|
|
trace_i82596_tx_tbd(tbd_addr, tbd.size, tbd.buffer);
|
|
buf_size = tbd.size & SIZE_MASK;
|
|
buf_addr = i82596_translate_address(s, tbd.buffer, true);
|
|
|
|
if (total_len + buf_size > sizeof(s->tx_buffer)) {
|
|
return -1;
|
|
}
|
|
|
|
if (buf_size > 0 && buf_addr != 0 && buf_addr != I596_NULL) {
|
|
address_space_read(&address_space_memory, buf_addr,
|
|
MEMTXATTRS_UNSPECIFIED,
|
|
s->tx_buffer + total_len, buf_size);
|
|
total_len += buf_size;
|
|
}
|
|
if (tbd.size & I596_EOF) {
|
|
break;
|
|
}
|
|
tbd_addr = tbd.link;
|
|
}
|
|
}
|
|
|
|
s->tx_frame_len = total_len;
|
|
return total_len;
|
|
}
|
|
|
|
static int i82596_tx_process_frame(I82596State *s, bool insert_crc)
|
|
{
|
|
uint32_t total_len = s->tx_frame_len;
|
|
|
|
if (total_len == 0) {
|
|
return 0;
|
|
}
|
|
|
|
if (I596_NO_SRC_ADD_IN == 0 && total_len >= ETH_ALEN * 2) {
|
|
memcpy(&s->tx_buffer[ETH_ALEN], s->conf.macaddr.a, ETH_ALEN);
|
|
}
|
|
|
|
if (I596_PADDING && total_len < I596_MIN_FRAME_LEN) {
|
|
size_t pad_len = I596_MIN_FRAME_LEN - total_len;
|
|
memset(s->tx_buffer + total_len, 0, pad_len);
|
|
total_len = I596_MIN_FRAME_LEN;
|
|
}
|
|
|
|
if (insert_crc) {
|
|
total_len = i82596_append_crc(s, s->tx_buffer, total_len);
|
|
}
|
|
|
|
s->tx_frame_len = total_len;
|
|
return total_len;
|
|
}
|
|
|
|
static void i82596_tx_update_status(I82596State *s, hwaddr tfd_addr,
|
|
struct i82596_tx_descriptor *desc,
|
|
uint16_t tx_status,
|
|
uint16_t collision_count,
|
|
bool success)
|
|
{
|
|
desc->status_bits = STAT_C;
|
|
|
|
if (success) {
|
|
desc->status_bits |= STAT_OK;
|
|
} else {
|
|
desc->status_bits |= STAT_A;
|
|
}
|
|
|
|
if (collision_count > 0) {
|
|
desc->status_bits |= (collision_count & 0x0F);
|
|
}
|
|
|
|
i82596_tx_tfd_write(s, tfd_addr, desc);
|
|
}
|
|
|
|
static int i82596_tx_csma_cd(I82596State *s, uint16_t *tx_status)
|
|
{
|
|
int retry_count = 0;
|
|
bool medium_available;
|
|
|
|
if (I596_FULL_DUPLEX || I596_LOOPBACK) {
|
|
return 0;
|
|
}
|
|
|
|
while (retry_count < CSMA_MAX_RETRIES) {
|
|
medium_available = i82596_check_medium_status(s);
|
|
|
|
if (medium_available) {
|
|
break;
|
|
}
|
|
i82596_csma_backoff(s, retry_count);
|
|
retry_count++;
|
|
s->total_collisions++;
|
|
}
|
|
if (retry_count >= CSMA_MAX_RETRIES) {
|
|
*tx_status |= TX_ABORTED_ERRORS;
|
|
return -1;
|
|
}
|
|
if (retry_count > 0) {
|
|
*tx_status |= TX_COLLISIONS;
|
|
s->collision_events++;
|
|
}
|
|
|
|
return retry_count;
|
|
}
|
|
|
|
static void i82596_transmit(I82596State *s, uint32_t addr)
|
|
{
|
|
struct i82596_tx_descriptor tfd;
|
|
hwaddr tfd_addr = addr;
|
|
uint16_t tx_status = 0;
|
|
int collision_count = 0;
|
|
int frame_len;
|
|
bool success = true;
|
|
bool insert_crc;
|
|
|
|
i82596_tx_tfd_read(s, tfd_addr, &tfd);
|
|
trace_i82596_tx_tfd(tfd_addr, tfd.status_bits, tfd.command,
|
|
tfd.link_addr, tfd.tbd_addr);
|
|
|
|
s->current_tx_desc = tfd_addr;
|
|
insert_crc = (I596_NOCRC_INS == 0) && ((tfd.command & 0x10) == 0) &&
|
|
!I596_LOOPBACK;
|
|
collision_count = i82596_tx_csma_cd(s, &tx_status);
|
|
if (collision_count < 0) {
|
|
success = false;
|
|
goto tx_complete;
|
|
}
|
|
frame_len = i82596_tx_copy_buffers(s, tfd_addr, &tfd);
|
|
if (frame_len < 0) {
|
|
tx_status |= TX_ABORTED_ERRORS;
|
|
success = false;
|
|
goto tx_complete;
|
|
}
|
|
frame_len = i82596_tx_process_frame(s, insert_crc);
|
|
if (frame_len <= 0) {
|
|
tx_status |= TX_ABORTED_ERRORS;
|
|
success = false;
|
|
goto tx_complete;
|
|
}
|
|
s->last_tx_len = frame_len;
|
|
trace_i82596_transmit(frame_len, addr);
|
|
|
|
if (I596_LOOPBACK) {
|
|
i82596_receive(qemu_get_queue(s->nic), s->tx_buffer, frame_len);
|
|
} else {
|
|
if (s->nic) {
|
|
qemu_send_packet_raw(qemu_get_queue(s->nic), s->tx_buffer,
|
|
frame_len);
|
|
}
|
|
}
|
|
|
|
tx_complete:
|
|
i82596_tx_update_status(s, tfd_addr, &tfd, tx_status, collision_count,
|
|
success);
|
|
i82596_update_statistics(s, true, tx_status, collision_count);
|
|
if (tfd.command & CMD_INTR) {
|
|
i82596_update_cu_status(s, tfd.status_bits, true);
|
|
}
|
|
}
|
|
|
|
bool i82596_can_receive(NetClientState *nc)
|
|
{
|
|
I82596State *s = qemu_get_nic_opaque(nc);
|
|
|
|
if (I596_LOOPBACK || !s->lnkst) {
|
|
return false;
|
|
}
|
|
|
|
if (s->rx_status == RX_SUSPENDED || s->rx_status == RX_IDLE ||
|
|
s->rx_status == RX_NO_RESOURCES) {
|
|
return true;
|
|
}
|
|
|
|
if (!s->throttle_state && !I596_FULL_DUPLEX) {
|
|
return (s->queue_count < PACKET_QUEUE_SIZE);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static int i82596_validate_receive_state(I82596State *s, size_t *sz,
|
|
bool from_queue)
|
|
{
|
|
if (*sz < 14 || *sz > PKT_BUF_SZ - 4) {
|
|
trace_i82596_receive_analysis(">>> Packet size invalid");
|
|
return -1;
|
|
}
|
|
|
|
if (!from_queue && s->rx_status == RX_SUSPENDED) {
|
|
trace_i82596_receive_analysis(">>> Receiving is suspended");
|
|
return -1;
|
|
}
|
|
|
|
if (s->rx_status != RX_READY && s->rx_status != RX_SUSPENDED) {
|
|
trace_i82596_receive_analysis(">>> RU not ready");
|
|
return -1;
|
|
}
|
|
|
|
if (!s->lnkst) {
|
|
trace_i82596_receive_analysis(">>> Link is down");
|
|
return -1;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static bool i82596_check_packet_filter(I82596State *s, const uint8_t *buf,
|
|
uint16_t *is_broadcast)
|
|
{
|
|
static const uint8_t broadcast_macaddr[6] = {
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
|
|
|
|
if (I596_PROMISC || I596_LOOPBACK) {
|
|
trace_i82596_receive_analysis(
|
|
">>> packet received in promiscuous mode");
|
|
return true;
|
|
} else {
|
|
if (!memcmp(buf, broadcast_macaddr, 6)) {
|
|
/* broadcast address */
|
|
if (I596_BC_DISABLE) {
|
|
trace_i82596_receive_analysis(">>> broadcast packet rejected");
|
|
return false;
|
|
}
|
|
trace_i82596_receive_analysis(">>> broadcast packet received");
|
|
*is_broadcast = 1;
|
|
return true;
|
|
} else if (buf[0] & 0x01) {
|
|
/* multicast */
|
|
if (!I596_MC_ALL) {
|
|
trace_i82596_receive_analysis(">>> multicast packet rejected");
|
|
return false;
|
|
}
|
|
|
|
int mcast_idx = (net_crc32(buf, ETH_ALEN) & BITS(7, 2)) >> 2;
|
|
assert(mcast_idx < 8 * sizeof(s->mult));
|
|
|
|
if (!(s->mult[mcast_idx >> 3] & (1 << (mcast_idx & 7)))) {
|
|
trace_i82596_receive_analysis(">>> multicast address mismatch");
|
|
return false;
|
|
}
|
|
|
|
trace_i82596_receive_analysis(">>> multicast packet received");
|
|
*is_broadcast = 1;
|
|
return true;
|
|
} else if (!memcmp(s->conf.macaddr.a, buf, 6)) {
|
|
/* match */
|
|
trace_i82596_receive_analysis(
|
|
">>> physical address matching packet received");
|
|
return true;
|
|
} else {
|
|
trace_i82596_receive_analysis(">>> unknown packet");
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* MONITOR MODE */
|
|
static bool i82596_monitor(I82596State *s, const uint8_t *buf, size_t sz,
|
|
bool packet_passes_filter)
|
|
{
|
|
if (I596_MONITOR_MODE == MONITOR_DISABLED) {
|
|
return true;
|
|
}
|
|
if (sz < I596_MIN_FRAME_LEN) {
|
|
s->short_fr_error++;
|
|
}
|
|
if ((sz % 2) != 0) {
|
|
s->align_err++;
|
|
}
|
|
|
|
switch (I596_MONITOR_MODE) {
|
|
case MONITOR_NORMAL: /* No monitor, just add to total frames */
|
|
if (packet_passes_filter) {
|
|
s->total_good_frames++;
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
break;
|
|
case MONITOR_FILTERED: /* Monitor only filtered packets */
|
|
s->total_frames++;
|
|
if (packet_passes_filter) {
|
|
s->total_good_frames++;
|
|
}
|
|
return false;
|
|
case MONITOR_ALL: /* Monitor all packets */
|
|
s->total_frames++;
|
|
if (packet_passes_filter) {
|
|
s->total_good_frames++;
|
|
}
|
|
return false;
|
|
|
|
default:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
static void i82596_update_rx_state(I82596State *s, int new_state)
|
|
{
|
|
if (s->rx_status != new_state) {
|
|
trace_i82596_rx_state_change(s->rx_status, new_state);
|
|
}
|
|
|
|
s->rx_status = new_state;
|
|
|
|
switch (new_state) {
|
|
case RX_NO_RESOURCES:
|
|
if (!s->rnr_signaled) {
|
|
s->scb_status |= SCB_STATUS_RNR;
|
|
s->rnr_signaled = true;
|
|
}
|
|
break;
|
|
case RX_SUSPENDED:
|
|
if (!s->rnr_signaled) {
|
|
s->scb_status |= SCB_STATUS_RNR;
|
|
s->rnr_signaled = true;
|
|
}
|
|
|
|
if (s->queue_count > 0 && !s->flushing_queue) {
|
|
i82596_flush_packet_queue(s);
|
|
if (s->queue_count > 0) {
|
|
timer_mod(s->flush_queue_timer,
|
|
qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 50000);
|
|
}
|
|
}
|
|
break;
|
|
case RX_READY:
|
|
/* When RU becomes ready, flush buffered packets */
|
|
if (s->queue_count > 0) {
|
|
i82596_flush_packet_queue(s);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void i82596_rx_store_frame_header(I82596State *s,
|
|
struct i82596_rx_descriptor *rfd,
|
|
const uint8_t *buf, size_t size)
|
|
{
|
|
memcpy(rfd->dest_addr, buf, 6);
|
|
if (size >= 12) {
|
|
memcpy(rfd->src_addr, buf + 6, 6);
|
|
}
|
|
if (size >= 14) {
|
|
rfd->length_field = (buf[12] << 8) | buf[13];
|
|
}
|
|
}
|
|
|
|
static size_t i82596_rx_copy_to_rfd(I82596State *s, hwaddr rfd_addr,
|
|
const uint8_t *buf, size_t size,
|
|
size_t rfd_size)
|
|
{
|
|
size_t to_copy = MIN(size, rfd_size);
|
|
size_t data_offset = 0x1E; /* Bypassing the header */
|
|
|
|
if (to_copy > 0) {
|
|
address_space_write(&address_space_memory, rfd_addr + data_offset,
|
|
MEMTXATTRS_UNSPECIFIED, buf, to_copy);
|
|
}
|
|
return to_copy;
|
|
}
|
|
|
|
static size_t i82596_rx_copy_to_rbds(I82596State *s, hwaddr rbd_addr,
|
|
const uint8_t *buf, size_t size,
|
|
bool *out_of_resources,
|
|
hwaddr *remaining_rbd)
|
|
{
|
|
size_t bytes_copied = 0;
|
|
hwaddr current_rbd = rbd_addr;
|
|
*out_of_resources = false;
|
|
*remaining_rbd = I596_NULL;
|
|
|
|
while (bytes_copied < size && current_rbd != I596_NULL &&
|
|
current_rbd != 0) {
|
|
struct i82596_rx_buffer_desc rbd;
|
|
i82596_rbd_read(s, current_rbd, &rbd);
|
|
trace_i82596_rx_rbd(current_rbd, rbd.actual_count, rbd.buffer_addr,
|
|
rbd.size);
|
|
if (rbd.size & 0x4000) { /* P bit set */
|
|
break;
|
|
}
|
|
|
|
uint16_t buf_size = rbd.size & 0x3FFF;
|
|
|
|
if (buf_size == 0) {
|
|
current_rbd = i82596_translate_address(s, rbd.next_rbd_addr, false);
|
|
continue;
|
|
}
|
|
|
|
hwaddr buf_addr = i82596_translate_address(s, rbd.buffer_addr, true);
|
|
if (buf_addr == 0 || buf_addr == I596_NULL) {
|
|
*out_of_resources = true;
|
|
break;
|
|
}
|
|
size_t remaining = size - bytes_copied;
|
|
size_t to_copy = MIN(remaining, buf_size);
|
|
if (to_copy > 0) {
|
|
address_space_write(&address_space_memory, buf_addr,
|
|
MEMTXATTRS_UNSPECIFIED,
|
|
buf + bytes_copied, to_copy);
|
|
bytes_copied += to_copy;
|
|
}
|
|
rbd.actual_count = to_copy | 0x4000; /* Set F (filled) bit */
|
|
if (bytes_copied >= size) {
|
|
rbd.actual_count |= 0x8000; /* Set EOF bit (bit 15) */
|
|
}
|
|
i82596_rbd_write(s, current_rbd, &rbd);
|
|
if (rbd.size & CMD_EOL) { /* EL bit */
|
|
if (bytes_copied < size) {
|
|
*out_of_resources = true;
|
|
}
|
|
current_rbd = I596_NULL;
|
|
break;
|
|
}
|
|
current_rbd = i82596_translate_address(s, rbd.next_rbd_addr, false);
|
|
}
|
|
|
|
*remaining_rbd = current_rbd;
|
|
return bytes_copied;
|
|
}
|
|
|
|
static inline size_t i82596_get_crc_size(I82596State *s)
|
|
{
|
|
return I596_CRC16_32 ? 4 : 2;
|
|
}
|
|
|
|
static ssize_t i82596_receive_packet(I82596State *s, const uint8_t *buf,
|
|
size_t size, bool from_queue)
|
|
{
|
|
struct i82596_rx_descriptor rfd;
|
|
uint32_t rfd_addr, rbd_addr;
|
|
uint16_t rx_status = 0;
|
|
uint16_t is_broadcast = 0;
|
|
bool packet_completed = true;
|
|
bool simplified_mode = false;
|
|
size_t frame_size = size;
|
|
size_t payload_size = 0;
|
|
size_t bytes_copied = 0;
|
|
const uint8_t *packet_data = buf;
|
|
bool out_of_resources = false;
|
|
size_t crc_size = i82596_get_crc_size(s);
|
|
|
|
trace_i82596_receive_packet(buf, size);
|
|
|
|
if (i82596_validate_receive_state(s, &size, from_queue) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
bool passes_filter = i82596_check_packet_filter(s, buf, &is_broadcast);
|
|
|
|
if (!i82596_monitor(s, buf, size, passes_filter) && (!passes_filter)) {
|
|
return size;
|
|
}
|
|
|
|
|
|
rfd_addr = get_uint32(s->scb + 8);
|
|
|
|
if (rfd_addr == 0 || rfd_addr == I596_NULL) {
|
|
i82596_update_rx_state(s, RX_NO_RESOURCES);
|
|
s->resource_err++;
|
|
set_uint16(s->scb, get_uint16(s->scb) | SCB_STATUS_RNR);
|
|
i82596_update_scb_irq(s, true);
|
|
return -1;
|
|
}
|
|
|
|
i82596_rx_rfd_read(s, rfd_addr, &rfd);
|
|
trace_i82596_rx_rfd(rfd_addr, rfd.status_bits, rfd.command,
|
|
rfd.link, rfd.rbd_addr);
|
|
|
|
s->current_rx_desc = rfd_addr;
|
|
|
|
if (rfd.status_bits & STAT_C) {
|
|
return -1;
|
|
}
|
|
|
|
/* 0: Simplified Mode 1: Flexible Mode */
|
|
simplified_mode = !(rfd.command & CMD_FLEX);
|
|
|
|
set_uint16(rfd_addr, STAT_B);
|
|
|
|
if (frame_size < 14) {
|
|
trace_i82596_rx_short_frame(frame_size);
|
|
rx_status |= RX_LENGTH_ERRORS;
|
|
i82596_record_error(s, RX_LENGTH_ERRORS, false);
|
|
s->short_fr_error++;
|
|
packet_completed = false;
|
|
goto rx_complete;
|
|
}
|
|
|
|
payload_size = frame_size;
|
|
do {
|
|
if (simplified_mode && I596_LOOPBACK) {
|
|
uint16_t rfd_size = rfd.size & 0x3FFF;
|
|
|
|
if (rfd_size % 2 != 0) {
|
|
rx_status |= RX_LENGTH_ERRORS;
|
|
i82596_record_error(s, RX_LENGTH_ERRORS, false);
|
|
s->align_err++;
|
|
packet_completed = false;
|
|
goto rx_complete;
|
|
}
|
|
|
|
if (payload_size > rfd_size) {
|
|
rx_status |= RFD_STATUS_TRUNC;
|
|
payload_size = rfd_size;
|
|
packet_completed = !SAVE_BAD_FRAMES ? false : true;
|
|
}
|
|
|
|
if (payload_size > 0) {
|
|
bytes_copied = i82596_rx_copy_to_rfd(s, rfd_addr, packet_data,
|
|
payload_size, rfd_size);
|
|
}
|
|
|
|
i82596_rx_store_frame_header(s, &rfd, packet_data, frame_size);
|
|
|
|
} else {
|
|
uint16_t rfd_size = rfd.size & 0x3FFF; /* SIZE_MASK */
|
|
size_t rfd_frame_size = 0;
|
|
size_t remaining_to_copy = payload_size - bytes_copied;
|
|
if (rfd_size > 0 && remaining_to_copy > 0) {
|
|
size_t data_offset = 0x10;
|
|
|
|
rfd_frame_size = MIN(remaining_to_copy, rfd_size);
|
|
address_space_write(&address_space_memory,
|
|
rfd_addr + data_offset,
|
|
MEMTXATTRS_UNSPECIFIED,
|
|
packet_data + bytes_copied,
|
|
rfd_frame_size);
|
|
bytes_copied += rfd_frame_size;
|
|
}
|
|
|
|
if (bytes_copied < payload_size) {
|
|
size_t remaining = payload_size - bytes_copied;
|
|
rbd_addr = i82596_translate_address(s, rfd.rbd_addr, false);
|
|
|
|
if (rbd_addr == I596_NULL || rbd_addr == 0) {
|
|
rx_status |= RFD_STATUS_TRUNC | RFD_STATUS_NOBUFS;
|
|
i82596_record_error(s, RFD_STATUS_NOBUFS, false);
|
|
packet_completed = true;
|
|
break;
|
|
} else {
|
|
hwaddr remaining_rbd = I596_NULL;
|
|
size_t rbd_bytes = i82596_rx_copy_to_rbds(
|
|
s, rbd_addr,
|
|
packet_data + bytes_copied,
|
|
remaining,
|
|
&out_of_resources,
|
|
&remaining_rbd);
|
|
bytes_copied += rbd_bytes;
|
|
|
|
uint32_t next_rfd = i82596_translate_address(s, rfd.link,
|
|
false);
|
|
if (next_rfd != I596_NULL && next_rfd != 0) {
|
|
if (remaining_rbd != I596_NULL && remaining_rbd != 0) {
|
|
trace_i82596_rx_rfd_update(next_rfd, remaining_rbd);
|
|
set_uint32(next_rfd + 8, remaining_rbd);
|
|
} else {
|
|
set_uint32(next_rfd + 8, I596_NULL);
|
|
}
|
|
}
|
|
|
|
if (out_of_resources) {
|
|
trace_i82596_rx_out_of_rbds();
|
|
i82596_record_error(s, RFD_STATUS_NOBUFS, false);
|
|
rx_status |= RFD_STATUS_TRUNC | RFD_STATUS_NOBUFS;
|
|
packet_completed = true;
|
|
break;
|
|
}
|
|
|
|
if (bytes_copied < payload_size) {
|
|
trace_i82596_rx_incomplete(bytes_copied, payload_size);
|
|
rx_status |= RFD_STATUS_TRUNC;
|
|
packet_completed = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} while (bytes_copied < payload_size);
|
|
|
|
rx_complete:
|
|
if (I596_CRCINM && !I596_LOOPBACK && packet_completed) {
|
|
uint8_t crc_data[4];
|
|
size_t crc_len = crc_size;
|
|
|
|
if (I596_CRC16_32) {
|
|
uint32_t crc = crc32(~0, packet_data, frame_size);
|
|
crc = cpu_to_be32(crc);
|
|
memcpy(crc_data, &crc, 4);
|
|
} else {
|
|
uint16_t crc = i82596_calculate_crc16(packet_data, frame_size);
|
|
crc = cpu_to_be16(crc);
|
|
memcpy(crc_data, &crc, 2);
|
|
}
|
|
|
|
if (simplified_mode) {
|
|
address_space_write(&address_space_memory,
|
|
rfd_addr + 0x1E + bytes_copied,
|
|
MEMTXATTRS_UNSPECIFIED, crc_data, crc_len);
|
|
}
|
|
}
|
|
|
|
if (packet_completed) {
|
|
rx_status |= STAT_C | STAT_OK;
|
|
if (is_broadcast) {
|
|
rx_status |= 0x0001;
|
|
}
|
|
} else {
|
|
rx_status |= STAT_B;
|
|
}
|
|
|
|
rfd.status_bits = rx_status & ~STAT_B;
|
|
rfd.actual_count = (bytes_copied & 0x3FFF) | 0x4000;
|
|
if (packet_completed) {
|
|
rfd.actual_count |= I596_EOF;
|
|
}
|
|
|
|
i82596_rx_desc_write(s, rfd_addr, &rfd, (simplified_mode || I596_LOOPBACK));
|
|
|
|
if (rfd.command & CMD_SUSP) {
|
|
i82596_update_rx_state(s, RX_SUSPENDED);
|
|
return size;
|
|
}
|
|
|
|
if (rfd.command & CMD_EOL) {
|
|
i82596_update_rx_state(s, RX_SUSPENDED);
|
|
return size;
|
|
}
|
|
|
|
if (packet_completed && s->rx_status == RX_READY) {
|
|
uint32_t next_rfd_addr = i82596_translate_address(s, rfd.link, false);
|
|
if (next_rfd_addr != 0 && next_rfd_addr != I596_NULL) {
|
|
set_uint32(s->scb + 8, next_rfd_addr);
|
|
}
|
|
|
|
s->scb_status |= SCB_STATUS_FR;
|
|
i82596_update_scb_irq(s, true);
|
|
}
|
|
trace_i82596_rx_complete(s->crc_err, s->align_err, s->resource_err);
|
|
return size;
|
|
}
|
|
|
|
ssize_t i82596_receive(NetClientState *nc, const uint8_t *buf, size_t size)
|
|
{
|
|
I82596State *s = qemu_get_nic_opaque(nc);
|
|
|
|
if (!I596_FULL_DUPLEX && !s->throttle_state) {
|
|
if (s->queue_count < PACKET_QUEUE_SIZE) {
|
|
goto queue_packet;
|
|
}
|
|
trace_i82596_receive_suspended();
|
|
return size;
|
|
}
|
|
|
|
if (s->rx_status != RX_READY) {
|
|
if (s->queue_count >= PACKET_QUEUE_SIZE) {
|
|
trace_i82596_receive_queue_full();
|
|
s->over_err++;
|
|
set_uint32(s->scb + 22, s->over_err);
|
|
i82596_record_error(s, RX_OVER_ERRORS, false);
|
|
return size;
|
|
}
|
|
queue_packet:
|
|
if (size <= PKT_BUF_SZ) {
|
|
memcpy(s->packet_queue[s->queue_head], buf, size);
|
|
s->packet_queue_len[s->queue_head] = size;
|
|
s->queue_head = (s->queue_head + 1) % PACKET_QUEUE_SIZE;
|
|
s->queue_count++;
|
|
}
|
|
return size;
|
|
}
|
|
|
|
return i82596_receive_packet(s, buf, size, false);
|
|
}
|
|
|
|
ssize_t i82596_receive_iov(NetClientState *nc, const struct iovec *iov,
|
|
int iovcnt)
|
|
{
|
|
size_t sz = 0;
|
|
uint8_t *buf;
|
|
int i;
|
|
for (i = 0; i < iovcnt; i++) {
|
|
sz += iov[i].iov_len;
|
|
}
|
|
trace_i82596_receive_iov(sz, iovcnt);
|
|
if (sz == 0) {
|
|
return -1;
|
|
}
|
|
buf = g_malloc(sz);
|
|
if (!buf) {
|
|
return -1;
|
|
}
|
|
size_t offset = 0;
|
|
for (i = 0; i < iovcnt; i++) {
|
|
if (iov[i].iov_base == NULL) {
|
|
g_free(buf);
|
|
return -1;
|
|
}
|
|
memcpy(buf + offset, iov[i].iov_base, iov[i].iov_len);
|
|
offset += iov[i].iov_len;
|
|
}
|
|
i82596_receive(nc, buf, sz);
|
|
g_free(buf);
|
|
return sz;
|
|
}
|
|
|
|
static void set_individual_address(I82596State *s, uint32_t addr)
|
|
{
|
|
NetClientState *nc;
|
|
uint8_t *m;
|
|
|
|
nc = qemu_get_queue(s->nic);
|
|
m = s->conf.macaddr.a;
|
|
address_space_read(&address_space_memory, addr + 8,
|
|
MEMTXATTRS_UNSPECIFIED, m, ETH_ALEN);
|
|
qemu_format_nic_info_str(nc, m);
|
|
trace_i82596_new_mac(nc->info_str);
|
|
}
|
|
|
|
static void set_multicast_list(I82596State *s, uint32_t addr)
|
|
{
|
|
uint16_t mc_count, i;
|
|
|
|
memset(&s->mult[0], 0, sizeof(s->mult));
|
|
mc_count = get_uint16(addr + 8) / ETH_ALEN;
|
|
addr += 10;
|
|
if (mc_count > MAX_MC_CNT) {
|
|
mc_count = MAX_MC_CNT;
|
|
}
|
|
for (i = 0; i < mc_count; i++) {
|
|
uint8_t multicast_addr[ETH_ALEN];
|
|
address_space_read(&address_space_memory, addr + i * ETH_ALEN,
|
|
MEMTXATTRS_UNSPECIFIED, multicast_addr, ETH_ALEN);
|
|
unsigned mcast_idx = (net_crc32(multicast_addr, ETH_ALEN) &
|
|
BITS(7, 2)) >> 2;
|
|
assert(mcast_idx < 8 * sizeof(s->mult));
|
|
s->mult[mcast_idx >> 3] |= (1 << (mcast_idx & 7));
|
|
}
|
|
trace_i82596_set_multicast(mc_count);
|
|
}
|
|
|
|
void i82596_set_link_status(NetClientState *nc)
|
|
{
|
|
I82596State *s = qemu_get_nic_opaque(nc);
|
|
bool was_up = s->lnkst != 0;
|
|
|
|
s->lnkst = nc->link_down ? 0 : 0x8000;
|
|
bool is_up = s->lnkst != 0;
|
|
|
|
if (!was_up && is_up && s->rx_status == RX_READY) {
|
|
qemu_flush_queued_packets(qemu_get_queue(s->nic));
|
|
}
|
|
}
|
|
|
|
static bool i82596_check_medium_status(I82596State *s)
|
|
{
|
|
if (I596_FULL_DUPLEX) {
|
|
return true;
|
|
}
|
|
|
|
if (!s->throttle_state) {
|
|
return false;
|
|
}
|
|
|
|
if (!I596_LOOPBACK && (qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) % 100 < 5)) {
|
|
s->collision_events++;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static int i82596_csma_backoff(I82596State *s, int retry_count)
|
|
{
|
|
int backoff_factor, slot_count, backoff_time;
|
|
|
|
backoff_factor = MIN(retry_count + 1, CSMA_BACKOFF_LIMIT);
|
|
slot_count = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) % (1 << backoff_factor);
|
|
backoff_time = slot_count * CSMA_SLOT_TIME;
|
|
|
|
return backoff_time;
|
|
}
|
|
|
|
static uint16_t i82596_calculate_crc16(const uint8_t *data, size_t len)
|
|
{
|
|
uint16_t crc = 0xFFFF;
|
|
size_t i, j;
|
|
|
|
for (i = 0; i < len; i++) {
|
|
crc ^= data[i] << 8;
|
|
for (j = 0; j < 8; j++) {
|
|
if (crc & 0x8000) {
|
|
crc = (crc << 1) ^ 0x1021;
|
|
} else {
|
|
crc <<= 1;
|
|
}
|
|
}
|
|
}
|
|
return crc;
|
|
}
|
|
|
|
static size_t i82596_append_crc(I82596State *s, uint8_t *buffer, size_t len)
|
|
{
|
|
if (len + 4 > PKT_BUF_SZ) {
|
|
return len;
|
|
}
|
|
|
|
if (I596_CRC16_32) {
|
|
uint32_t crc = crc32(~0, buffer, len);
|
|
crc = cpu_to_be32(crc);
|
|
memcpy(&buffer[len], &crc, sizeof(crc));
|
|
return len + sizeof(crc);
|
|
} else {
|
|
uint16_t crc = i82596_calculate_crc16(buffer, len);
|
|
crc = cpu_to_be16(crc);
|
|
memcpy(&buffer[len], &crc, sizeof(crc));
|
|
return len + sizeof(crc);
|
|
}
|
|
}
|
|
|
|
static void i82596_update_statistics(I82596State *s, bool is_tx,
|
|
uint16_t error_flags,
|
|
uint16_t collision_count)
|
|
{
|
|
if (is_tx) {
|
|
if (collision_count > 0) {
|
|
s->tx_collisions += collision_count;
|
|
s->collision_events++;
|
|
s->total_collisions += collision_count;
|
|
set_uint32(s->scb + 32, s->tx_collisions);
|
|
}
|
|
if (error_flags) {
|
|
i82596_record_error(s, error_flags, true);
|
|
}
|
|
if (!(error_flags & (TX_ABORTED_ERRORS | TX_CARRIER_ERRORS))) {
|
|
s->tx_good_frames++;
|
|
set_uint32(s->scb + 36, s->tx_good_frames);
|
|
}
|
|
} else {
|
|
s->total_frames++;
|
|
set_uint32(s->scb + 40, s->total_frames);
|
|
if (error_flags) {
|
|
i82596_record_error(s, error_flags, false);
|
|
} else {
|
|
s->total_good_frames++;
|
|
set_uint32(s->scb + 44, s->total_good_frames);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Bus Throttle Functionality */
|
|
static void i82596_bus_throttle_timer(void *opaque)
|
|
{
|
|
I82596State *s = opaque;
|
|
|
|
if (s->throttle_state) {
|
|
s->throttle_state = false;
|
|
if (s->t_off > 0) {
|
|
timer_mod(s->throttle_timer,
|
|
qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
|
|
s->t_off * NANOSECONDS_PER_MICROSECOND);
|
|
} else {
|
|
s->throttle_state = true;
|
|
}
|
|
} else {
|
|
s->throttle_state = true;
|
|
if (s->t_on > 0 && s->t_on != 0xFFFF) {
|
|
timer_mod(s->throttle_timer,
|
|
qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
|
|
s->t_on * NANOSECONDS_PER_MICROSECOND);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void i82596_load_throttle_timers(I82596State *s, bool start_now)
|
|
{
|
|
uint16_t previous_t_on = s->t_on;
|
|
uint16_t previous_t_off = s->t_off;
|
|
s->t_on = get_uint16(s->scb + 36);
|
|
s->t_off = get_uint16(s->scb + 38);
|
|
|
|
bool values_changed = (s->t_on != previous_t_on ||
|
|
s->t_off != previous_t_off);
|
|
if (start_now || (values_changed && s->throttle_timer)) {
|
|
timer_del(s->throttle_timer);
|
|
s->throttle_state = true;
|
|
if (s->t_on > 0 && s->t_on != 0xFFFF && !I596_FULL_DUPLEX) {
|
|
timer_mod(s->throttle_timer,
|
|
qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
|
|
s->t_on * NANOSECONDS_PER_MICROSECOND);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void write_uint16(uint8_t *buffer, int offset, uint16_t value)
|
|
{
|
|
buffer[offset] = value >> 8;
|
|
buffer[offset + 1] = value & 0xFF;
|
|
}
|
|
static void write_uint32(uint8_t *buffer, int offset, uint32_t value)
|
|
{
|
|
write_uint16(buffer, offset, value >> 16);
|
|
write_uint16(buffer, offset + 2, value & 0xFFFF);
|
|
}
|
|
|
|
static void i82596_init_dump_area(I82596State *s, uint8_t *buffer)
|
|
{
|
|
memset(buffer, 0, DUMP_BUF_SZ);
|
|
|
|
printf("This is the dump area function for i82596 QEMU side\n"
|
|
"If you are seeing this message, please contact:\n"
|
|
"Soumyajyotii Sarkar <soumyajyotisarkar23@gmail.com>\n"
|
|
"With the process in which you encountered this issue:\n"
|
|
"This still needs developement so,\n"
|
|
"I will be more than delighted to help you out!\n"
|
|
);
|
|
|
|
write_uint16(buffer, 0x00, (s->config[5] << 8) | s->config[4]);
|
|
write_uint16(buffer, 0x02, (s->config[3] << 8) | s->config[2]);
|
|
write_uint16(buffer, 0x04, (s->config[9] << 8) | s->config[8]);
|
|
write_uint16(buffer, 0x06, (s->config[7] << 8) | s->config[6]);
|
|
write_uint16(buffer, 0x08, (s->config[13] << 8) | s->config[12]);
|
|
write_uint16(buffer, 0x0A, (s->config[11] << 8) | s->config[10]);
|
|
|
|
buffer[0x0C] = s->conf.macaddr.a[0];
|
|
buffer[0x0D] = s->conf.macaddr.a[1];
|
|
buffer[0x10] = s->conf.macaddr.a[2];
|
|
buffer[0x11] = s->conf.macaddr.a[3];
|
|
buffer[0x12] = s->conf.macaddr.a[4];
|
|
buffer[0x13] = s->conf.macaddr.a[5];
|
|
|
|
if (s->last_tx_len > 0) {
|
|
uint32_t tx_crc = crc32(~0, s->tx_buffer, s->last_tx_len);
|
|
write_uint16(buffer, 0x14, tx_crc & 0xFFFF);
|
|
write_uint16(buffer, 0x16, tx_crc >> 16);
|
|
}
|
|
|
|
memcpy(&buffer[0x24], s->mult, sizeof(s->mult));
|
|
|
|
buffer[0xB0] = s->cu_status;
|
|
buffer[0xB1] = s->rx_status;
|
|
|
|
write_uint32(buffer, 0xB4, s->crc_err);
|
|
write_uint32(buffer, 0xB8, s->align_err);
|
|
write_uint32(buffer, 0xBC, s->resource_err);
|
|
write_uint32(buffer, 0xC0, s->over_err);
|
|
|
|
write_uint32(buffer, 0xC4, s->short_fr_error);
|
|
write_uint32(buffer, 0xC8, s->total_frames);
|
|
write_uint32(buffer, 0xCC, s->total_good_frames);
|
|
|
|
buffer[0xD0] = I596_PROMISC ? 1 : 0;
|
|
buffer[0xD1] = I596_BC_DISABLE ? 1 : 0;
|
|
buffer[0xD2] = I596_FULL_DUPLEX ? 1 : 0;
|
|
buffer[0xD3] = I596_LOOPBACK;
|
|
|
|
uint8_t mc_count = 0;
|
|
for (int i = 0; i < sizeof(s->mult); i++) {
|
|
uint8_t byte = s->mult[i];
|
|
while (byte) {
|
|
if (byte & 0x01) {
|
|
mc_count++;
|
|
}
|
|
byte >>= 1;
|
|
}
|
|
}
|
|
buffer[0xD4] = mc_count;
|
|
buffer[0xD5] = I596_NOCRC_INS ? 1 : 0;
|
|
buffer[0xD6] = I596_CRC16_32 ? 1 : 0;
|
|
|
|
write_uint16(buffer, 0xD8, s->lnkst);
|
|
buffer[0xDA] = I596_MONITOR_MODE;
|
|
write_uint32(buffer, 0xDC, s->collision_events);
|
|
|
|
write_uint16(buffer, 0x110, s->t_on);
|
|
write_uint16(buffer, 0x112, s->t_off);
|
|
write_uint16(buffer, 0x114, s->throttle_state ? 0x0001 : 0x0000);
|
|
write_uint16(buffer, 0x120, s->sysbus);
|
|
write_uint16(buffer, 0x128, s->scb_status);
|
|
write_uint32(buffer, 0, 0xFFFF0000);
|
|
}
|
|
|
|
static void i82596_port_dump(I82596State *s, uint32_t dump_addr)
|
|
{
|
|
uint8_t dump_buffer[DUMP_BUF_SZ];
|
|
|
|
i82596_init_dump_area(s, dump_buffer);
|
|
|
|
address_space_write(&address_space_memory, dump_addr,
|
|
MEMTXATTRS_UNSPECIFIED, dump_buffer, sizeof(dump_buffer));
|
|
|
|
set_uint32(dump_addr, 0xFFFF0000);
|
|
s->scb_status |= SCB_STATUS_CX;
|
|
s->send_irq = 1;
|
|
}
|
|
|
|
static void i82596_command_dump(I82596State *s, uint32_t cmd_addr)
|
|
{
|
|
uint32_t dump_addr;
|
|
uint8_t dump_buffer[DUMP_BUF_SZ];
|
|
uint16_t cmd = get_uint16(cmd_addr + 2);
|
|
uint16_t status;
|
|
|
|
dump_addr = get_uint32(cmd_addr + 8);
|
|
|
|
i82596_init_dump_area(s, dump_buffer);
|
|
address_space_write(&address_space_memory, dump_addr,
|
|
MEMTXATTRS_UNSPECIFIED, dump_buffer, sizeof(dump_buffer));
|
|
status = STAT_C | STAT_OK;
|
|
set_uint16(cmd_addr, status);
|
|
if (cmd & CMD_INTR) {
|
|
s->scb_status |= SCB_STATUS_CX;
|
|
s->send_irq = 1;
|
|
}
|
|
if (cmd & CMD_SUSP) {
|
|
s->cu_status = CU_SUSPENDED;
|
|
s->scb_status |= SCB_STATUS_CNA;
|
|
}
|
|
}
|
|
|
|
static void i82596_configure(I82596State *s, uint32_t addr)
|
|
{
|
|
uint8_t byte_cnt;
|
|
byte_cnt = get_byte(addr + 8) & 0x0f;
|
|
byte_cnt = MAX(byte_cnt, 4);
|
|
byte_cnt = MIN(byte_cnt, sizeof(s->config));
|
|
s->config[2] &= 0x82; /* mask valid bits */
|
|
s->config[2] |= 0x40;
|
|
s->config[7] &= 0xf7; /* clear zero bit */
|
|
|
|
address_space_read(&address_space_memory, addr + 8,
|
|
MEMTXATTRS_UNSPECIFIED, s->config, byte_cnt);
|
|
|
|
if (byte_cnt > 12) {
|
|
s->config[12] &= 0x40;
|
|
|
|
if (byte_cnt > 11) {
|
|
uint8_t monitor_mode = I596_MONITOR_MODE;
|
|
s->config[11] &= ~0xC0; /* Clear bits 6-7 */
|
|
s->config[11] |= (monitor_mode << 6); /* Set monitor mode */
|
|
}
|
|
}
|
|
|
|
if (s->rx_status == RX_READY) {
|
|
timer_mod(s->flush_queue_timer,
|
|
qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 100000000);
|
|
}
|
|
|
|
s->scb_status |= SCB_STATUS_CNA;
|
|
s->config[13] |= 0x3f;
|
|
qemu_set_irq(s->irq, 1);
|
|
}
|
|
|
|
static void i82596_update_scb_irq(I82596State *s, bool trigger)
|
|
{
|
|
update_scb_status(s);
|
|
|
|
if (trigger) {
|
|
s->send_irq = 1;
|
|
qemu_set_irq(s->irq, 1);
|
|
}
|
|
}
|
|
|
|
static void i82596_update_cu_status(I82596State *s, uint16_t cmd_status,
|
|
bool generate_interrupt)
|
|
{
|
|
if (cmd_status & STAT_C) {
|
|
if (cmd_status & STAT_OK) {
|
|
if (s->cu_status == CU_ACTIVE && s->cmd_p == I596_NULL) {
|
|
s->cu_status = CU_IDLE;
|
|
s->scb_status |= SCB_STATUS_CNA;
|
|
}
|
|
} else {
|
|
s->cu_status = CU_IDLE;
|
|
s->scb_status |= SCB_STATUS_CNA;
|
|
}
|
|
|
|
if (generate_interrupt) {
|
|
s->scb_status |= SCB_STATUS_CX;
|
|
i82596_update_scb_irq(s, true);
|
|
}
|
|
}
|
|
|
|
update_scb_status(s);
|
|
}
|
|
|
|
/**
|
|
* Update SCB Status
|
|
* Synchronizes device state with SCB status word and statistics counters.
|
|
* This function is called frequently to keep the kernel driver updated.
|
|
*/
|
|
static void update_scb_status(I82596State *s)
|
|
{
|
|
s->scb_status = (s->scb_status & 0xf000)
|
|
| (s->cu_status << 8) | (s->rx_status << 4) | (s->lnkst >> 8);
|
|
set_uint16(s->scb, s->scb_status);
|
|
|
|
set_uint32(s->scb + 28, s->tx_aborted_errors);
|
|
set_uint32(s->scb + 32, s->tx_collisions);
|
|
set_uint32(s->scb + 36, s->tx_good_frames);
|
|
|
|
set_uint32(s->scb + 16, s->crc_err);
|
|
set_uint32(s->scb + 18, s->align_err);
|
|
set_uint32(s->scb + 20, s->resource_err);
|
|
set_uint32(s->scb + 22, s->over_err);
|
|
set_uint32(s->scb + 24, s->rcvdt_err);
|
|
set_uint32(s->scb + 26, s->short_fr_error);
|
|
}
|
|
|
|
static void command_loop(I82596State *s)
|
|
{
|
|
while (s->cu_status == CU_ACTIVE && s->cmd_p != I596_NULL &&
|
|
s->cmd_p != 0) {
|
|
uint16_t status = get_uint16(s->cmd_p);
|
|
if (status & (STAT_C | STAT_B)) {
|
|
uint32_t next = get_uint32(s->cmd_p + 4);
|
|
if (next == 0 || next == s->cmd_p) {
|
|
s->cmd_p = I596_NULL;
|
|
s->cu_status = CU_IDLE;
|
|
s->scb_status |= SCB_STATUS_CNA;
|
|
break;
|
|
}
|
|
s->cmd_p = i82596_translate_address(s, next, false);
|
|
continue;
|
|
}
|
|
set_uint16(s->cmd_p, STAT_B);
|
|
uint16_t cmd = get_uint16(s->cmd_p + 2);
|
|
uint32_t next_addr = get_uint32(s->cmd_p + 4);
|
|
next_addr = (next_addr == 0) ? I596_NULL :
|
|
i82596_translate_address(s, next_addr, false);
|
|
switch (cmd & CMD_MASK) {
|
|
case CmdNOp:
|
|
break;
|
|
case CmdSASetup:
|
|
set_individual_address(s, s->cmd_p);
|
|
break;
|
|
case CmdConfigure:
|
|
i82596_configure(s, s->cmd_p);
|
|
break;
|
|
case CmdTDR:
|
|
set_uint32(s->cmd_p + 8, s->lnkst);
|
|
break;
|
|
case CmdTx:
|
|
i82596_transmit(s, s->cmd_p);
|
|
goto skip_status_update;
|
|
case CmdMulticastList:
|
|
set_multicast_list(s, s->cmd_p);
|
|
break;
|
|
case CmdDump:
|
|
i82596_command_dump(s, s->cmd_p);
|
|
break;
|
|
case CmdDiagnose:
|
|
break;
|
|
default:
|
|
printf("CMD_LOOP: Unknown command %d\n", cmd & CMD_MASK);
|
|
break;
|
|
}
|
|
|
|
status = get_uint16(s->cmd_p);
|
|
if (!(status & STAT_C)) {
|
|
set_uint16(s->cmd_p, STAT_C | STAT_OK);
|
|
}
|
|
|
|
skip_status_update:
|
|
if (cmd & CMD_INTR) {
|
|
s->scb_status |= SCB_STATUS_CX;
|
|
s->send_irq = 1;
|
|
}
|
|
|
|
bool stop = false;
|
|
|
|
if (cmd & CMD_SUSP) {
|
|
s->cu_status = CU_SUSPENDED;
|
|
s->scb_status |= SCB_STATUS_CNA;
|
|
stop = true;
|
|
}
|
|
|
|
if (cmd & CMD_EOL) {
|
|
s->cmd_p = I596_NULL;
|
|
s->cu_status = CU_IDLE;
|
|
s->scb_status |= SCB_STATUS_CNA;
|
|
stop = true;
|
|
} else if (!stop) {
|
|
if (next_addr == 0 || next_addr == I596_NULL ||
|
|
next_addr == s->cmd_p) {
|
|
s->cmd_p = I596_NULL;
|
|
s->cu_status = CU_IDLE;
|
|
s->scb_status |= SCB_STATUS_CNA;
|
|
stop = true;
|
|
} else {
|
|
s->cmd_p = next_addr;
|
|
}
|
|
}
|
|
|
|
update_scb_status(s);
|
|
|
|
if (stop || s->cu_status != CU_ACTIVE) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
update_scb_status(s);
|
|
|
|
if (s->rx_status == RX_READY && s->nic) {
|
|
qemu_flush_queued_packets(qemu_get_queue(s->nic));
|
|
}
|
|
|
|
}
|
|
|
|
static void examine_scb(I82596State *s)
|
|
{
|
|
uint16_t command = get_uint16(s->scb + 2);
|
|
uint8_t cuc = (command >> 8) & 0x7;
|
|
uint8_t ruc = (command >> 4) & 0x7;
|
|
|
|
trace_i82596_scb_command(cuc, ruc);
|
|
|
|
set_uint16(s->scb + 2, 0);
|
|
s->scb_status &= ~(command & SCB_ACK_MASK);
|
|
|
|
if (command & SCB_STATUS_RNR) {
|
|
s->rnr_signaled = false;
|
|
}
|
|
|
|
/* Process Command Unit (CU) commands */
|
|
switch (cuc) {
|
|
case SCB_CUC_NOP:
|
|
break;
|
|
|
|
case SCB_CUC_START: {
|
|
uint32_t cmd_ptr = get_uint32(s->scb + 4);
|
|
s->cmd_p = i82596_translate_address(s, cmd_ptr, false);
|
|
s->cu_status = CU_ACTIVE;
|
|
break;
|
|
}
|
|
|
|
case SCB_CUC_RESUME:
|
|
if (s->cu_status == CU_SUSPENDED) {
|
|
s->cu_status = CU_ACTIVE;
|
|
}
|
|
break;
|
|
|
|
case SCB_CUC_SUSPEND:
|
|
s->cu_status = CU_SUSPENDED;
|
|
s->scb_status |= SCB_STATUS_CNA;
|
|
break;
|
|
|
|
case SCB_CUC_ABORT:
|
|
s->cu_status = CU_IDLE;
|
|
s->scb_status |= SCB_STATUS_CNA;
|
|
break;
|
|
|
|
case SCB_CUC_LOAD_THROTTLE: {
|
|
bool external_trigger = (s->sysbus & I82586_MODE);
|
|
i82596_load_throttle_timers(s, !external_trigger);
|
|
break;
|
|
}
|
|
|
|
case SCB_CUC_LOAD_START:
|
|
i82596_load_throttle_timers(s, true);
|
|
break;
|
|
}
|
|
|
|
/* Process Receive Unit (RU) commands */
|
|
switch (ruc) {
|
|
case SCB_RUC_NOP:
|
|
break;
|
|
|
|
case SCB_RUC_START: {
|
|
uint32_t rfd_log = get_uint32(s->scb + 8);
|
|
hwaddr rfd = i82596_translate_address(s, rfd_log, false);
|
|
|
|
if (rfd == 0 || rfd == I596_NULL) {
|
|
s->rx_status = RX_NO_RESOURCES;
|
|
s->scb_status |= SCB_STATUS_RNR;
|
|
break;
|
|
}
|
|
|
|
/* Find first usable RFD with valid RBD */
|
|
struct i82596_rx_descriptor test_rfd;
|
|
hwaddr test_rfd_addr = rfd;
|
|
uint32_t test_rfd_log = rfd_log;
|
|
hwaddr first_usable_rfd = 0;
|
|
uint32_t first_usable_rfd_log = 0;
|
|
bool found = false;
|
|
|
|
for (int i = 0; i < 10 && test_rfd_addr != 0 &&
|
|
test_rfd_addr != I596_NULL; i++) {
|
|
i82596_rx_rfd_read(s, test_rfd_addr, &test_rfd);
|
|
|
|
if (test_rfd.command & CMD_FLEX) {
|
|
hwaddr rbd = i82596_translate_address(s, test_rfd.rbd_addr,
|
|
false);
|
|
if (rbd != I596_NULL && rbd != 0) {
|
|
first_usable_rfd = test_rfd_addr;
|
|
first_usable_rfd_log = test_rfd_log;
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
test_rfd_log = test_rfd.link;
|
|
test_rfd_addr = i82596_translate_address(s, test_rfd.link, false);
|
|
}
|
|
|
|
if (found) {
|
|
s->current_rx_desc = first_usable_rfd;
|
|
s->last_good_rfa = first_usable_rfd_log;
|
|
i82596_update_rx_state(s, RX_READY);
|
|
|
|
if (first_usable_rfd != rfd) {
|
|
set_uint32(s->scb + 8, first_usable_rfd_log);
|
|
}
|
|
|
|
if (s->queue_count > 0) {
|
|
trace_i82596_flush_queue(s->queue_count);
|
|
i82596_flush_packet_queue(s);
|
|
}
|
|
|
|
qemu_flush_queued_packets(qemu_get_queue(s->nic));
|
|
} else {
|
|
s->rx_status = RX_NO_RESOURCES;
|
|
s->scb_status |= SCB_STATUS_RNR;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SCB_RUC_RESUME:
|
|
if (s->rx_status == RX_SUSPENDED) {
|
|
i82596_update_rx_state(s, RX_READY);
|
|
if (s->queue_count > 0) {
|
|
trace_i82596_flush_queue(s->queue_count);
|
|
i82596_flush_packet_queue(s);
|
|
}
|
|
qemu_flush_queued_packets(qemu_get_queue(s->nic));
|
|
}
|
|
break;
|
|
|
|
case SCB_RUC_SUSPEND:
|
|
s->rx_status = RX_SUSPENDED;
|
|
s->scb_status |= SCB_STATUS_RNR;
|
|
break;
|
|
|
|
case SCB_RUC_ABORT:
|
|
s->rx_status = RX_IDLE;
|
|
s->scb_status |= SCB_STATUS_RNR;
|
|
break;
|
|
}
|
|
|
|
if (command & 0x80) {
|
|
i82596_s_reset(s);
|
|
return;
|
|
}
|
|
if (s->cu_status == CU_ACTIVE) {
|
|
if (s->cmd_p == I596_NULL) {
|
|
s->cmd_p = get_uint32(s->scb + 4);
|
|
}
|
|
update_scb_status(s);
|
|
command_loop(s);
|
|
} else {
|
|
update_scb_status(s);
|
|
}
|
|
}
|
|
|
|
static void signal_ca(I82596State *s)
|
|
{
|
|
if (s->scp) {
|
|
/* CA after reset -> initialize with new SCP */
|
|
s->sysbus = get_byte(s->scp + 3);
|
|
s->mode = (s->sysbus >> 1) & 0x03; /* Extract mode bits (m0, m1) */
|
|
s->iscp = get_uint32(s->scp + 8);
|
|
|
|
s->scb = get_uint32(s->iscp + 4);
|
|
|
|
s->scb_base = (s->mode == I82596_MODE_LINEAR) ? 0 :
|
|
get_uint32(s->iscp + 8);
|
|
s->scb = i82596_translate_address(s, s->scb, false);
|
|
trace_i82596_ca_init(s->scb, s->mode, s->scb_base);
|
|
|
|
/*
|
|
* Complete initialization sequence:
|
|
* - Clear BUSY flag in ISCP
|
|
* - Set CX and CNA in SCB status
|
|
* - Clear SCB command word
|
|
* - Signal interrupt
|
|
*/
|
|
set_byte(s->iscp + 1, 0);
|
|
s->scb_status |= SCB_STATUS_CX | SCB_STATUS_CNA;
|
|
update_scb_status(s);
|
|
set_uint16(s->scb + 2, 0);
|
|
s->scp = 0;
|
|
qemu_set_irq(s->irq, 1);
|
|
return;
|
|
}
|
|
|
|
if (s->ca_active) {
|
|
s->ca++;
|
|
return;
|
|
}
|
|
s->ca_active = 1;
|
|
s->ca++;
|
|
|
|
while (s->ca > 0) {
|
|
s->ca--;
|
|
examine_scb(s);
|
|
}
|
|
|
|
s->ca_active = 0;
|
|
|
|
if (s->send_irq) {
|
|
s->send_irq = 0;
|
|
qemu_set_irq(s->irq, 1);
|
|
}
|
|
}
|
|
|
|
static void i82596_self_test(I82596State *s, uint32_t val)
|
|
{
|
|
/*
|
|
* The documentation for the self test is a bit unclear,
|
|
* we are currently doing this and it seems to work.
|
|
*/
|
|
set_uint32(val, 0xFFC00000);
|
|
set_uint32(val + 4, 0);
|
|
|
|
s->scb_status &= ~SCB_STATUS_CNA;
|
|
s->scb_status |= SCB_STATUS_CNA;
|
|
|
|
qemu_set_irq(s->irq, 1);
|
|
update_scb_status(s);
|
|
}
|
|
|
|
/*
|
|
* LASI specific interfaces
|
|
*/
|
|
static uint32_t bit_align_16(uint32_t val)
|
|
{
|
|
return val & ~0x0f;
|
|
}
|
|
|
|
uint32_t i82596_ioport_readw(void *opaque, uint32_t addr)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
void i82596_ioport_writew(void *opaque, uint32_t addr, uint32_t val)
|
|
{
|
|
I82596State *s = opaque;
|
|
trace_i82596_ioport_write(addr, val);
|
|
switch (addr) {
|
|
case PORT_RESET:
|
|
i82596_s_reset(s);
|
|
break;
|
|
case PORT_SELFTEST:
|
|
val = bit_align_16(val);
|
|
i82596_self_test(s, val);
|
|
break;
|
|
case PORT_ALTSCP:
|
|
s->scp = bit_align_16(val);
|
|
break;
|
|
case PORT_ALTDUMP:
|
|
trace_i82596_dump(val);
|
|
i82596_port_dump(s, bit_align_16(val));
|
|
break;
|
|
case PORT_CA:
|
|
signal_ca(s);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void i82596_poll(NetClientState *nc, bool enable)
|
|
{
|
|
I82596State *s = qemu_get_nic_opaque(nc);
|
|
|
|
if (!enable) {
|
|
return;
|
|
}
|
|
|
|
if (s->send_irq) {
|
|
qemu_set_irq(s->irq, 1);
|
|
}
|
|
|
|
if (s->rx_status == RX_NO_RESOURCES) {
|
|
if (s->cmd_p != I596_NULL) {
|
|
i82596_update_rx_state(s, RX_READY);
|
|
update_scb_status(s);
|
|
}
|
|
}
|
|
|
|
if (s->cu_status == CU_ACTIVE && s->cmd_p != I596_NULL) {
|
|
examine_scb(s);
|
|
}
|
|
qemu_set_irq(s->irq, 0);
|
|
}
|
|
|
|
const VMStateDescription vmstate_i82596 = {
|
|
.name = "i82596",
|
|
.version_id = 1,
|
|
.minimum_version_id = 1,
|
|
.fields = (VMStateField[]) {
|
|
VMSTATE_UINT8(mode, I82596State),
|
|
VMSTATE_UINT16(t_on, I82596State),
|
|
VMSTATE_UINT16(t_off, I82596State),
|
|
VMSTATE_BOOL(throttle_state, I82596State),
|
|
VMSTATE_UINT32(iscp, I82596State),
|
|
VMSTATE_UINT8(sysbus, I82596State),
|
|
VMSTATE_UINT32(scb, I82596State),
|
|
VMSTATE_UINT32(scb_base, I82596State),
|
|
VMSTATE_UINT16(scb_status, I82596State),
|
|
VMSTATE_UINT8(cu_status, I82596State),
|
|
VMSTATE_UINT8(rx_status, I82596State),
|
|
VMSTATE_UINT16(lnkst, I82596State),
|
|
VMSTATE_UINT32(cmd_p, I82596State),
|
|
VMSTATE_INT32(ca, I82596State),
|
|
VMSTATE_INT32(ca_active, I82596State),
|
|
VMSTATE_INT32(send_irq, I82596State),
|
|
VMSTATE_BUFFER(mult, I82596State),
|
|
VMSTATE_BUFFER(config, I82596State),
|
|
VMSTATE_BUFFER(tx_buffer, I82596State),
|
|
VMSTATE_UINT32(tx_retry_addr, I82596State),
|
|
VMSTATE_INT32(tx_retry_count, I82596State),
|
|
VMSTATE_UINT32(tx_good_frames, I82596State),
|
|
VMSTATE_UINT32(tx_collisions, I82596State),
|
|
VMSTATE_UINT32(tx_aborted_errors, I82596State),
|
|
VMSTATE_UINT32(last_tx_len, I82596State),
|
|
VMSTATE_UINT32(collision_events, I82596State),
|
|
VMSTATE_UINT32(total_collisions, I82596State),
|
|
VMSTATE_UINT32(crc_err, I82596State),
|
|
VMSTATE_UINT32(align_err, I82596State),
|
|
VMSTATE_UINT32(resource_err, I82596State),
|
|
VMSTATE_UINT32(over_err, I82596State),
|
|
VMSTATE_UINT32(rcvdt_err, I82596State),
|
|
VMSTATE_UINT32(short_fr_error, I82596State),
|
|
VMSTATE_UINT32(total_frames, I82596State),
|
|
VMSTATE_UINT32(total_good_frames, I82596State),
|
|
VMSTATE_BUFFER(rx_buffer, I82596State),
|
|
VMSTATE_UINT16(tx_frame_len, I82596State),
|
|
VMSTATE_UINT16(rx_frame_len, I82596State),
|
|
VMSTATE_UINT64(current_tx_desc, I82596State),
|
|
VMSTATE_UINT64(current_rx_desc, I82596State),
|
|
VMSTATE_UINT32(last_good_rfa, I82596State),
|
|
VMSTATE_INT32(queue_head, I82596State),
|
|
VMSTATE_INT32(queue_tail, I82596State),
|
|
VMSTATE_INT32(queue_count, I82596State),
|
|
VMSTATE_BOOL(rnr_signaled, I82596State),
|
|
VMSTATE_BOOL(flushing_queue, I82596State),
|
|
VMSTATE_END_OF_LIST()
|
|
}
|
|
};
|
|
|
|
static int i82596_flush_packet_queue(I82596State *s)
|
|
{
|
|
if (s->flushing_queue) {
|
|
return 0;
|
|
}
|
|
|
|
s->flushing_queue = true;
|
|
int processed = 0;
|
|
|
|
while (s->queue_count > 0) {
|
|
int tail = s->queue_tail;
|
|
size_t len = s->packet_queue_len[tail];
|
|
|
|
ssize_t ret = i82596_receive_packet(s, s->packet_queue[tail], len,
|
|
true);
|
|
|
|
if (ret < 0) {
|
|
break;
|
|
}
|
|
|
|
s->queue_tail = (s->queue_tail + 1) % PACKET_QUEUE_SIZE;
|
|
s->queue_count--;
|
|
processed++;
|
|
}
|
|
|
|
s->flushing_queue = false;
|
|
trace_i82596_flush_queue(processed);
|
|
|
|
return processed;
|
|
}
|
|
|
|
static void i82596_flush_queue_timer(void *opaque)
|
|
{
|
|
I82596State *s = opaque;
|
|
|
|
if (s->queue_count == 0) {
|
|
return;
|
|
}
|
|
|
|
int processed = i82596_flush_packet_queue(s);
|
|
|
|
if (processed > 0 && s->rx_status == RX_READY) {
|
|
qemu_flush_queued_packets(qemu_get_queue(s->nic));
|
|
}
|
|
|
|
if (s->queue_count > 0 && s->rx_status != RX_READY) {
|
|
timer_mod(s->flush_queue_timer,
|
|
qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 50000);
|
|
}
|
|
}
|
|
|
|
void i82596_common_init(DeviceState *dev, I82596State *s, NetClientInfo *info)
|
|
{
|
|
if (s->conf.macaddr.a[0] == 0) {
|
|
qemu_macaddr_default_if_unset(&s->conf.macaddr);
|
|
}
|
|
s->nic = qemu_new_nic(info, &s->conf, object_get_typename(OBJECT(dev)),
|
|
dev->id, &dev->mem_reentrancy_guard, s);
|
|
qemu_format_nic_info_str(qemu_get_queue(s->nic), s->conf.macaddr.a);
|
|
|
|
if (USE_TIMER) {
|
|
if (!s->flush_queue_timer) {
|
|
s->flush_queue_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
|
|
i82596_flush_queue_timer, s);
|
|
}
|
|
if (!s->throttle_timer) {
|
|
s->throttle_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
|
|
i82596_bus_throttle_timer, s);
|
|
}
|
|
}
|
|
|
|
s->lnkst = 0x8000; /* initial link state: up */
|
|
}
|