271 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			C
		
	
	
	
			
		
		
	
	
			271 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			C
		
	
	
	
| #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_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;
 | |
| 
 | |
|     addr &= 0x3f;
 | |
|     switch (addr) {
 | |
|     case MIPSNET_DEV_ID:
 | |
| 	ret = be32_to_cpu(0x4d495053);		/* MIPS */
 | |
|         break;
 | |
|     case MIPSNET_DEV_ID + 4:
 | |
| 	ret = be32_to_cpu(0x4e455430);		/* NET0 */
 | |
|         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);
 | |
| }
 |