aboutsummaryrefslogtreecommitdiff
path: root/hw/net/dp8393x.c
diff options
context:
space:
mode:
Diffstat (limited to 'hw/net/dp8393x.c')
-rw-r--r--hw/net/dp8393x.c200
1 files changed, 133 insertions, 67 deletions
diff --git a/hw/net/dp8393x.c b/hw/net/dp8393x.c
index 7045193..8a3504d 100644
--- a/hw/net/dp8393x.c
+++ b/hw/net/dp8393x.c
@@ -137,6 +137,7 @@ do { printf("sonic ERROR: %s: " fmt, __func__ , ## __VA_ARGS__); } while (0)
#define SONIC_TCR_CRCI 0x2000
#define SONIC_TCR_PINT 0x8000
+#define SONIC_ISR_RBAE 0x0010
#define SONIC_ISR_RBE 0x0020
#define SONIC_ISR_RDE 0x0040
#define SONIC_ISR_TC 0x0080
@@ -145,6 +146,9 @@ do { printf("sonic ERROR: %s: " fmt, __func__ , ## __VA_ARGS__); } while (0)
#define SONIC_ISR_PINT 0x0800
#define SONIC_ISR_LCD 0x1000
+#define SONIC_DESC_EOL 0x0001
+#define SONIC_DESC_ADDR 0xFFFE
+
#define TYPE_DP8393X "dp8393x"
#define DP8393X(obj) OBJECT_CHECK(dp8393xState, (obj), TYPE_DP8393X)
@@ -154,6 +158,7 @@ typedef struct dp8393xState {
/* Hardware */
uint8_t it_shift;
bool big_endian;
+ bool last_rba_is_full;
qemu_irq irq;
#ifdef DEBUG_SONIC
int irq_level;
@@ -197,7 +202,8 @@ static uint32_t dp8393x_crba(dp8393xState *s)
static uint32_t dp8393x_crda(dp8393xState *s)
{
- return (s->regs[SONIC_URDA] << 16) | s->regs[SONIC_CRDA];
+ return (s->regs[SONIC_URDA] << 16) |
+ (s->regs[SONIC_CRDA] & SONIC_DESC_ADDR);
}
static uint32_t dp8393x_rbwc(dp8393xState *s)
@@ -217,7 +223,8 @@ static uint32_t dp8393x_tsa(dp8393xState *s)
static uint32_t dp8393x_ttda(dp8393xState *s)
{
- return (s->regs[SONIC_UTDA] << 16) | s->regs[SONIC_TTDA];
+ return (s->regs[SONIC_UTDA] << 16) |
+ (s->regs[SONIC_TTDA] & SONIC_DESC_ADDR);
}
static uint32_t dp8393x_wt(dp8393xState *s)
@@ -241,9 +248,19 @@ static void dp8393x_put(dp8393xState *s, int width, int offset,
uint16_t val)
{
if (s->big_endian) {
- s->data[offset * width + width - 1] = cpu_to_be16(val);
+ if (width == 2) {
+ s->data[offset * 2] = 0;
+ s->data[offset * 2 + 1] = cpu_to_be16(val);
+ } else {
+ s->data[offset] = cpu_to_be16(val);
+ }
} else {
- s->data[offset * width] = cpu_to_le16(val);
+ if (width == 2) {
+ s->data[offset * 2] = cpu_to_le16(val);
+ s->data[offset * 2 + 1] = 0;
+ } else {
+ s->data[offset] = cpu_to_le16(val);
+ }
}
}
@@ -331,15 +348,15 @@ static void dp8393x_do_read_rra(dp8393xState *s)
s->regs[SONIC_RRP] = s->regs[SONIC_RSA];
}
- /* Check resource exhaustion */
+ /* Warn the host if CRBA now has the last available resource */
if (s->regs[SONIC_RRP] == s->regs[SONIC_RWP])
{
s->regs[SONIC_ISR] |= SONIC_ISR_RBE;
dp8393x_update_irq(s);
}
- /* Done */
- s->regs[SONIC_CR] &= ~SONIC_CR_RRRA;
+ /* Allow packet reception */
+ s->last_rba_is_full = false;
}
static void dp8393x_do_software_reset(dp8393xState *s)
@@ -509,7 +526,7 @@ static void dp8393x_do_transmit_packets(dp8393xState *s)
MEMTXATTRS_UNSPECIFIED, s->data,
size);
s->regs[SONIC_CTDA] = dp8393x_get(s, width, 0) & ~0x1;
- if (dp8393x_get(s, width, 0) & 0x1) {
+ if (dp8393x_get(s, width, 0) & SONIC_DESC_EOL) {
/* EOL detected */
break;
}
@@ -550,8 +567,10 @@ static void dp8393x_do_command(dp8393xState *s, uint16_t command)
dp8393x_do_start_timer(s);
if (command & SONIC_CR_RST)
dp8393x_do_software_reset(s);
- if (command & SONIC_CR_RRRA)
+ if (command & SONIC_CR_RRRA) {
dp8393x_do_read_rra(s);
+ s->regs[SONIC_CR] &= ~SONIC_CR_RRRA;
+ }
if (command & SONIC_CR_LCAM)
dp8393x_do_load_cam(s);
}
@@ -585,7 +604,7 @@ static uint64_t dp8393x_read(void *opaque, hwaddr addr, unsigned int size)
DPRINTF("read 0x%04x from reg %s\n", val, reg_names[reg]);
- return val;
+ return s->big_endian ? val << 16 : val;
}
static void dp8393x_write(void *opaque, hwaddr addr, uint64_t data,
@@ -593,13 +612,14 @@ static void dp8393x_write(void *opaque, hwaddr addr, uint64_t data,
{
dp8393xState *s = opaque;
int reg = addr >> s->it_shift;
+ uint32_t val = s->big_endian ? data >> 16 : data;
- DPRINTF("write 0x%04x to reg %s\n", (uint16_t)data, reg_names[reg]);
+ DPRINTF("write 0x%04x to reg %s\n", (uint16_t)val, reg_names[reg]);
switch (reg) {
/* Command register */
case SONIC_CR:
- dp8393x_do_command(s, data);
+ dp8393x_do_command(s, val);
break;
/* Prevent write to read-only registers */
case SONIC_CAP2:
@@ -612,59 +632,60 @@ static void dp8393x_write(void *opaque, hwaddr addr, uint64_t data,
/* Accept write to some registers only when in reset mode */
case SONIC_DCR:
if (s->regs[SONIC_CR] & SONIC_CR_RST) {
- s->regs[reg] = data & 0xbfff;
+ s->regs[reg] = val & 0xbfff;
} else {
DPRINTF("writing to DCR invalid\n");
}
break;
case SONIC_DCR2:
if (s->regs[SONIC_CR] & SONIC_CR_RST) {
- s->regs[reg] = data & 0xf017;
+ s->regs[reg] = val & 0xf017;
} else {
DPRINTF("writing to DCR2 invalid\n");
}
break;
/* 12 lower bytes are Read Only */
case SONIC_TCR:
- s->regs[reg] = data & 0xf000;
+ s->regs[reg] = val & 0xf000;
break;
/* 9 lower bytes are Read Only */
case SONIC_RCR:
- s->regs[reg] = data & 0xffe0;
+ s->regs[reg] = val & 0xffe0;
break;
/* Ignore most significant bit */
case SONIC_IMR:
- s->regs[reg] = data & 0x7fff;
+ s->regs[reg] = val & 0x7fff;
dp8393x_update_irq(s);
break;
/* Clear bits by writing 1 to them */
case SONIC_ISR:
- data &= s->regs[reg];
- s->regs[reg] &= ~data;
- if (data & SONIC_ISR_RBE) {
+ val &= s->regs[reg];
+ s->regs[reg] &= ~val;
+ if (val & SONIC_ISR_RBE) {
dp8393x_do_read_rra(s);
}
dp8393x_update_irq(s);
- if (dp8393x_can_receive(s->nic->ncs)) {
- qemu_flush_queued_packets(qemu_get_queue(s->nic));
- }
break;
- /* Ignore least significant bit */
+ /* The guest is required to store aligned pointers here */
case SONIC_RSA:
case SONIC_REA:
case SONIC_RRP:
case SONIC_RWP:
- s->regs[reg] = data & 0xfffe;
+ if (s->regs[SONIC_DCR] & SONIC_DCR_DW) {
+ s->regs[reg] = val & 0xfffc;
+ } else {
+ s->regs[reg] = val & 0xfffe;
+ }
break;
/* Invert written value for some registers */
case SONIC_CRCT:
case SONIC_FAET:
case SONIC_MPT:
- s->regs[reg] = data ^ 0xffff;
+ s->regs[reg] = val ^ 0xffff;
break;
/* All other registers have no special contrainst */
default:
- s->regs[reg] = data;
+ s->regs[reg] = val;
}
if (reg == SONIC_WT0 || reg == SONIC_WT1) {
@@ -675,8 +696,8 @@ static void dp8393x_write(void *opaque, hwaddr addr, uint64_t data,
static const MemoryRegionOps dp8393x_ops = {
.read = dp8393x_read,
.write = dp8393x_write,
- .impl.min_access_size = 2,
- .impl.max_access_size = 2,
+ .impl.min_access_size = 4,
+ .impl.max_access_size = 4,
.endianness = DEVICE_NATIVE_ENDIAN,
};
@@ -703,8 +724,6 @@ static int dp8393x_can_receive(NetClientState *nc)
if (!(s->regs[SONIC_CR] & SONIC_CR_RXEN))
return 0;
- if (s->regs[SONIC_ISR] & SONIC_ISR_RBE)
- return 0;
return 1;
}
@@ -743,40 +762,69 @@ static int dp8393x_receive_filter(dp8393xState *s, const uint8_t * buf,
}
static ssize_t dp8393x_receive(NetClientState *nc, const uint8_t * buf,
- size_t size)
+ size_t pkt_size)
{
dp8393xState *s = qemu_get_nic_opaque(nc);
int packet_type;
uint32_t available, address;
- int width, rx_len = size;
+ int width, rx_len, padded_len;
uint32_t checksum;
-
- width = (s->regs[SONIC_DCR] & SONIC_DCR_DW) ? 2 : 1;
+ int size;
s->regs[SONIC_RCR] &= ~(SONIC_RCR_PRX | SONIC_RCR_LBK | SONIC_RCR_FAER |
SONIC_RCR_CRCR | SONIC_RCR_LPKT | SONIC_RCR_BC | SONIC_RCR_MC);
- packet_type = dp8393x_receive_filter(s, buf, size);
+ if (s->last_rba_is_full) {
+ return pkt_size;
+ }
+
+ rx_len = pkt_size + sizeof(checksum);
+ if (s->regs[SONIC_DCR] & SONIC_DCR_DW) {
+ width = 2;
+ padded_len = ((rx_len - 1) | 3) + 1;
+ } else {
+ width = 1;
+ padded_len = ((rx_len - 1) | 1) + 1;
+ }
+
+ if (padded_len > dp8393x_rbwc(s) * 2) {
+ DPRINTF("oversize packet, pkt_size is %d\n", pkt_size);
+ s->regs[SONIC_ISR] |= SONIC_ISR_RBAE;
+ dp8393x_update_irq(s);
+ s->regs[SONIC_RCR] |= SONIC_RCR_LPKT;
+ goto done;
+ }
+
+ packet_type = dp8393x_receive_filter(s, buf, pkt_size);
if (packet_type < 0) {
DPRINTF("packet not for netcard\n");
return -1;
}
- /* XXX: Check byte ordering */
-
/* Check for EOL */
- if (s->regs[SONIC_LLFA] & 0x1) {
+ if (s->regs[SONIC_LLFA] & SONIC_DESC_EOL) {
/* Are we still in resource exhaustion? */
size = sizeof(uint16_t) * 1 * width;
address = dp8393x_crda(s) + sizeof(uint16_t) * 5 * width;
address_space_read(&s->as, address, MEMTXATTRS_UNSPECIFIED,
s->data, size);
- if (dp8393x_get(s, width, 0) & 0x1) {
+ s->regs[SONIC_LLFA] = dp8393x_get(s, width, 0);
+ if (s->regs[SONIC_LLFA] & SONIC_DESC_EOL) {
/* Still EOL ; stop reception */
return -1;
- } else {
- s->regs[SONIC_CRDA] = s->regs[SONIC_LLFA];
}
+ /* Link has been updated by host */
+
+ /* Clear in_use */
+ size = sizeof(uint16_t) * width;
+ address = dp8393x_crda(s) + sizeof(uint16_t) * 6 * width;
+ dp8393x_put(s, width, 0, 0);
+ address_space_rw(&s->as, address, MEMTXATTRS_UNSPECIFIED,
+ (uint8_t *)s->data, size, 1);
+
+ /* Move to next descriptor */
+ s->regs[SONIC_CRDA] = s->regs[SONIC_LLFA];
+ s->regs[SONIC_ISR] |= SONIC_ISR_PKTRX;
}
/* Save current position */
@@ -784,21 +832,32 @@ static ssize_t dp8393x_receive(NetClientState *nc, const uint8_t * buf,
s->regs[SONIC_TRBA0] = s->regs[SONIC_CRBA0];
/* Calculate the ethernet checksum */
- checksum = cpu_to_le32(crc32(0, buf, rx_len));
+ checksum = cpu_to_le32(crc32(0, buf, pkt_size));
/* Put packet into RBA */
DPRINTF("Receive packet at %08x\n", dp8393x_crba(s));
address = dp8393x_crba(s);
address_space_write(&s->as, address, MEMTXATTRS_UNSPECIFIED,
- buf, rx_len);
- address += rx_len;
+ buf, pkt_size);
+ address += pkt_size;
+
+ /* Put frame checksum into RBA */
address_space_write(&s->as, address, MEMTXATTRS_UNSPECIFIED,
- &checksum, 4);
- rx_len += 4;
+ &checksum, sizeof(checksum));
+ address += sizeof(checksum);
+
+ /* Pad short packets to keep pointers aligned */
+ if (rx_len < padded_len) {
+ size = padded_len - rx_len;
+ address_space_rw(&s->as, address, MEMTXATTRS_UNSPECIFIED,
+ (uint8_t *)"\xFF\xFF\xFF", size, 1);
+ address += size;
+ }
+
s->regs[SONIC_CRBA1] = address >> 16;
s->regs[SONIC_CRBA0] = address & 0xffff;
available = dp8393x_rbwc(s);
- available -= rx_len / 2;
+ available -= padded_len >> 1;
s->regs[SONIC_RBWC1] = available >> 16;
s->regs[SONIC_RBWC0] = available & 0xffff;
@@ -825,39 +884,46 @@ static ssize_t dp8393x_receive(NetClientState *nc, const uint8_t * buf,
MEMTXATTRS_UNSPECIFIED,
s->data, size);
- /* Move to next descriptor */
+ /* Check link field */
size = sizeof(uint16_t) * width;
address_space_read(&s->as,
dp8393x_crda(s) + sizeof(uint16_t) * 5 * width,
MEMTXATTRS_UNSPECIFIED, s->data, size);
s->regs[SONIC_LLFA] = dp8393x_get(s, width, 0);
- if (s->regs[SONIC_LLFA] & 0x1) {
+ if (s->regs[SONIC_LLFA] & SONIC_DESC_EOL) {
/* EOL detected */
s->regs[SONIC_ISR] |= SONIC_ISR_RDE;
} else {
- /* Clear in_use, but it is always 16bit wide */
- int offset = dp8393x_crda(s) + sizeof(uint16_t) * 6 * width;
- if (s->big_endian && width == 2) {
- /* we need to adjust the offset of the 16bit field */
- offset += sizeof(uint16_t);
- }
- s->data[0] = 0;
- address_space_write(&s->as, offset, MEMTXATTRS_UNSPECIFIED,
- s->data, sizeof(uint16_t));
+ /* Clear in_use */
+ size = sizeof(uint16_t) * width;
+ address = dp8393x_crda(s) + sizeof(uint16_t) * 6 * width;
+ dp8393x_put(s, width, 0, 0);
+ address_space_write(&s->as, address, MEMTXATTRS_UNSPECIFIED,
+ s->data, size);
+
+ /* Move to next descriptor */
s->regs[SONIC_CRDA] = s->regs[SONIC_LLFA];
s->regs[SONIC_ISR] |= SONIC_ISR_PKTRX;
- s->regs[SONIC_RSC] = (s->regs[SONIC_RSC] & 0xff00) | (((s->regs[SONIC_RSC] & 0x00ff) + 1) & 0x00ff);
+ }
+
+ dp8393x_update_irq(s);
+
+ s->regs[SONIC_RSC] = (s->regs[SONIC_RSC] & 0xff00) |
+ ((s->regs[SONIC_RSC] + 1) & 0x00ff);
- if (s->regs[SONIC_RCR] & SONIC_RCR_LPKT) {
- /* Read next RRA */
+done:
+
+ if (s->regs[SONIC_RCR] & SONIC_RCR_LPKT) {
+ if (s->regs[SONIC_RRP] == s->regs[SONIC_RWP]) {
+ /* Stop packet reception */
+ s->last_rba_is_full = true;
+ } else {
+ /* Read next resource */
dp8393x_do_read_rra(s);
}
}
- /* Done */
- dp8393x_update_irq(s);
-
- return size;
+ return pkt_size;
}
static void dp8393x_reset(DeviceState *dev)
@@ -866,6 +932,7 @@ static void dp8393x_reset(DeviceState *dev)
timer_del(s->watchdog);
memset(s->regs, 0, sizeof(s->regs));
+ s->regs[SONIC_SR] = 0x0004; /* only revision recognized by Linux/mips */
s->regs[SONIC_CR] = SONIC_CR_RST | SONIC_CR_STP | SONIC_CR_RXDIS;
s->regs[SONIC_DCR] &= ~(SONIC_DCR_EXBUS | SONIC_DCR_LBR);
s->regs[SONIC_RCR] &= ~(SONIC_RCR_LB0 | SONIC_RCR_LB1 | SONIC_RCR_BRD | SONIC_RCR_RNT);
@@ -918,7 +985,6 @@ static void dp8393x_realize(DeviceState *dev, Error **errp)
qemu_format_nic_info_str(qemu_get_queue(s->nic), s->conf.macaddr.a);
s->watchdog = timer_new_ns(QEMU_CLOCK_VIRTUAL, dp8393x_watchdog, s);
- s->regs[SONIC_SR] = 0x0004; /* only revision recognized by Linux */
memory_region_init_ram(&s->prom, OBJECT(dev),
"dp8393x-prom", SONIC_PROM_SIZE, &local_err);