|
@@ -13,6 +13,7 @@
|
|
#include "net/net.h"
|
|
#include "net/net.h"
|
|
#include "hw/irq.h"
|
|
#include "hw/irq.h"
|
|
#include "hw/net/smc91c111.h"
|
|
#include "hw/net/smc91c111.h"
|
|
|
|
+#include "hw/registerfields.h"
|
|
#include "hw/qdev-properties.h"
|
|
#include "hw/qdev-properties.h"
|
|
#include "qapi/error.h"
|
|
#include "qapi/error.h"
|
|
#include "qemu/log.h"
|
|
#include "qemu/log.h"
|
|
@@ -22,6 +23,13 @@
|
|
|
|
|
|
/* Number of 2k memory pages available. */
|
|
/* Number of 2k memory pages available. */
|
|
#define NUM_PACKETS 4
|
|
#define NUM_PACKETS 4
|
|
|
|
+/*
|
|
|
|
+ * Maximum size of a data frame, including the leading status word
|
|
|
|
+ * and byte count fields and the trailing CRC, last data byte
|
|
|
|
+ * and control byte (per figure 8-1 in the Microchip Technology
|
|
|
|
+ * LAN91C111 datasheet).
|
|
|
|
+ */
|
|
|
|
+#define MAX_PACKET_SIZE 2048
|
|
|
|
|
|
#define TYPE_SMC91C111 "smc91c111"
|
|
#define TYPE_SMC91C111 "smc91c111"
|
|
OBJECT_DECLARE_SIMPLE_TYPE(smc91c111_state, SMC91C111)
|
|
OBJECT_DECLARE_SIMPLE_TYPE(smc91c111_state, SMC91C111)
|
|
@@ -51,7 +59,7 @@ struct smc91c111_state {
|
|
int tx_fifo_done_len;
|
|
int tx_fifo_done_len;
|
|
int tx_fifo_done[NUM_PACKETS];
|
|
int tx_fifo_done[NUM_PACKETS];
|
|
/* Packet buffer memory. */
|
|
/* Packet buffer memory. */
|
|
- uint8_t data[NUM_PACKETS][2048];
|
|
|
|
|
|
+ uint8_t data[NUM_PACKETS][MAX_PACKET_SIZE];
|
|
uint8_t int_level;
|
|
uint8_t int_level;
|
|
uint8_t int_mask;
|
|
uint8_t int_mask;
|
|
MemoryRegion mmio;
|
|
MemoryRegion mmio;
|
|
@@ -79,7 +87,8 @@ static const VMStateDescription vmstate_smc91c111 = {
|
|
VMSTATE_INT32_ARRAY(rx_fifo, smc91c111_state, NUM_PACKETS),
|
|
VMSTATE_INT32_ARRAY(rx_fifo, smc91c111_state, NUM_PACKETS),
|
|
VMSTATE_INT32(tx_fifo_done_len, smc91c111_state),
|
|
VMSTATE_INT32(tx_fifo_done_len, smc91c111_state),
|
|
VMSTATE_INT32_ARRAY(tx_fifo_done, smc91c111_state, NUM_PACKETS),
|
|
VMSTATE_INT32_ARRAY(tx_fifo_done, smc91c111_state, NUM_PACKETS),
|
|
- VMSTATE_BUFFER_UNSAFE(data, smc91c111_state, 0, NUM_PACKETS * 2048),
|
|
|
|
|
|
+ VMSTATE_BUFFER_UNSAFE(data, smc91c111_state, 0,
|
|
|
|
+ NUM_PACKETS * MAX_PACKET_SIZE),
|
|
VMSTATE_UINT8(int_level, smc91c111_state),
|
|
VMSTATE_UINT8(int_level, smc91c111_state),
|
|
VMSTATE_UINT8(int_mask, smc91c111_state),
|
|
VMSTATE_UINT8(int_mask, smc91c111_state),
|
|
VMSTATE_END_OF_LIST()
|
|
VMSTATE_END_OF_LIST()
|
|
@@ -118,6 +127,18 @@ static const VMStateDescription vmstate_smc91c111 = {
|
|
#define RS_TOOSHORT 0x0400
|
|
#define RS_TOOSHORT 0x0400
|
|
#define RS_MULTICAST 0x0001
|
|
#define RS_MULTICAST 0x0001
|
|
|
|
|
|
|
|
+FIELD(PTR, PTR, 0, 11)
|
|
|
|
+FIELD(PTR, NOT_EMPTY, 11, 1)
|
|
|
|
+FIELD(PTR, RESERVED, 12, 1)
|
|
|
|
+FIELD(PTR, READ, 13, 1)
|
|
|
|
+FIELD(PTR, AUTOINCR, 14, 1)
|
|
|
|
+FIELD(PTR, RCV, 15, 1)
|
|
|
|
+
|
|
|
|
+static inline bool packetnum_valid(int packet_num)
|
|
|
|
+{
|
|
|
|
+ return packet_num >= 0 && packet_num < NUM_PACKETS;
|
|
|
|
+}
|
|
|
|
+
|
|
/* Update interrupt status. */
|
|
/* Update interrupt status. */
|
|
static void smc91c111_update(smc91c111_state *s)
|
|
static void smc91c111_update(smc91c111_state *s)
|
|
{
|
|
{
|
|
@@ -218,12 +239,33 @@ static void smc91c111_pop_tx_fifo_done(smc91c111_state *s)
|
|
/* Release the memory allocated to a packet. */
|
|
/* Release the memory allocated to a packet. */
|
|
static void smc91c111_release_packet(smc91c111_state *s, int packet)
|
|
static void smc91c111_release_packet(smc91c111_state *s, int packet)
|
|
{
|
|
{
|
|
|
|
+ if (!packetnum_valid(packet)) {
|
|
|
|
+ /*
|
|
|
|
+ * Data sheet doesn't document behaviour in this guest error
|
|
|
|
+ * case, and there is no error status register to report it.
|
|
|
|
+ * Log and ignore the attempt.
|
|
|
|
+ */
|
|
|
|
+ qemu_log_mask(LOG_GUEST_ERROR,
|
|
|
|
+ "smc91c111: attempt to release invalid packet %d\n",
|
|
|
|
+ packet);
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
s->allocated &= ~(1 << packet);
|
|
s->allocated &= ~(1 << packet);
|
|
if (s->tx_alloc == 0x80)
|
|
if (s->tx_alloc == 0x80)
|
|
smc91c111_tx_alloc(s);
|
|
smc91c111_tx_alloc(s);
|
|
smc91c111_flush_queued_packets(s);
|
|
smc91c111_flush_queued_packets(s);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+static void smc91c111_complete_tx_packet(smc91c111_state *s, int packetnum)
|
|
|
|
+{
|
|
|
|
+ if (s->ctr & CTR_AUTO_RELEASE) {
|
|
|
|
+ /* Race? */
|
|
|
|
+ smc91c111_release_packet(s, packetnum);
|
|
|
|
+ } else if (s->tx_fifo_done_len < NUM_PACKETS) {
|
|
|
|
+ s->tx_fifo_done[s->tx_fifo_done_len++] = packetnum;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
/* Flush the TX FIFO. */
|
|
/* Flush the TX FIFO. */
|
|
static void smc91c111_do_tx(smc91c111_state *s)
|
|
static void smc91c111_do_tx(smc91c111_state *s)
|
|
{
|
|
{
|
|
@@ -239,12 +281,25 @@ static void smc91c111_do_tx(smc91c111_state *s)
|
|
return;
|
|
return;
|
|
for (i = 0; i < s->tx_fifo_len; i++) {
|
|
for (i = 0; i < s->tx_fifo_len; i++) {
|
|
packetnum = s->tx_fifo[i];
|
|
packetnum = s->tx_fifo[i];
|
|
|
|
+ /* queue_tx checked the packet number was valid */
|
|
|
|
+ assert(packetnum_valid(packetnum));
|
|
p = &s->data[packetnum][0];
|
|
p = &s->data[packetnum][0];
|
|
/* Set status word. */
|
|
/* Set status word. */
|
|
*(p++) = 0x01;
|
|
*(p++) = 0x01;
|
|
*(p++) = 0x40;
|
|
*(p++) = 0x40;
|
|
len = *(p++);
|
|
len = *(p++);
|
|
len |= ((int)*(p++)) << 8;
|
|
len |= ((int)*(p++)) << 8;
|
|
|
|
+ if (len > MAX_PACKET_SIZE) {
|
|
|
|
+ /*
|
|
|
|
+ * Datasheet doesn't say what to do here, and there is no
|
|
|
|
+ * relevant tx error condition listed. Log, and drop the packet.
|
|
|
|
+ */
|
|
|
|
+ qemu_log_mask(LOG_GUEST_ERROR,
|
|
|
|
+ "smc91c111: tx packet with bad length %d, dropping\n",
|
|
|
|
+ len);
|
|
|
|
+ smc91c111_complete_tx_packet(s, packetnum);
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
len -= 6;
|
|
len -= 6;
|
|
control = p[len + 1];
|
|
control = p[len + 1];
|
|
if (control & 0x20)
|
|
if (control & 0x20)
|
|
@@ -273,11 +328,7 @@ static void smc91c111_do_tx(smc91c111_state *s)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
#endif
|
|
- if (s->ctr & CTR_AUTO_RELEASE)
|
|
|
|
- /* Race? */
|
|
|
|
- smc91c111_release_packet(s, packetnum);
|
|
|
|
- else if (s->tx_fifo_done_len < NUM_PACKETS)
|
|
|
|
- s->tx_fifo_done[s->tx_fifo_done_len++] = packetnum;
|
|
|
|
|
|
+ smc91c111_complete_tx_packet(s, packetnum);
|
|
qemu_send_packet(qemu_get_queue(s->nic), p, len);
|
|
qemu_send_packet(qemu_get_queue(s->nic), p, len);
|
|
}
|
|
}
|
|
s->tx_fifo_len = 0;
|
|
s->tx_fifo_len = 0;
|
|
@@ -287,6 +338,17 @@ static void smc91c111_do_tx(smc91c111_state *s)
|
|
/* Add a packet to the TX FIFO. */
|
|
/* Add a packet to the TX FIFO. */
|
|
static void smc91c111_queue_tx(smc91c111_state *s, int packet)
|
|
static void smc91c111_queue_tx(smc91c111_state *s, int packet)
|
|
{
|
|
{
|
|
|
|
+ if (!packetnum_valid(packet)) {
|
|
|
|
+ /*
|
|
|
|
+ * Datasheet doesn't document behaviour in this error case, and
|
|
|
|
+ * there's no error status register we could report it in.
|
|
|
|
+ * Log and ignore.
|
|
|
|
+ */
|
|
|
|
+ qemu_log_mask(LOG_GUEST_ERROR,
|
|
|
|
+ "smc91c111: attempt to queue invalid packet %d\n",
|
|
|
|
+ packet);
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
if (s->tx_fifo_len == NUM_PACKETS)
|
|
if (s->tx_fifo_len == NUM_PACKETS)
|
|
return;
|
|
return;
|
|
s->tx_fifo[s->tx_fifo_len++] = packet;
|
|
s->tx_fifo[s->tx_fifo_len++] = packet;
|
|
@@ -318,6 +380,49 @@ static void smc91c111_reset(DeviceState *dev)
|
|
#define SET_LOW(name, val) s->name = (s->name & 0xff00) | val
|
|
#define SET_LOW(name, val) s->name = (s->name & 0xff00) | val
|
|
#define SET_HIGH(name, val) s->name = (s->name & 0xff) | (val << 8)
|
|
#define SET_HIGH(name, val) s->name = (s->name & 0xff) | (val << 8)
|
|
|
|
|
|
|
|
+/*
|
|
|
|
+ * The pointer register's pointer is an 11 bit value (so it exactly
|
|
|
|
+ * indexes a 2048-byte data frame). Add the specified offset to it,
|
|
|
|
+ * wrapping around at the 2048 byte mark, and return the resulting
|
|
|
|
+ * wrapped value. There are flag bits in the top part of the register,
|
|
|
|
+ * but we can ignore them here as the mask will mask them out.
|
|
|
|
+ */
|
|
|
|
+static int ptr_reg_add(smc91c111_state *s, int offset)
|
|
|
|
+{
|
|
|
|
+ return (s->ptr + offset) & R_PTR_PTR_MASK;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * For an access to the Data Register at @offset, return the
|
|
|
|
+ * required offset into the packet's data frame. This will
|
|
|
|
+ * perform the pointer register autoincrement if required, and
|
|
|
|
+ * guarantees to return an in-bounds offset.
|
|
|
|
+ */
|
|
|
|
+static int data_reg_ptr(smc91c111_state *s, int offset)
|
|
|
|
+{
|
|
|
|
+ int p;
|
|
|
|
+
|
|
|
|
+ if (s->ptr & R_PTR_AUTOINCR_MASK) {
|
|
|
|
+ /*
|
|
|
|
+ * Autoincrement: use the current pointer value, and
|
|
|
|
+ * increment the pointer register's pointer field.
|
|
|
|
+ */
|
|
|
|
+ p = FIELD_EX32(s->ptr, PTR, PTR);
|
|
|
|
+ s->ptr = FIELD_DP32(s->ptr, PTR, PTR, ptr_reg_add(s, 1));
|
|
|
|
+ } else {
|
|
|
|
+ /*
|
|
|
|
+ * No autoincrement: register offset determines which
|
|
|
|
+ * byte we're addressing. Setting the pointer to the top
|
|
|
|
+ * of the data buffer and then using the pointer wrapping
|
|
|
|
+ * to read the bottom byte of the buffer is not something
|
|
|
|
+ * sensible guest software will do, but the datasheet
|
|
|
|
+ * doesn't say what the behaviour is, so we don't forbid it.
|
|
|
|
+ */
|
|
|
|
+ p = ptr_reg_add(s, offset & 3);
|
|
|
|
+ }
|
|
|
|
+ return p;
|
|
|
|
+}
|
|
|
|
+
|
|
static void smc91c111_writeb(void *opaque, hwaddr offset,
|
|
static void smc91c111_writeb(void *opaque, hwaddr offset,
|
|
uint32_t value)
|
|
uint32_t value)
|
|
{
|
|
{
|
|
@@ -457,12 +562,14 @@ static void smc91c111_writeb(void *opaque, hwaddr offset,
|
|
n = s->rx_fifo[0];
|
|
n = s->rx_fifo[0];
|
|
else
|
|
else
|
|
n = s->packet_num;
|
|
n = s->packet_num;
|
|
- p = s->ptr & 0x07ff;
|
|
|
|
- if (s->ptr & 0x4000) {
|
|
|
|
- s->ptr = (s->ptr & 0xf800) | ((s->ptr + 1) & 0x7ff);
|
|
|
|
- } else {
|
|
|
|
- p += (offset & 3);
|
|
|
|
|
|
+ if (!packetnum_valid(n)) {
|
|
|
|
+ /* Datasheet doesn't document what to do here */
|
|
|
|
+ qemu_log_mask(LOG_GUEST_ERROR,
|
|
|
|
+ "smc91c111: attempt to write data to invalid packet %d\n",
|
|
|
|
+ n);
|
|
|
|
+ return;
|
|
}
|
|
}
|
|
|
|
+ p = data_reg_ptr(s, offset);
|
|
s->data[n][p] = value;
|
|
s->data[n][p] = value;
|
|
}
|
|
}
|
|
return;
|
|
return;
|
|
@@ -605,12 +712,14 @@ static uint32_t smc91c111_readb(void *opaque, hwaddr offset)
|
|
n = s->rx_fifo[0];
|
|
n = s->rx_fifo[0];
|
|
else
|
|
else
|
|
n = s->packet_num;
|
|
n = s->packet_num;
|
|
- p = s->ptr & 0x07ff;
|
|
|
|
- if (s->ptr & 0x4000) {
|
|
|
|
- s->ptr = (s->ptr & 0xf800) | ((s->ptr + 1) & 0x07ff);
|
|
|
|
- } else {
|
|
|
|
- p += (offset & 3);
|
|
|
|
|
|
+ if (!packetnum_valid(n)) {
|
|
|
|
+ /* Datasheet doesn't document what to do here */
|
|
|
|
+ qemu_log_mask(LOG_GUEST_ERROR,
|
|
|
|
+ "smc91c111: attempt to read data from invalid packet %d\n",
|
|
|
|
+ n);
|
|
|
|
+ return 0;
|
|
}
|
|
}
|
|
|
|
+ p = data_reg_ptr(s, offset);
|
|
return s->data[n][p];
|
|
return s->data[n][p];
|
|
}
|
|
}
|
|
case 12: /* Interrupt status. */
|
|
case 12: /* Interrupt status. */
|
|
@@ -706,13 +815,16 @@ static ssize_t smc91c111_receive(NetClientState *nc, const uint8_t *buf, size_t
|
|
if (crc)
|
|
if (crc)
|
|
packetsize += 4;
|
|
packetsize += 4;
|
|
/* TODO: Flag overrun and receive errors. */
|
|
/* TODO: Flag overrun and receive errors. */
|
|
- if (packetsize > 2048)
|
|
|
|
|
|
+ if (packetsize > MAX_PACKET_SIZE) {
|
|
return -1;
|
|
return -1;
|
|
|
|
+ }
|
|
packetnum = smc91c111_allocate_packet(s);
|
|
packetnum = smc91c111_allocate_packet(s);
|
|
if (packetnum == 0x80)
|
|
if (packetnum == 0x80)
|
|
return -1;
|
|
return -1;
|
|
s->rx_fifo[s->rx_fifo_len++] = packetnum;
|
|
s->rx_fifo[s->rx_fifo_len++] = packetnum;
|
|
|
|
|
|
|
|
+ /* allocate_packet() will not hand us back an invalid packet number */
|
|
|
|
+ assert(packetnum_valid(packetnum));
|
|
p = &s->data[packetnum][0];
|
|
p = &s->data[packetnum][0];
|
|
/* ??? Multicast packets? */
|
|
/* ??? Multicast packets? */
|
|
status = 0;
|
|
status = 0;
|