aboutsummaryrefslogtreecommitdiff
path: root/hw/mipsnet.c
diff options
context:
space:
mode:
Diffstat (limited to 'hw/mipsnet.c')
-rw-r--r--hw/mipsnet.c272
1 files changed, 272 insertions, 0 deletions
diff --git a/hw/mipsnet.c b/hw/mipsnet.c
new file mode 100644
index 000000000..5a74ad962
--- /dev/null
+++ b/hw/mipsnet.c
@@ -0,0 +1,272 @@
+#include "hw.h"
+#include "mips.h"
+#include "net.h"
+#include "isa.h"
+
+//#define DEBUG_MIPSNET_SEND
+//#define DEBUG_MIPSNET_RECEIVE
+//#define DEBUG_MIPSNET_DATA
+//#define DEBUG_MIPSNET_IRQ
+
+/* MIPSnet register offsets */
+
+#define MIPSNET_DEV_ID 0x00
+# define MIPSNET_DEV_ID_STRING "MIPSNET0"
+#define MIPSNET_BUSY 0x08
+#define MIPSNET_RX_DATA_COUNT 0x0c
+#define MIPSNET_TX_DATA_COUNT 0x10
+#define MIPSNET_INT_CTL 0x14
+# define MIPSNET_INTCTL_TXDONE 0x00000001
+# define MIPSNET_INTCTL_RXDONE 0x00000002
+# define MIPSNET_INTCTL_TESTBIT 0x80000000
+#define MIPSNET_INTERRUPT_INFO 0x18
+#define MIPSNET_RX_DATA_BUFFER 0x1c
+#define MIPSNET_TX_DATA_BUFFER 0x20
+
+#define MAX_ETH_FRAME_SIZE 1514
+
+typedef struct MIPSnetState {
+ uint32_t busy;
+ uint32_t rx_count;
+ uint32_t rx_read;
+ uint32_t tx_count;
+ uint32_t tx_written;
+ uint32_t intctl;
+ uint8_t rx_buffer[MAX_ETH_FRAME_SIZE];
+ uint8_t tx_buffer[MAX_ETH_FRAME_SIZE];
+ qemu_irq irq;
+ VLANClientState *vc;
+ NICInfo *nd;
+} MIPSnetState;
+
+static void mipsnet_reset(MIPSnetState *s)
+{
+ s->busy = 1;
+ s->rx_count = 0;
+ s->rx_read = 0;
+ s->tx_count = 0;
+ s->tx_written = 0;
+ s->intctl = 0;
+ memset(s->rx_buffer, 0, MAX_ETH_FRAME_SIZE);
+ memset(s->tx_buffer, 0, MAX_ETH_FRAME_SIZE);
+}
+
+static void mipsnet_update_irq(MIPSnetState *s)
+{
+ int isr = !!s->intctl;
+#ifdef DEBUG_MIPSNET_IRQ
+ printf("mipsnet: Set IRQ to %d (%02x)\n", isr, s->intctl);
+#endif
+ qemu_set_irq(s->irq, isr);
+}
+
+static int mipsnet_buffer_full(MIPSnetState *s)
+{
+ if (s->rx_count >= MAX_ETH_FRAME_SIZE)
+ return 1;
+ return 0;
+}
+
+static int mipsnet_can_receive(void *opaque)
+{
+ MIPSnetState *s = opaque;
+
+ if (s->busy)
+ return 0;
+ return !mipsnet_buffer_full(s);
+}
+
+static void mipsnet_receive(void *opaque, const uint8_t *buf, int size)
+{
+ MIPSnetState *s = opaque;
+
+#ifdef DEBUG_MIPSNET_RECEIVE
+ printf("mipsnet: receiving len=%d\n", size);
+#endif
+ if (!mipsnet_can_receive(opaque))
+ return;
+
+ s->busy = 1;
+
+ /* Just accept everything. */
+
+ /* Write packet data. */
+ memcpy(s->rx_buffer, buf, size);
+
+ s->rx_count = size;
+ s->rx_read = 0;
+
+ /* Now we can signal we have received something. */
+ s->intctl |= MIPSNET_INTCTL_RXDONE;
+ mipsnet_update_irq(s);
+}
+
+static uint32_t mipsnet_ioport_read(void *opaque, uint32_t addr)
+{
+ MIPSnetState *s = opaque;
+ int ret = 0;
+ const char *devid = MIPSNET_DEV_ID_STRING;
+
+ addr &= 0x3f;
+ switch (addr) {
+ case MIPSNET_DEV_ID:
+ ret = *((uint32_t *)&devid);
+ break;
+ case MIPSNET_DEV_ID + 4:
+ ret = *((uint32_t *)(&devid + 4));
+ break;
+ case MIPSNET_BUSY:
+ ret = s->busy;
+ break;
+ case MIPSNET_RX_DATA_COUNT:
+ ret = s->rx_count;
+ break;
+ case MIPSNET_TX_DATA_COUNT:
+ ret = s->tx_count;
+ break;
+ case MIPSNET_INT_CTL:
+ ret = s->intctl;
+ s->intctl &= ~MIPSNET_INTCTL_TESTBIT;
+ break;
+ case MIPSNET_INTERRUPT_INFO:
+ /* XXX: This seems to be a per-VPE interrupt number. */
+ ret = 0;
+ break;
+ case MIPSNET_RX_DATA_BUFFER:
+ if (s->rx_count) {
+ s->rx_count--;
+ ret = s->rx_buffer[s->rx_read++];
+ }
+ break;
+ /* Reads as zero. */
+ case MIPSNET_TX_DATA_BUFFER:
+ default:
+ break;
+ }
+#ifdef DEBUG_MIPSNET_DATA
+ printf("mipsnet: read addr=0x%02x val=0x%02x\n", addr, ret);
+#endif
+ return ret;
+}
+
+static void mipsnet_ioport_write(void *opaque, uint32_t addr, uint32_t val)
+{
+ MIPSnetState *s = opaque;
+
+ addr &= 0x3f;
+#ifdef DEBUG_MIPSNET_DATA
+ printf("mipsnet: write addr=0x%02x val=0x%02x\n", addr, val);
+#endif
+ switch (addr) {
+ case MIPSNET_TX_DATA_COUNT:
+ s->tx_count = (val <= MAX_ETH_FRAME_SIZE) ? val : 0;
+ s->tx_written = 0;
+ break;
+ case MIPSNET_INT_CTL:
+ if (val & MIPSNET_INTCTL_TXDONE) {
+ s->intctl &= ~MIPSNET_INTCTL_TXDONE;
+ } else if (val & MIPSNET_INTCTL_RXDONE) {
+ s->intctl &= ~MIPSNET_INTCTL_RXDONE;
+ } else if (val & MIPSNET_INTCTL_TESTBIT) {
+ mipsnet_reset(s);
+ s->intctl |= MIPSNET_INTCTL_TESTBIT;
+ } else if (!val) {
+ /* ACK testbit interrupt, flag was cleared on read. */
+ }
+ s->busy = !!s->intctl;
+ mipsnet_update_irq(s);
+ break;
+ case MIPSNET_TX_DATA_BUFFER:
+ s->tx_buffer[s->tx_written++] = val;
+ if (s->tx_written == s->tx_count) {
+ /* Send buffer. */
+#ifdef DEBUG_MIPSNET_SEND
+ printf("mipsnet: sending len=%d\n", s->tx_count);
+#endif
+ qemu_send_packet(s->vc, s->tx_buffer, s->tx_count);
+ s->tx_count = s->tx_written = 0;
+ s->intctl |= MIPSNET_INTCTL_TXDONE;
+ s->busy = 1;
+ mipsnet_update_irq(s);
+ }
+ break;
+ /* Read-only registers */
+ case MIPSNET_DEV_ID:
+ case MIPSNET_BUSY:
+ case MIPSNET_RX_DATA_COUNT:
+ case MIPSNET_INTERRUPT_INFO:
+ case MIPSNET_RX_DATA_BUFFER:
+ default:
+ break;
+ }
+}
+
+static void mipsnet_save(QEMUFile *f, void *opaque)
+{
+ MIPSnetState *s = opaque;
+
+ qemu_put_be32s(f, &s->busy);
+ qemu_put_be32s(f, &s->rx_count);
+ qemu_put_be32s(f, &s->rx_read);
+ qemu_put_be32s(f, &s->tx_count);
+ qemu_put_be32s(f, &s->tx_written);
+ qemu_put_be32s(f, &s->intctl);
+ qemu_put_buffer(f, s->rx_buffer, MAX_ETH_FRAME_SIZE);
+ qemu_put_buffer(f, s->tx_buffer, MAX_ETH_FRAME_SIZE);
+}
+
+static int mipsnet_load(QEMUFile *f, void *opaque, int version_id)
+{
+ MIPSnetState *s = opaque;
+
+ if (version_id > 0)
+ return -EINVAL;
+
+ qemu_get_be32s(f, &s->busy);
+ qemu_get_be32s(f, &s->rx_count);
+ qemu_get_be32s(f, &s->rx_read);
+ qemu_get_be32s(f, &s->tx_count);
+ qemu_get_be32s(f, &s->tx_written);
+ qemu_get_be32s(f, &s->intctl);
+ qemu_get_buffer(f, s->rx_buffer, MAX_ETH_FRAME_SIZE);
+ qemu_get_buffer(f, s->tx_buffer, MAX_ETH_FRAME_SIZE);
+
+ return 0;
+}
+
+void mipsnet_init (int base, qemu_irq irq, NICInfo *nd)
+{
+ MIPSnetState *s;
+
+ s = qemu_mallocz(sizeof(MIPSnetState));
+ if (!s)
+ return;
+
+ register_ioport_write(base, 36, 1, mipsnet_ioport_write, s);
+ register_ioport_read(base, 36, 1, mipsnet_ioport_read, s);
+ register_ioport_write(base, 36, 2, mipsnet_ioport_write, s);
+ register_ioport_read(base, 36, 2, mipsnet_ioport_read, s);
+ register_ioport_write(base, 36, 4, mipsnet_ioport_write, s);
+ register_ioport_read(base, 36, 4, mipsnet_ioport_read, s);
+
+ s->irq = irq;
+ s->nd = nd;
+ if (nd && nd->vlan) {
+ s->vc = qemu_new_vlan_client(nd->vlan, mipsnet_receive,
+ mipsnet_can_receive, s);
+ } else {
+ s->vc = NULL;
+ }
+
+ snprintf(s->vc->info_str, sizeof(s->vc->info_str),
+ "mipsnet macaddr=%02x:%02x:%02x:%02x:%02x:%02x",
+ s->nd->macaddr[0],
+ s->nd->macaddr[1],
+ s->nd->macaddr[2],
+ s->nd->macaddr[3],
+ s->nd->macaddr[4],
+ s->nd->macaddr[5]);
+
+ mipsnet_reset(s);
+ register_savevm("mipsnet", 0, 0, mipsnet_save, mipsnet_load, s);
+}