git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@3674 c046a42c-6fe2-441c-8c8c-71466251a162
		
			
				
	
	
		
			273 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			273 lines
		
	
	
		
			7.0 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_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);
 | 
						|
}
 |