aboutsummaryrefslogtreecommitdiff
path: root/hw/net/smc91c111.c
diff options
context:
space:
mode:
Diffstat (limited to 'hw/net/smc91c111.c')
-rw-r--r--hw/net/smc91c111.c167
1 files changed, 143 insertions, 24 deletions
diff --git a/hw/net/smc91c111.c b/hw/net/smc91c111.c
index 702d0e8..5cd78e3 100644
--- a/hw/net/smc91c111.c
+++ b/hw/net/smc91c111.c
@@ -13,16 +13,23 @@
#include "net/net.h"
#include "hw/irq.h"
#include "hw/net/smc91c111.h"
+#include "hw/registerfields.h"
#include "hw/qdev-properties.h"
#include "qapi/error.h"
#include "qemu/log.h"
#include "qemu/module.h"
-/* For crc32 */
-#include <zlib.h>
+#include <zlib.h> /* for crc32 */
#include "qom/object.h"
/* Number of 2k memory pages available. */
#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"
OBJECT_DECLARE_SIMPLE_TYPE(smc91c111_state, SMC91C111)
@@ -52,7 +59,7 @@ struct smc91c111_state {
int tx_fifo_done_len;
int tx_fifo_done[NUM_PACKETS];
/* Packet buffer memory. */
- uint8_t data[NUM_PACKETS][2048];
+ uint8_t data[NUM_PACKETS][MAX_PACKET_SIZE];
uint8_t int_level;
uint8_t int_mask;
MemoryRegion mmio;
@@ -80,7 +87,8 @@ static const VMStateDescription vmstate_smc91c111 = {
VMSTATE_INT32_ARRAY(rx_fifo, smc91c111_state, NUM_PACKETS),
VMSTATE_INT32(tx_fifo_done_len, smc91c111_state),
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_mask, smc91c111_state),
VMSTATE_END_OF_LIST()
@@ -119,6 +127,18 @@ static const VMStateDescription vmstate_smc91c111 = {
#define RS_TOOSHORT 0x0400
#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. */
static void smc91c111_update(smc91c111_state *s)
{
@@ -183,6 +203,15 @@ static void smc91c111_pop_rx_fifo(smc91c111_state *s)
{
int i;
+ if (s->rx_fifo_len == 0) {
+ /*
+ * The datasheet doesn't document what the behaviour is if the
+ * guest tries to pop an empty RX FIFO, and there's no obvious
+ * error status register to report it. Just ignore the attempt.
+ */
+ return;
+ }
+
s->rx_fifo_len--;
if (s->rx_fifo_len) {
for (i = 0; i < s->rx_fifo_len; i++)
@@ -210,12 +239,33 @@ static void smc91c111_pop_tx_fifo_done(smc91c111_state *s)
/* Release the memory allocated to a 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);
if (s->tx_alloc == 0x80)
smc91c111_tx_alloc(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. */
static void smc91c111_do_tx(smc91c111_state *s)
{
@@ -231,12 +281,25 @@ static void smc91c111_do_tx(smc91c111_state *s)
return;
for (i = 0; i < s->tx_fifo_len; i++) {
packetnum = s->tx_fifo[i];
+ /* queue_tx checked the packet number was valid */
+ assert(packetnum_valid(packetnum));
p = &s->data[packetnum][0];
/* Set status word. */
*(p++) = 0x01;
*(p++) = 0x40;
len = *(p++);
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;
control = p[len + 1];
if (control & 0x20)
@@ -265,11 +328,7 @@ static void smc91c111_do_tx(smc91c111_state *s)
}
}
#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);
}
s->tx_fifo_len = 0;
@@ -279,6 +338,17 @@ static void smc91c111_do_tx(smc91c111_state *s)
/* Add a packet to the TX FIFO. */
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)
return;
s->tx_fifo[s->tx_fifo_len++] = packet;
@@ -310,6 +380,49 @@ static void smc91c111_reset(DeviceState *dev)
#define SET_LOW(name, val) s->name = (s->name & 0xff00) | val
#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,
uint32_t value)
{
@@ -449,12 +562,14 @@ static void smc91c111_writeb(void *opaque, hwaddr offset,
n = s->rx_fifo[0];
else
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;
}
return;
@@ -597,12 +712,14 @@ static uint32_t smc91c111_readb(void *opaque, hwaddr offset)
n = s->rx_fifo[0];
else
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];
}
case 12: /* Interrupt status. */
@@ -698,13 +815,16 @@ static ssize_t smc91c111_receive(NetClientState *nc, const uint8_t *buf, size_t
if (crc)
packetsize += 4;
/* TODO: Flag overrun and receive errors. */
- if (packetsize > 2048)
+ if (packetsize > MAX_PACKET_SIZE) {
return -1;
+ }
packetnum = smc91c111_allocate_packet(s);
if (packetnum == 0x80)
return -1;
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];
/* ??? Multicast packets? */
status = 0;
@@ -789,17 +909,16 @@ static void smc91c111_realize(DeviceState *dev, Error **errp)
/* ??? Save/restore. */
}
-static Property smc91c111_properties[] = {
+static const Property smc91c111_properties[] = {
DEFINE_NIC_PROPERTIES(smc91c111_state, conf),
- DEFINE_PROP_END_OF_LIST(),
};
-static void smc91c111_class_init(ObjectClass *klass, void *data)
+static void smc91c111_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
dc->realize = smc91c111_realize;
- dc->reset = smc91c111_reset;
+ device_class_set_legacy_reset(dc, smc91c111_reset);
dc->vmsd = &vmstate_smc91c111;
device_class_set_props(dc, smc91c111_properties);
}