mirror of
https://github.com/Motorhead1991/qemu.git
synced 2025-08-04 16:23:55 -06:00
hw: move NICs to hw/net/, configure via default-configs/
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
This commit is contained in:
parent
0ddfaf7fe4
commit
d7e35d4a84
25 changed files with 22 additions and 10 deletions
|
@ -20,6 +20,14 @@ common-obj-$(CONFIG_MIPSNET) += mipsnet.o
|
|||
common-obj-$(CONFIG_XILINX_AXI) += xilinx_axienet.o
|
||||
|
||||
common-obj-$(CONFIG_CADENCE) += cadence_gem.o
|
||||
common-obj-$(CONFIG_STELLARIS_ENET) += stellaris_enet.o
|
||||
common-obj-$(CONFIG_LANCE) += lance.o
|
||||
|
||||
obj-$(CONFIG_ETRAXFS) += etraxfs_eth.o
|
||||
obj-$(CONFIG_COLDFIRE) += mcf_fec.o
|
||||
obj-$(CONFIG_MILKYMIST) += milkymist-minimac2.o
|
||||
obj-$(CONFIG_PSERIES) += spapr_llan.o
|
||||
obj-$(CONFIG_XILINX_ETHLITE) += xilinx_ethlite.o
|
||||
|
||||
obj-$(CONFIG_VIRTIO) += virtio-net.o
|
||||
obj-y += vhost_net.o
|
||||
|
|
656
hw/net/etraxfs_eth.c
Normal file
656
hw/net/etraxfs_eth.c
Normal file
|
@ -0,0 +1,656 @@
|
|||
/*
|
||||
* QEMU ETRAX Ethernet Controller.
|
||||
*
|
||||
* Copyright (c) 2008 Edgar E. Iglesias, Axis Communications AB.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include "hw/sysbus.h"
|
||||
#include "net/net.h"
|
||||
#include "hw/cris/etraxfs.h"
|
||||
|
||||
#define D(x)
|
||||
|
||||
/* Advertisement control register. */
|
||||
#define ADVERTISE_10HALF 0x0020 /* Try for 10mbps half-duplex */
|
||||
#define ADVERTISE_10FULL 0x0040 /* Try for 10mbps full-duplex */
|
||||
#define ADVERTISE_100HALF 0x0080 /* Try for 100mbps half-duplex */
|
||||
#define ADVERTISE_100FULL 0x0100 /* Try for 100mbps full-duplex */
|
||||
|
||||
/*
|
||||
* The MDIO extensions in the TDK PHY model were reversed engineered from the
|
||||
* linux driver (PHYID and Diagnostics reg).
|
||||
* TODO: Add friendly names for the register nums.
|
||||
*/
|
||||
struct qemu_phy
|
||||
{
|
||||
uint32_t regs[32];
|
||||
|
||||
int link;
|
||||
|
||||
unsigned int (*read)(struct qemu_phy *phy, unsigned int req);
|
||||
void (*write)(struct qemu_phy *phy, unsigned int req, unsigned int data);
|
||||
};
|
||||
|
||||
static unsigned int tdk_read(struct qemu_phy *phy, unsigned int req)
|
||||
{
|
||||
int regnum;
|
||||
unsigned r = 0;
|
||||
|
||||
regnum = req & 0x1f;
|
||||
|
||||
switch (regnum) {
|
||||
case 1:
|
||||
if (!phy->link) {
|
||||
break;
|
||||
}
|
||||
/* MR1. */
|
||||
/* Speeds and modes. */
|
||||
r |= (1 << 13) | (1 << 14);
|
||||
r |= (1 << 11) | (1 << 12);
|
||||
r |= (1 << 5); /* Autoneg complete. */
|
||||
r |= (1 << 3); /* Autoneg able. */
|
||||
r |= (1 << 2); /* link. */
|
||||
break;
|
||||
case 5:
|
||||
/* Link partner ability.
|
||||
We are kind; always agree with whatever best mode
|
||||
the guest advertises. */
|
||||
r = 1 << 14; /* Success. */
|
||||
/* Copy advertised modes. */
|
||||
r |= phy->regs[4] & (15 << 5);
|
||||
/* Autoneg support. */
|
||||
r |= 1;
|
||||
break;
|
||||
case 18:
|
||||
{
|
||||
/* Diagnostics reg. */
|
||||
int duplex = 0;
|
||||
int speed_100 = 0;
|
||||
|
||||
if (!phy->link) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Are we advertising 100 half or 100 duplex ? */
|
||||
speed_100 = !!(phy->regs[4] & ADVERTISE_100HALF);
|
||||
speed_100 |= !!(phy->regs[4] & ADVERTISE_100FULL);
|
||||
|
||||
/* Are we advertising 10 duplex or 100 duplex ? */
|
||||
duplex = !!(phy->regs[4] & ADVERTISE_100FULL);
|
||||
duplex |= !!(phy->regs[4] & ADVERTISE_10FULL);
|
||||
r = (speed_100 << 10) | (duplex << 11);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
r = phy->regs[regnum];
|
||||
break;
|
||||
}
|
||||
D(printf("\n%s %x = reg[%d]\n", __func__, r, regnum));
|
||||
return r;
|
||||
}
|
||||
|
||||
static void
|
||||
tdk_write(struct qemu_phy *phy, unsigned int req, unsigned int data)
|
||||
{
|
||||
int regnum;
|
||||
|
||||
regnum = req & 0x1f;
|
||||
D(printf("%s reg[%d] = %x\n", __func__, regnum, data));
|
||||
switch (regnum) {
|
||||
default:
|
||||
phy->regs[regnum] = data;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
tdk_init(struct qemu_phy *phy)
|
||||
{
|
||||
phy->regs[0] = 0x3100;
|
||||
/* PHY Id. */
|
||||
phy->regs[2] = 0x0300;
|
||||
phy->regs[3] = 0xe400;
|
||||
/* Autonegotiation advertisement reg. */
|
||||
phy->regs[4] = 0x01E1;
|
||||
phy->link = 1;
|
||||
|
||||
phy->read = tdk_read;
|
||||
phy->write = tdk_write;
|
||||
}
|
||||
|
||||
struct qemu_mdio
|
||||
{
|
||||
/* bus. */
|
||||
int mdc;
|
||||
int mdio;
|
||||
|
||||
/* decoder. */
|
||||
enum {
|
||||
PREAMBLE,
|
||||
SOF,
|
||||
OPC,
|
||||
ADDR,
|
||||
REQ,
|
||||
TURNAROUND,
|
||||
DATA
|
||||
} state;
|
||||
unsigned int drive;
|
||||
|
||||
unsigned int cnt;
|
||||
unsigned int addr;
|
||||
unsigned int opc;
|
||||
unsigned int req;
|
||||
unsigned int data;
|
||||
|
||||
struct qemu_phy *devs[32];
|
||||
};
|
||||
|
||||
static void
|
||||
mdio_attach(struct qemu_mdio *bus, struct qemu_phy *phy, unsigned int addr)
|
||||
{
|
||||
bus->devs[addr & 0x1f] = phy;
|
||||
}
|
||||
|
||||
#ifdef USE_THIS_DEAD_CODE
|
||||
static void
|
||||
mdio_detach(struct qemu_mdio *bus, struct qemu_phy *phy, unsigned int addr)
|
||||
{
|
||||
bus->devs[addr & 0x1f] = NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void mdio_read_req(struct qemu_mdio *bus)
|
||||
{
|
||||
struct qemu_phy *phy;
|
||||
|
||||
phy = bus->devs[bus->addr];
|
||||
if (phy && phy->read) {
|
||||
bus->data = phy->read(phy, bus->req);
|
||||
} else {
|
||||
bus->data = 0xffff;
|
||||
}
|
||||
}
|
||||
|
||||
static void mdio_write_req(struct qemu_mdio *bus)
|
||||
{
|
||||
struct qemu_phy *phy;
|
||||
|
||||
phy = bus->devs[bus->addr];
|
||||
if (phy && phy->write) {
|
||||
phy->write(phy, bus->req, bus->data);
|
||||
}
|
||||
}
|
||||
|
||||
static void mdio_cycle(struct qemu_mdio *bus)
|
||||
{
|
||||
bus->cnt++;
|
||||
|
||||
D(printf("mdc=%d mdio=%d state=%d cnt=%d drv=%d\n",
|
||||
bus->mdc, bus->mdio, bus->state, bus->cnt, bus->drive));
|
||||
#if 0
|
||||
if (bus->mdc) {
|
||||
printf("%d", bus->mdio);
|
||||
}
|
||||
#endif
|
||||
switch (bus->state) {
|
||||
case PREAMBLE:
|
||||
if (bus->mdc) {
|
||||
if (bus->cnt >= (32 * 2) && !bus->mdio) {
|
||||
bus->cnt = 0;
|
||||
bus->state = SOF;
|
||||
bus->data = 0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SOF:
|
||||
if (bus->mdc) {
|
||||
if (bus->mdio != 1) {
|
||||
printf("WARNING: no SOF\n");
|
||||
}
|
||||
if (bus->cnt == 1*2) {
|
||||
bus->cnt = 0;
|
||||
bus->opc = 0;
|
||||
bus->state = OPC;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case OPC:
|
||||
if (bus->mdc) {
|
||||
bus->opc <<= 1;
|
||||
bus->opc |= bus->mdio & 1;
|
||||
if (bus->cnt == 2*2) {
|
||||
bus->cnt = 0;
|
||||
bus->addr = 0;
|
||||
bus->state = ADDR;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ADDR:
|
||||
if (bus->mdc) {
|
||||
bus->addr <<= 1;
|
||||
bus->addr |= bus->mdio & 1;
|
||||
|
||||
if (bus->cnt == 5*2) {
|
||||
bus->cnt = 0;
|
||||
bus->req = 0;
|
||||
bus->state = REQ;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case REQ:
|
||||
if (bus->mdc) {
|
||||
bus->req <<= 1;
|
||||
bus->req |= bus->mdio & 1;
|
||||
if (bus->cnt == 5*2) {
|
||||
bus->cnt = 0;
|
||||
bus->state = TURNAROUND;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case TURNAROUND:
|
||||
if (bus->mdc && bus->cnt == 2*2) {
|
||||
bus->mdio = 0;
|
||||
bus->cnt = 0;
|
||||
|
||||
if (bus->opc == 2) {
|
||||
bus->drive = 1;
|
||||
mdio_read_req(bus);
|
||||
bus->mdio = bus->data & 1;
|
||||
}
|
||||
bus->state = DATA;
|
||||
}
|
||||
break;
|
||||
case DATA:
|
||||
if (!bus->mdc) {
|
||||
if (bus->drive) {
|
||||
bus->mdio = !!(bus->data & (1 << 15));
|
||||
bus->data <<= 1;
|
||||
}
|
||||
} else {
|
||||
if (!bus->drive) {
|
||||
bus->data <<= 1;
|
||||
bus->data |= bus->mdio;
|
||||
}
|
||||
if (bus->cnt == 16 * 2) {
|
||||
bus->cnt = 0;
|
||||
bus->state = PREAMBLE;
|
||||
if (!bus->drive) {
|
||||
mdio_write_req(bus);
|
||||
}
|
||||
bus->drive = 0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* ETRAX-FS Ethernet MAC block starts here. */
|
||||
|
||||
#define RW_MA0_LO 0x00
|
||||
#define RW_MA0_HI 0x01
|
||||
#define RW_MA1_LO 0x02
|
||||
#define RW_MA1_HI 0x03
|
||||
#define RW_GA_LO 0x04
|
||||
#define RW_GA_HI 0x05
|
||||
#define RW_GEN_CTRL 0x06
|
||||
#define RW_REC_CTRL 0x07
|
||||
#define RW_TR_CTRL 0x08
|
||||
#define RW_CLR_ERR 0x09
|
||||
#define RW_MGM_CTRL 0x0a
|
||||
#define R_STAT 0x0b
|
||||
#define FS_ETH_MAX_REGS 0x17
|
||||
|
||||
struct fs_eth
|
||||
{
|
||||
SysBusDevice busdev;
|
||||
MemoryRegion mmio;
|
||||
NICState *nic;
|
||||
NICConf conf;
|
||||
|
||||
/* Two addrs in the filter. */
|
||||
uint8_t macaddr[2][6];
|
||||
uint32_t regs[FS_ETH_MAX_REGS];
|
||||
|
||||
union {
|
||||
void *vdma_out;
|
||||
struct etraxfs_dma_client *dma_out;
|
||||
};
|
||||
union {
|
||||
void *vdma_in;
|
||||
struct etraxfs_dma_client *dma_in;
|
||||
};
|
||||
|
||||
/* MDIO bus. */
|
||||
struct qemu_mdio mdio_bus;
|
||||
unsigned int phyaddr;
|
||||
int duplex_mismatch;
|
||||
|
||||
/* PHY. */
|
||||
struct qemu_phy phy;
|
||||
};
|
||||
|
||||
static void eth_validate_duplex(struct fs_eth *eth)
|
||||
{
|
||||
struct qemu_phy *phy;
|
||||
unsigned int phy_duplex;
|
||||
unsigned int mac_duplex;
|
||||
int new_mm = 0;
|
||||
|
||||
phy = eth->mdio_bus.devs[eth->phyaddr];
|
||||
phy_duplex = !!(phy->read(phy, 18) & (1 << 11));
|
||||
mac_duplex = !!(eth->regs[RW_REC_CTRL] & 128);
|
||||
|
||||
if (mac_duplex != phy_duplex) {
|
||||
new_mm = 1;
|
||||
}
|
||||
|
||||
if (eth->regs[RW_GEN_CTRL] & 1) {
|
||||
if (new_mm != eth->duplex_mismatch) {
|
||||
if (new_mm) {
|
||||
printf("HW: WARNING ETH duplex mismatch MAC=%d PHY=%d\n",
|
||||
mac_duplex, phy_duplex);
|
||||
} else {
|
||||
printf("HW: ETH duplex ok.\n");
|
||||
}
|
||||
}
|
||||
eth->duplex_mismatch = new_mm;
|
||||
}
|
||||
}
|
||||
|
||||
static uint64_t
|
||||
eth_read(void *opaque, hwaddr addr, unsigned int size)
|
||||
{
|
||||
struct fs_eth *eth = opaque;
|
||||
uint32_t r = 0;
|
||||
|
||||
addr >>= 2;
|
||||
|
||||
switch (addr) {
|
||||
case R_STAT:
|
||||
r = eth->mdio_bus.mdio & 1;
|
||||
break;
|
||||
default:
|
||||
r = eth->regs[addr];
|
||||
D(printf("%s %x\n", __func__, addr * 4));
|
||||
break;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
static void eth_update_ma(struct fs_eth *eth, int ma)
|
||||
{
|
||||
int reg;
|
||||
int i = 0;
|
||||
|
||||
ma &= 1;
|
||||
|
||||
reg = RW_MA0_LO;
|
||||
if (ma) {
|
||||
reg = RW_MA1_LO;
|
||||
}
|
||||
|
||||
eth->macaddr[ma][i++] = eth->regs[reg];
|
||||
eth->macaddr[ma][i++] = eth->regs[reg] >> 8;
|
||||
eth->macaddr[ma][i++] = eth->regs[reg] >> 16;
|
||||
eth->macaddr[ma][i++] = eth->regs[reg] >> 24;
|
||||
eth->macaddr[ma][i++] = eth->regs[reg + 1];
|
||||
eth->macaddr[ma][i] = eth->regs[reg + 1] >> 8;
|
||||
|
||||
D(printf("set mac%d=%x.%x.%x.%x.%x.%x\n", ma,
|
||||
eth->macaddr[ma][0], eth->macaddr[ma][1],
|
||||
eth->macaddr[ma][2], eth->macaddr[ma][3],
|
||||
eth->macaddr[ma][4], eth->macaddr[ma][5]));
|
||||
}
|
||||
|
||||
static void
|
||||
eth_write(void *opaque, hwaddr addr,
|
||||
uint64_t val64, unsigned int size)
|
||||
{
|
||||
struct fs_eth *eth = opaque;
|
||||
uint32_t value = val64;
|
||||
|
||||
addr >>= 2;
|
||||
switch (addr) {
|
||||
case RW_MA0_LO:
|
||||
case RW_MA0_HI:
|
||||
eth->regs[addr] = value;
|
||||
eth_update_ma(eth, 0);
|
||||
break;
|
||||
case RW_MA1_LO:
|
||||
case RW_MA1_HI:
|
||||
eth->regs[addr] = value;
|
||||
eth_update_ma(eth, 1);
|
||||
break;
|
||||
|
||||
case RW_MGM_CTRL:
|
||||
/* Attach an MDIO/PHY abstraction. */
|
||||
if (value & 2) {
|
||||
eth->mdio_bus.mdio = value & 1;
|
||||
}
|
||||
if (eth->mdio_bus.mdc != (value & 4)) {
|
||||
mdio_cycle(ð->mdio_bus);
|
||||
eth_validate_duplex(eth);
|
||||
}
|
||||
eth->mdio_bus.mdc = !!(value & 4);
|
||||
eth->regs[addr] = value;
|
||||
break;
|
||||
|
||||
case RW_REC_CTRL:
|
||||
eth->regs[addr] = value;
|
||||
eth_validate_duplex(eth);
|
||||
break;
|
||||
|
||||
default:
|
||||
eth->regs[addr] = value;
|
||||
D(printf("%s %x %x\n", __func__, addr, value));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* The ETRAX FS has a groupt address table (GAT) which works like a k=1 bloom
|
||||
filter dropping group addresses we have not joined. The filter has 64
|
||||
bits (m). The has function is a simple nible xor of the group addr. */
|
||||
static int eth_match_groupaddr(struct fs_eth *eth, const unsigned char *sa)
|
||||
{
|
||||
unsigned int hsh;
|
||||
int m_individual = eth->regs[RW_REC_CTRL] & 4;
|
||||
int match;
|
||||
|
||||
/* First bit on the wire of a MAC address signals multicast or
|
||||
physical address. */
|
||||
if (!m_individual && !(sa[0] & 1)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Calculate the hash index for the GA registers. */
|
||||
hsh = 0;
|
||||
hsh ^= (*sa) & 0x3f;
|
||||
hsh ^= ((*sa) >> 6) & 0x03;
|
||||
++sa;
|
||||
hsh ^= ((*sa) << 2) & 0x03c;
|
||||
hsh ^= ((*sa) >> 4) & 0xf;
|
||||
++sa;
|
||||
hsh ^= ((*sa) << 4) & 0x30;
|
||||
hsh ^= ((*sa) >> 2) & 0x3f;
|
||||
++sa;
|
||||
hsh ^= (*sa) & 0x3f;
|
||||
hsh ^= ((*sa) >> 6) & 0x03;
|
||||
++sa;
|
||||
hsh ^= ((*sa) << 2) & 0x03c;
|
||||
hsh ^= ((*sa) >> 4) & 0xf;
|
||||
++sa;
|
||||
hsh ^= ((*sa) << 4) & 0x30;
|
||||
hsh ^= ((*sa) >> 2) & 0x3f;
|
||||
|
||||
hsh &= 63;
|
||||
if (hsh > 31) {
|
||||
match = eth->regs[RW_GA_HI] & (1 << (hsh - 32));
|
||||
} else {
|
||||
match = eth->regs[RW_GA_LO] & (1 << hsh);
|
||||
}
|
||||
D(printf("hsh=%x ga=%x.%x mtch=%d\n", hsh,
|
||||
eth->regs[RW_GA_HI], eth->regs[RW_GA_LO], match));
|
||||
return match;
|
||||
}
|
||||
|
||||
static int eth_can_receive(NetClientState *nc)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
static ssize_t eth_receive(NetClientState *nc, const uint8_t *buf, size_t size)
|
||||
{
|
||||
unsigned char sa_bcast[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
|
||||
struct fs_eth *eth = qemu_get_nic_opaque(nc);
|
||||
int use_ma0 = eth->regs[RW_REC_CTRL] & 1;
|
||||
int use_ma1 = eth->regs[RW_REC_CTRL] & 2;
|
||||
int r_bcast = eth->regs[RW_REC_CTRL] & 8;
|
||||
|
||||
if (size < 12) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
D(printf("%x.%x.%x.%x.%x.%x ma=%d %d bc=%d\n",
|
||||
buf[0], buf[1], buf[2], buf[3], buf[4], buf[5],
|
||||
use_ma0, use_ma1, r_bcast));
|
||||
|
||||
/* Does the frame get through the address filters? */
|
||||
if ((!use_ma0 || memcmp(buf, eth->macaddr[0], 6))
|
||||
&& (!use_ma1 || memcmp(buf, eth->macaddr[1], 6))
|
||||
&& (!r_bcast || memcmp(buf, sa_bcast, 6))
|
||||
&& !eth_match_groupaddr(eth, buf)) {
|
||||
return size;
|
||||
}
|
||||
|
||||
/* FIXME: Find another way to pass on the fake csum. */
|
||||
etraxfs_dmac_input(eth->dma_in, (void *)buf, size + 4, 1);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static int eth_tx_push(void *opaque, unsigned char *buf, int len, bool eop)
|
||||
{
|
||||
struct fs_eth *eth = opaque;
|
||||
|
||||
D(printf("%s buf=%p len=%d\n", __func__, buf, len));
|
||||
qemu_send_packet(qemu_get_queue(eth->nic), buf, len);
|
||||
return len;
|
||||
}
|
||||
|
||||
static void eth_set_link(NetClientState *nc)
|
||||
{
|
||||
struct fs_eth *eth = qemu_get_nic_opaque(nc);
|
||||
D(printf("%s %d\n", __func__, nc->link_down));
|
||||
eth->phy.link = !nc->link_down;
|
||||
}
|
||||
|
||||
static const MemoryRegionOps eth_ops = {
|
||||
.read = eth_read,
|
||||
.write = eth_write,
|
||||
.endianness = DEVICE_LITTLE_ENDIAN,
|
||||
.valid = {
|
||||
.min_access_size = 4,
|
||||
.max_access_size = 4
|
||||
}
|
||||
};
|
||||
|
||||
static void eth_cleanup(NetClientState *nc)
|
||||
{
|
||||
struct fs_eth *eth = qemu_get_nic_opaque(nc);
|
||||
|
||||
/* Disconnect the client. */
|
||||
eth->dma_out->client.push = NULL;
|
||||
eth->dma_out->client.opaque = NULL;
|
||||
eth->dma_in->client.opaque = NULL;
|
||||
eth->dma_in->client.pull = NULL;
|
||||
g_free(eth);
|
||||
}
|
||||
|
||||
static NetClientInfo net_etraxfs_info = {
|
||||
.type = NET_CLIENT_OPTIONS_KIND_NIC,
|
||||
.size = sizeof(NICState),
|
||||
.can_receive = eth_can_receive,
|
||||
.receive = eth_receive,
|
||||
.cleanup = eth_cleanup,
|
||||
.link_status_changed = eth_set_link,
|
||||
};
|
||||
|
||||
static int fs_eth_init(SysBusDevice *dev)
|
||||
{
|
||||
struct fs_eth *s = FROM_SYSBUS(typeof(*s), dev);
|
||||
|
||||
if (!s->dma_out || !s->dma_in) {
|
||||
hw_error("Unconnected ETRAX-FS Ethernet MAC.\n");
|
||||
}
|
||||
|
||||
s->dma_out->client.push = eth_tx_push;
|
||||
s->dma_out->client.opaque = s;
|
||||
s->dma_in->client.opaque = s;
|
||||
s->dma_in->client.pull = NULL;
|
||||
|
||||
memory_region_init_io(&s->mmio, ð_ops, s, "etraxfs-eth", 0x5c);
|
||||
sysbus_init_mmio(dev, &s->mmio);
|
||||
|
||||
qemu_macaddr_default_if_unset(&s->conf.macaddr);
|
||||
s->nic = qemu_new_nic(&net_etraxfs_info, &s->conf,
|
||||
object_get_typename(OBJECT(s)), dev->qdev.id, s);
|
||||
qemu_format_nic_info_str(qemu_get_queue(s->nic), s->conf.macaddr.a);
|
||||
|
||||
|
||||
tdk_init(&s->phy);
|
||||
mdio_attach(&s->mdio_bus, &s->phy, s->phyaddr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static Property etraxfs_eth_properties[] = {
|
||||
DEFINE_PROP_UINT32("phyaddr", struct fs_eth, phyaddr, 1),
|
||||
DEFINE_PROP_PTR("dma_out", struct fs_eth, vdma_out),
|
||||
DEFINE_PROP_PTR("dma_in", struct fs_eth, vdma_in),
|
||||
DEFINE_NIC_PROPERTIES(struct fs_eth, conf),
|
||||
DEFINE_PROP_END_OF_LIST(),
|
||||
};
|
||||
|
||||
static void etraxfs_eth_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
|
||||
|
||||
k->init = fs_eth_init;
|
||||
dc->props = etraxfs_eth_properties;
|
||||
}
|
||||
|
||||
static const TypeInfo etraxfs_eth_info = {
|
||||
.name = "etraxfs-eth",
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(struct fs_eth),
|
||||
.class_init = etraxfs_eth_class_init,
|
||||
};
|
||||
|
||||
static void etraxfs_eth_register_types(void)
|
||||
{
|
||||
type_register_static(&etraxfs_eth_info);
|
||||
}
|
||||
|
||||
type_init(etraxfs_eth_register_types)
|
170
hw/net/lance.c
Normal file
170
hw/net/lance.c
Normal file
|
@ -0,0 +1,170 @@
|
|||
/*
|
||||
* QEMU AMD PC-Net II (Am79C970A) emulation
|
||||
*
|
||||
* Copyright (c) 2004 Antony T Curtis
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/* This software was written to be compatible with the specification:
|
||||
* AMD Am79C970A PCnet-PCI II Ethernet Controller Data-Sheet
|
||||
* AMD Publication# 19436 Rev:E Amendment/0 Issue Date: June 2000
|
||||
*/
|
||||
|
||||
/*
|
||||
* On Sparc32, this is the Lance (Am7990) part of chip STP2000 (Master I/O), also
|
||||
* produced as NCR89C100. See
|
||||
* http://www.ibiblio.org/pub/historic-linux/early-ports/Sparc/NCR/NCR89C100.txt
|
||||
* and
|
||||
* http://www.ibiblio.org/pub/historic-linux/early-ports/Sparc/NCR/NCR92C990.txt
|
||||
*/
|
||||
|
||||
#include "hw/sysbus.h"
|
||||
#include "net/net.h"
|
||||
#include "qemu/timer.h"
|
||||
#include "qemu/sockets.h"
|
||||
#include "hw/sparc/sun4m.h"
|
||||
#include "hw/pcnet.h"
|
||||
#include "trace.h"
|
||||
|
||||
typedef struct {
|
||||
SysBusDevice busdev;
|
||||
PCNetState state;
|
||||
} SysBusPCNetState;
|
||||
|
||||
static void parent_lance_reset(void *opaque, int irq, int level)
|
||||
{
|
||||
SysBusPCNetState *d = opaque;
|
||||
if (level)
|
||||
pcnet_h_reset(&d->state);
|
||||
}
|
||||
|
||||
static void lance_mem_write(void *opaque, hwaddr addr,
|
||||
uint64_t val, unsigned size)
|
||||
{
|
||||
SysBusPCNetState *d = opaque;
|
||||
|
||||
trace_lance_mem_writew(addr, val & 0xffff);
|
||||
pcnet_ioport_writew(&d->state, addr, val & 0xffff);
|
||||
}
|
||||
|
||||
static uint64_t lance_mem_read(void *opaque, hwaddr addr,
|
||||
unsigned size)
|
||||
{
|
||||
SysBusPCNetState *d = opaque;
|
||||
uint32_t val;
|
||||
|
||||
val = pcnet_ioport_readw(&d->state, addr);
|
||||
trace_lance_mem_readw(addr, val & 0xffff);
|
||||
return val & 0xffff;
|
||||
}
|
||||
|
||||
static const MemoryRegionOps lance_mem_ops = {
|
||||
.read = lance_mem_read,
|
||||
.write = lance_mem_write,
|
||||
.endianness = DEVICE_NATIVE_ENDIAN,
|
||||
.valid = {
|
||||
.min_access_size = 2,
|
||||
.max_access_size = 2,
|
||||
},
|
||||
};
|
||||
|
||||
static void lance_cleanup(NetClientState *nc)
|
||||
{
|
||||
PCNetState *d = qemu_get_nic_opaque(nc);
|
||||
|
||||
pcnet_common_cleanup(d);
|
||||
}
|
||||
|
||||
static NetClientInfo net_lance_info = {
|
||||
.type = NET_CLIENT_OPTIONS_KIND_NIC,
|
||||
.size = sizeof(NICState),
|
||||
.can_receive = pcnet_can_receive,
|
||||
.receive = pcnet_receive,
|
||||
.link_status_changed = pcnet_set_link_status,
|
||||
.cleanup = lance_cleanup,
|
||||
};
|
||||
|
||||
static const VMStateDescription vmstate_lance = {
|
||||
.name = "pcnet",
|
||||
.version_id = 3,
|
||||
.minimum_version_id = 2,
|
||||
.minimum_version_id_old = 2,
|
||||
.fields = (VMStateField []) {
|
||||
VMSTATE_STRUCT(state, SysBusPCNetState, 0, vmstate_pcnet, PCNetState),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
static int lance_init(SysBusDevice *dev)
|
||||
{
|
||||
SysBusPCNetState *d = FROM_SYSBUS(SysBusPCNetState, dev);
|
||||
PCNetState *s = &d->state;
|
||||
|
||||
memory_region_init_io(&s->mmio, &lance_mem_ops, d, "lance-mmio", 4);
|
||||
|
||||
qdev_init_gpio_in(&dev->qdev, parent_lance_reset, 1);
|
||||
|
||||
sysbus_init_mmio(dev, &s->mmio);
|
||||
|
||||
sysbus_init_irq(dev, &s->irq);
|
||||
|
||||
s->phys_mem_read = ledma_memory_read;
|
||||
s->phys_mem_write = ledma_memory_write;
|
||||
return pcnet_common_init(&dev->qdev, s, &net_lance_info);
|
||||
}
|
||||
|
||||
static void lance_reset(DeviceState *dev)
|
||||
{
|
||||
SysBusPCNetState *d = DO_UPCAST(SysBusPCNetState, busdev.qdev, dev);
|
||||
|
||||
pcnet_h_reset(&d->state);
|
||||
}
|
||||
|
||||
static Property lance_properties[] = {
|
||||
DEFINE_PROP_PTR("dma", SysBusPCNetState, state.dma_opaque),
|
||||
DEFINE_NIC_PROPERTIES(SysBusPCNetState, state.conf),
|
||||
DEFINE_PROP_END_OF_LIST(),
|
||||
};
|
||||
|
||||
static void lance_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
|
||||
|
||||
k->init = lance_init;
|
||||
dc->fw_name = "ethernet";
|
||||
dc->reset = lance_reset;
|
||||
dc->vmsd = &vmstate_lance;
|
||||
dc->props = lance_properties;
|
||||
}
|
||||
|
||||
static const TypeInfo lance_info = {
|
||||
.name = "lance",
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(SysBusPCNetState),
|
||||
.class_init = lance_class_init,
|
||||
};
|
||||
|
||||
static void lance_register_types(void)
|
||||
{
|
||||
type_register_static(&lance_info);
|
||||
}
|
||||
|
||||
type_init(lance_register_types)
|
480
hw/net/mcf_fec.c
Normal file
480
hw/net/mcf_fec.c
Normal file
|
@ -0,0 +1,480 @@
|
|||
/*
|
||||
* ColdFire Fast Ethernet Controller emulation.
|
||||
*
|
||||
* Copyright (c) 2007 CodeSourcery.
|
||||
*
|
||||
* This code is licensed under the GPL
|
||||
*/
|
||||
#include "hw/hw.h"
|
||||
#include "net/net.h"
|
||||
#include "hw/m68k/mcf.h"
|
||||
/* For crc32 */
|
||||
#include <zlib.h>
|
||||
#include "exec/address-spaces.h"
|
||||
|
||||
//#define DEBUG_FEC 1
|
||||
|
||||
#ifdef DEBUG_FEC
|
||||
#define DPRINTF(fmt, ...) \
|
||||
do { printf("mcf_fec: " fmt , ## __VA_ARGS__); } while (0)
|
||||
#else
|
||||
#define DPRINTF(fmt, ...) do {} while(0)
|
||||
#endif
|
||||
|
||||
#define FEC_MAX_FRAME_SIZE 2032
|
||||
|
||||
typedef struct {
|
||||
MemoryRegion *sysmem;
|
||||
MemoryRegion iomem;
|
||||
qemu_irq *irq;
|
||||
NICState *nic;
|
||||
NICConf conf;
|
||||
uint32_t irq_state;
|
||||
uint32_t eir;
|
||||
uint32_t eimr;
|
||||
int rx_enabled;
|
||||
uint32_t rx_descriptor;
|
||||
uint32_t tx_descriptor;
|
||||
uint32_t ecr;
|
||||
uint32_t mmfr;
|
||||
uint32_t mscr;
|
||||
uint32_t rcr;
|
||||
uint32_t tcr;
|
||||
uint32_t tfwr;
|
||||
uint32_t rfsr;
|
||||
uint32_t erdsr;
|
||||
uint32_t etdsr;
|
||||
uint32_t emrbr;
|
||||
} mcf_fec_state;
|
||||
|
||||
#define FEC_INT_HB 0x80000000
|
||||
#define FEC_INT_BABR 0x40000000
|
||||
#define FEC_INT_BABT 0x20000000
|
||||
#define FEC_INT_GRA 0x10000000
|
||||
#define FEC_INT_TXF 0x08000000
|
||||
#define FEC_INT_TXB 0x04000000
|
||||
#define FEC_INT_RXF 0x02000000
|
||||
#define FEC_INT_RXB 0x01000000
|
||||
#define FEC_INT_MII 0x00800000
|
||||
#define FEC_INT_EB 0x00400000
|
||||
#define FEC_INT_LC 0x00200000
|
||||
#define FEC_INT_RL 0x00100000
|
||||
#define FEC_INT_UN 0x00080000
|
||||
|
||||
#define FEC_EN 2
|
||||
#define FEC_RESET 1
|
||||
|
||||
/* Map interrupt flags onto IRQ lines. */
|
||||
#define FEC_NUM_IRQ 13
|
||||
static const uint32_t mcf_fec_irq_map[FEC_NUM_IRQ] = {
|
||||
FEC_INT_TXF,
|
||||
FEC_INT_TXB,
|
||||
FEC_INT_UN,
|
||||
FEC_INT_RL,
|
||||
FEC_INT_RXF,
|
||||
FEC_INT_RXB,
|
||||
FEC_INT_MII,
|
||||
FEC_INT_LC,
|
||||
FEC_INT_HB,
|
||||
FEC_INT_GRA,
|
||||
FEC_INT_EB,
|
||||
FEC_INT_BABT,
|
||||
FEC_INT_BABR
|
||||
};
|
||||
|
||||
/* Buffer Descriptor. */
|
||||
typedef struct {
|
||||
uint16_t flags;
|
||||
uint16_t length;
|
||||
uint32_t data;
|
||||
} mcf_fec_bd;
|
||||
|
||||
#define FEC_BD_R 0x8000
|
||||
#define FEC_BD_E 0x8000
|
||||
#define FEC_BD_O1 0x4000
|
||||
#define FEC_BD_W 0x2000
|
||||
#define FEC_BD_O2 0x1000
|
||||
#define FEC_BD_L 0x0800
|
||||
#define FEC_BD_TC 0x0400
|
||||
#define FEC_BD_ABC 0x0200
|
||||
#define FEC_BD_M 0x0100
|
||||
#define FEC_BD_BC 0x0080
|
||||
#define FEC_BD_MC 0x0040
|
||||
#define FEC_BD_LG 0x0020
|
||||
#define FEC_BD_NO 0x0010
|
||||
#define FEC_BD_CR 0x0004
|
||||
#define FEC_BD_OV 0x0002
|
||||
#define FEC_BD_TR 0x0001
|
||||
|
||||
static void mcf_fec_read_bd(mcf_fec_bd *bd, uint32_t addr)
|
||||
{
|
||||
cpu_physical_memory_read(addr, (uint8_t *)bd, sizeof(*bd));
|
||||
be16_to_cpus(&bd->flags);
|
||||
be16_to_cpus(&bd->length);
|
||||
be32_to_cpus(&bd->data);
|
||||
}
|
||||
|
||||
static void mcf_fec_write_bd(mcf_fec_bd *bd, uint32_t addr)
|
||||
{
|
||||
mcf_fec_bd tmp;
|
||||
tmp.flags = cpu_to_be16(bd->flags);
|
||||
tmp.length = cpu_to_be16(bd->length);
|
||||
tmp.data = cpu_to_be32(bd->data);
|
||||
cpu_physical_memory_write(addr, (uint8_t *)&tmp, sizeof(tmp));
|
||||
}
|
||||
|
||||
static void mcf_fec_update(mcf_fec_state *s)
|
||||
{
|
||||
uint32_t active;
|
||||
uint32_t changed;
|
||||
uint32_t mask;
|
||||
int i;
|
||||
|
||||
active = s->eir & s->eimr;
|
||||
changed = active ^s->irq_state;
|
||||
for (i = 0; i < FEC_NUM_IRQ; i++) {
|
||||
mask = mcf_fec_irq_map[i];
|
||||
if (changed & mask) {
|
||||
DPRINTF("IRQ %d = %d\n", i, (active & mask) != 0);
|
||||
qemu_set_irq(s->irq[i], (active & mask) != 0);
|
||||
}
|
||||
}
|
||||
s->irq_state = active;
|
||||
}
|
||||
|
||||
static void mcf_fec_do_tx(mcf_fec_state *s)
|
||||
{
|
||||
uint32_t addr;
|
||||
mcf_fec_bd bd;
|
||||
int frame_size;
|
||||
int len;
|
||||
uint8_t frame[FEC_MAX_FRAME_SIZE];
|
||||
uint8_t *ptr;
|
||||
|
||||
DPRINTF("do_tx\n");
|
||||
ptr = frame;
|
||||
frame_size = 0;
|
||||
addr = s->tx_descriptor;
|
||||
while (1) {
|
||||
mcf_fec_read_bd(&bd, addr);
|
||||
DPRINTF("tx_bd %x flags %04x len %d data %08x\n",
|
||||
addr, bd.flags, bd.length, bd.data);
|
||||
if ((bd.flags & FEC_BD_R) == 0) {
|
||||
/* Run out of descriptors to transmit. */
|
||||
break;
|
||||
}
|
||||
len = bd.length;
|
||||
if (frame_size + len > FEC_MAX_FRAME_SIZE) {
|
||||
len = FEC_MAX_FRAME_SIZE - frame_size;
|
||||
s->eir |= FEC_INT_BABT;
|
||||
}
|
||||
cpu_physical_memory_read(bd.data, ptr, len);
|
||||
ptr += len;
|
||||
frame_size += len;
|
||||
if (bd.flags & FEC_BD_L) {
|
||||
/* Last buffer in frame. */
|
||||
DPRINTF("Sending packet\n");
|
||||
qemu_send_packet(qemu_get_queue(s->nic), frame, len);
|
||||
ptr = frame;
|
||||
frame_size = 0;
|
||||
s->eir |= FEC_INT_TXF;
|
||||
}
|
||||
s->eir |= FEC_INT_TXB;
|
||||
bd.flags &= ~FEC_BD_R;
|
||||
/* Write back the modified descriptor. */
|
||||
mcf_fec_write_bd(&bd, addr);
|
||||
/* Advance to the next descriptor. */
|
||||
if ((bd.flags & FEC_BD_W) != 0) {
|
||||
addr = s->etdsr;
|
||||
} else {
|
||||
addr += 8;
|
||||
}
|
||||
}
|
||||
s->tx_descriptor = addr;
|
||||
}
|
||||
|
||||
static void mcf_fec_enable_rx(mcf_fec_state *s)
|
||||
{
|
||||
mcf_fec_bd bd;
|
||||
|
||||
mcf_fec_read_bd(&bd, s->rx_descriptor);
|
||||
s->rx_enabled = ((bd.flags & FEC_BD_E) != 0);
|
||||
if (!s->rx_enabled)
|
||||
DPRINTF("RX buffer full\n");
|
||||
}
|
||||
|
||||
static void mcf_fec_reset(mcf_fec_state *s)
|
||||
{
|
||||
s->eir = 0;
|
||||
s->eimr = 0;
|
||||
s->rx_enabled = 0;
|
||||
s->ecr = 0;
|
||||
s->mscr = 0;
|
||||
s->rcr = 0x05ee0001;
|
||||
s->tcr = 0;
|
||||
s->tfwr = 0;
|
||||
s->rfsr = 0x500;
|
||||
}
|
||||
|
||||
static uint64_t mcf_fec_read(void *opaque, hwaddr addr,
|
||||
unsigned size)
|
||||
{
|
||||
mcf_fec_state *s = (mcf_fec_state *)opaque;
|
||||
switch (addr & 0x3ff) {
|
||||
case 0x004: return s->eir;
|
||||
case 0x008: return s->eimr;
|
||||
case 0x010: return s->rx_enabled ? (1 << 24) : 0; /* RDAR */
|
||||
case 0x014: return 0; /* TDAR */
|
||||
case 0x024: return s->ecr;
|
||||
case 0x040: return s->mmfr;
|
||||
case 0x044: return s->mscr;
|
||||
case 0x064: return 0; /* MIBC */
|
||||
case 0x084: return s->rcr;
|
||||
case 0x0c4: return s->tcr;
|
||||
case 0x0e4: /* PALR */
|
||||
return (s->conf.macaddr.a[0] << 24) | (s->conf.macaddr.a[1] << 16)
|
||||
| (s->conf.macaddr.a[2] << 8) | s->conf.macaddr.a[3];
|
||||
break;
|
||||
case 0x0e8: /* PAUR */
|
||||
return (s->conf.macaddr.a[4] << 24) | (s->conf.macaddr.a[5] << 16) | 0x8808;
|
||||
case 0x0ec: return 0x10000; /* OPD */
|
||||
case 0x118: return 0;
|
||||
case 0x11c: return 0;
|
||||
case 0x120: return 0;
|
||||
case 0x124: return 0;
|
||||
case 0x144: return s->tfwr;
|
||||
case 0x14c: return 0x600;
|
||||
case 0x150: return s->rfsr;
|
||||
case 0x180: return s->erdsr;
|
||||
case 0x184: return s->etdsr;
|
||||
case 0x188: return s->emrbr;
|
||||
default:
|
||||
hw_error("mcf_fec_read: Bad address 0x%x\n", (int)addr);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void mcf_fec_write(void *opaque, hwaddr addr,
|
||||
uint64_t value, unsigned size)
|
||||
{
|
||||
mcf_fec_state *s = (mcf_fec_state *)opaque;
|
||||
switch (addr & 0x3ff) {
|
||||
case 0x004:
|
||||
s->eir &= ~value;
|
||||
break;
|
||||
case 0x008:
|
||||
s->eimr = value;
|
||||
break;
|
||||
case 0x010: /* RDAR */
|
||||
if ((s->ecr & FEC_EN) && !s->rx_enabled) {
|
||||
DPRINTF("RX enable\n");
|
||||
mcf_fec_enable_rx(s);
|
||||
}
|
||||
break;
|
||||
case 0x014: /* TDAR */
|
||||
if (s->ecr & FEC_EN) {
|
||||
mcf_fec_do_tx(s);
|
||||
}
|
||||
break;
|
||||
case 0x024:
|
||||
s->ecr = value;
|
||||
if (value & FEC_RESET) {
|
||||
DPRINTF("Reset\n");
|
||||
mcf_fec_reset(s);
|
||||
}
|
||||
if ((s->ecr & FEC_EN) == 0) {
|
||||
s->rx_enabled = 0;
|
||||
}
|
||||
break;
|
||||
case 0x040:
|
||||
/* TODO: Implement MII. */
|
||||
s->mmfr = value;
|
||||
break;
|
||||
case 0x044:
|
||||
s->mscr = value & 0xfe;
|
||||
break;
|
||||
case 0x064:
|
||||
/* TODO: Implement MIB. */
|
||||
break;
|
||||
case 0x084:
|
||||
s->rcr = value & 0x07ff003f;
|
||||
/* TODO: Implement LOOP mode. */
|
||||
break;
|
||||
case 0x0c4: /* TCR */
|
||||
/* We transmit immediately, so raise GRA immediately. */
|
||||
s->tcr = value;
|
||||
if (value & 1)
|
||||
s->eir |= FEC_INT_GRA;
|
||||
break;
|
||||
case 0x0e4: /* PALR */
|
||||
s->conf.macaddr.a[0] = value >> 24;
|
||||
s->conf.macaddr.a[1] = value >> 16;
|
||||
s->conf.macaddr.a[2] = value >> 8;
|
||||
s->conf.macaddr.a[3] = value;
|
||||
break;
|
||||
case 0x0e8: /* PAUR */
|
||||
s->conf.macaddr.a[4] = value >> 24;
|
||||
s->conf.macaddr.a[5] = value >> 16;
|
||||
break;
|
||||
case 0x0ec:
|
||||
/* OPD */
|
||||
break;
|
||||
case 0x118:
|
||||
case 0x11c:
|
||||
case 0x120:
|
||||
case 0x124:
|
||||
/* TODO: implement MAC hash filtering. */
|
||||
break;
|
||||
case 0x144:
|
||||
s->tfwr = value & 3;
|
||||
break;
|
||||
case 0x14c:
|
||||
/* FRBR writes ignored. */
|
||||
break;
|
||||
case 0x150:
|
||||
s->rfsr = (value & 0x3fc) | 0x400;
|
||||
break;
|
||||
case 0x180:
|
||||
s->erdsr = value & ~3;
|
||||
s->rx_descriptor = s->erdsr;
|
||||
break;
|
||||
case 0x184:
|
||||
s->etdsr = value & ~3;
|
||||
s->tx_descriptor = s->etdsr;
|
||||
break;
|
||||
case 0x188:
|
||||
s->emrbr = value & 0x7f0;
|
||||
break;
|
||||
default:
|
||||
hw_error("mcf_fec_write Bad address 0x%x\n", (int)addr);
|
||||
}
|
||||
mcf_fec_update(s);
|
||||
}
|
||||
|
||||
static int mcf_fec_can_receive(NetClientState *nc)
|
||||
{
|
||||
mcf_fec_state *s = qemu_get_nic_opaque(nc);
|
||||
return s->rx_enabled;
|
||||
}
|
||||
|
||||
static ssize_t mcf_fec_receive(NetClientState *nc, const uint8_t *buf, size_t size)
|
||||
{
|
||||
mcf_fec_state *s = qemu_get_nic_opaque(nc);
|
||||
mcf_fec_bd bd;
|
||||
uint32_t flags = 0;
|
||||
uint32_t addr;
|
||||
uint32_t crc;
|
||||
uint32_t buf_addr;
|
||||
uint8_t *crc_ptr;
|
||||
unsigned int buf_len;
|
||||
|
||||
DPRINTF("do_rx len %d\n", size);
|
||||
if (!s->rx_enabled) {
|
||||
fprintf(stderr, "mcf_fec_receive: Unexpected packet\n");
|
||||
}
|
||||
/* 4 bytes for the CRC. */
|
||||
size += 4;
|
||||
crc = cpu_to_be32(crc32(~0, buf, size));
|
||||
crc_ptr = (uint8_t *)&crc;
|
||||
/* Huge frames are truncted. */
|
||||
if (size > FEC_MAX_FRAME_SIZE) {
|
||||
size = FEC_MAX_FRAME_SIZE;
|
||||
flags |= FEC_BD_TR | FEC_BD_LG;
|
||||
}
|
||||
/* Frames larger than the user limit just set error flags. */
|
||||
if (size > (s->rcr >> 16)) {
|
||||
flags |= FEC_BD_LG;
|
||||
}
|
||||
addr = s->rx_descriptor;
|
||||
while (size > 0) {
|
||||
mcf_fec_read_bd(&bd, addr);
|
||||
if ((bd.flags & FEC_BD_E) == 0) {
|
||||
/* No descriptors available. Bail out. */
|
||||
/* FIXME: This is wrong. We should probably either save the
|
||||
remainder for when more RX buffers are available, or
|
||||
flag an error. */
|
||||
fprintf(stderr, "mcf_fec: Lost end of frame\n");
|
||||
break;
|
||||
}
|
||||
buf_len = (size <= s->emrbr) ? size: s->emrbr;
|
||||
bd.length = buf_len;
|
||||
size -= buf_len;
|
||||
DPRINTF("rx_bd %x length %d\n", addr, bd.length);
|
||||
/* The last 4 bytes are the CRC. */
|
||||
if (size < 4)
|
||||
buf_len += size - 4;
|
||||
buf_addr = bd.data;
|
||||
cpu_physical_memory_write(buf_addr, buf, buf_len);
|
||||
buf += buf_len;
|
||||
if (size < 4) {
|
||||
cpu_physical_memory_write(buf_addr + buf_len, crc_ptr, 4 - size);
|
||||
crc_ptr += 4 - size;
|
||||
}
|
||||
bd.flags &= ~FEC_BD_E;
|
||||
if (size == 0) {
|
||||
/* Last buffer in frame. */
|
||||
bd.flags |= flags | FEC_BD_L;
|
||||
DPRINTF("rx frame flags %04x\n", bd.flags);
|
||||
s->eir |= FEC_INT_RXF;
|
||||
} else {
|
||||
s->eir |= FEC_INT_RXB;
|
||||
}
|
||||
mcf_fec_write_bd(&bd, addr);
|
||||
/* Advance to the next descriptor. */
|
||||
if ((bd.flags & FEC_BD_W) != 0) {
|
||||
addr = s->erdsr;
|
||||
} else {
|
||||
addr += 8;
|
||||
}
|
||||
}
|
||||
s->rx_descriptor = addr;
|
||||
mcf_fec_enable_rx(s);
|
||||
mcf_fec_update(s);
|
||||
return size;
|
||||
}
|
||||
|
||||
static const MemoryRegionOps mcf_fec_ops = {
|
||||
.read = mcf_fec_read,
|
||||
.write = mcf_fec_write,
|
||||
.endianness = DEVICE_NATIVE_ENDIAN,
|
||||
};
|
||||
|
||||
static void mcf_fec_cleanup(NetClientState *nc)
|
||||
{
|
||||
mcf_fec_state *s = qemu_get_nic_opaque(nc);
|
||||
|
||||
memory_region_del_subregion(s->sysmem, &s->iomem);
|
||||
memory_region_destroy(&s->iomem);
|
||||
|
||||
g_free(s);
|
||||
}
|
||||
|
||||
static NetClientInfo net_mcf_fec_info = {
|
||||
.type = NET_CLIENT_OPTIONS_KIND_NIC,
|
||||
.size = sizeof(NICState),
|
||||
.can_receive = mcf_fec_can_receive,
|
||||
.receive = mcf_fec_receive,
|
||||
.cleanup = mcf_fec_cleanup,
|
||||
};
|
||||
|
||||
void mcf_fec_init(MemoryRegion *sysmem, NICInfo *nd,
|
||||
hwaddr base, qemu_irq *irq)
|
||||
{
|
||||
mcf_fec_state *s;
|
||||
|
||||
qemu_check_nic_model(nd, "mcf_fec");
|
||||
|
||||
s = (mcf_fec_state *)g_malloc0(sizeof(mcf_fec_state));
|
||||
s->sysmem = sysmem;
|
||||
s->irq = irq;
|
||||
|
||||
memory_region_init_io(&s->iomem, &mcf_fec_ops, s, "fec", 0x400);
|
||||
memory_region_add_subregion(sysmem, base, &s->iomem);
|
||||
|
||||
s->conf.macaddr = nd->macaddr;
|
||||
s->conf.peers.ncs[0] = nd->netdev;
|
||||
|
||||
s->nic = qemu_new_nic(&net_mcf_fec_info, &s->conf, nd->model, nd->name, s);
|
||||
|
||||
qemu_format_nic_info_str(qemu_get_queue(s->nic), s->conf.macaddr.a);
|
||||
}
|
547
hw/net/milkymist-minimac2.c
Normal file
547
hw/net/milkymist-minimac2.c
Normal file
|
@ -0,0 +1,547 @@
|
|||
/*
|
||||
* QEMU model of the Milkymist minimac2 block.
|
||||
*
|
||||
* Copyright (c) 2011 Michael Walle <michael@walle.cc>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*
|
||||
* Specification available at:
|
||||
* not available yet
|
||||
*
|
||||
*/
|
||||
|
||||
#include "hw/hw.h"
|
||||
#include "hw/sysbus.h"
|
||||
#include "trace.h"
|
||||
#include "net/net.h"
|
||||
#include "qemu/error-report.h"
|
||||
#include "hw/qdev-addr.h"
|
||||
|
||||
#include <zlib.h>
|
||||
|
||||
enum {
|
||||
R_SETUP = 0,
|
||||
R_MDIO,
|
||||
R_STATE0,
|
||||
R_COUNT0,
|
||||
R_STATE1,
|
||||
R_COUNT1,
|
||||
R_TXCOUNT,
|
||||
R_MAX
|
||||
};
|
||||
|
||||
enum {
|
||||
SETUP_PHY_RST = (1<<0),
|
||||
};
|
||||
|
||||
enum {
|
||||
MDIO_DO = (1<<0),
|
||||
MDIO_DI = (1<<1),
|
||||
MDIO_OE = (1<<2),
|
||||
MDIO_CLK = (1<<3),
|
||||
};
|
||||
|
||||
enum {
|
||||
STATE_EMPTY = 0,
|
||||
STATE_LOADED = 1,
|
||||
STATE_PENDING = 2,
|
||||
};
|
||||
|
||||
enum {
|
||||
MDIO_OP_WRITE = 1,
|
||||
MDIO_OP_READ = 2,
|
||||
};
|
||||
|
||||
enum mdio_state {
|
||||
MDIO_STATE_IDLE,
|
||||
MDIO_STATE_READING,
|
||||
MDIO_STATE_WRITING,
|
||||
};
|
||||
|
||||
enum {
|
||||
R_PHY_ID1 = 2,
|
||||
R_PHY_ID2 = 3,
|
||||
R_PHY_MAX = 32
|
||||
};
|
||||
|
||||
#define MINIMAC2_MTU 1530
|
||||
#define MINIMAC2_BUFFER_SIZE 2048
|
||||
|
||||
struct MilkymistMinimac2MdioState {
|
||||
int last_clk;
|
||||
int count;
|
||||
uint32_t data;
|
||||
uint16_t data_out;
|
||||
int state;
|
||||
|
||||
uint8_t phy_addr;
|
||||
uint8_t reg_addr;
|
||||
};
|
||||
typedef struct MilkymistMinimac2MdioState MilkymistMinimac2MdioState;
|
||||
|
||||
struct MilkymistMinimac2State {
|
||||
SysBusDevice busdev;
|
||||
NICState *nic;
|
||||
NICConf conf;
|
||||
char *phy_model;
|
||||
MemoryRegion buffers;
|
||||
MemoryRegion regs_region;
|
||||
|
||||
qemu_irq rx_irq;
|
||||
qemu_irq tx_irq;
|
||||
|
||||
uint32_t regs[R_MAX];
|
||||
|
||||
MilkymistMinimac2MdioState mdio;
|
||||
|
||||
uint16_t phy_regs[R_PHY_MAX];
|
||||
|
||||
uint8_t *rx0_buf;
|
||||
uint8_t *rx1_buf;
|
||||
uint8_t *tx_buf;
|
||||
};
|
||||
typedef struct MilkymistMinimac2State MilkymistMinimac2State;
|
||||
|
||||
static const uint8_t preamble_sfd[] = {
|
||||
0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0xd5
|
||||
};
|
||||
|
||||
static void minimac2_mdio_write_reg(MilkymistMinimac2State *s,
|
||||
uint8_t phy_addr, uint8_t reg_addr, uint16_t value)
|
||||
{
|
||||
trace_milkymist_minimac2_mdio_write(phy_addr, reg_addr, value);
|
||||
|
||||
/* nop */
|
||||
}
|
||||
|
||||
static uint16_t minimac2_mdio_read_reg(MilkymistMinimac2State *s,
|
||||
uint8_t phy_addr, uint8_t reg_addr)
|
||||
{
|
||||
uint16_t r = s->phy_regs[reg_addr];
|
||||
|
||||
trace_milkymist_minimac2_mdio_read(phy_addr, reg_addr, r);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
static void minimac2_update_mdio(MilkymistMinimac2State *s)
|
||||
{
|
||||
MilkymistMinimac2MdioState *m = &s->mdio;
|
||||
|
||||
/* detect rising clk edge */
|
||||
if (m->last_clk == 0 && (s->regs[R_MDIO] & MDIO_CLK)) {
|
||||
/* shift data in */
|
||||
int bit = ((s->regs[R_MDIO] & MDIO_DO)
|
||||
&& (s->regs[R_MDIO] & MDIO_OE)) ? 1 : 0;
|
||||
m->data = (m->data << 1) | bit;
|
||||
|
||||
/* check for sync */
|
||||
if (m->data == 0xffffffff) {
|
||||
m->count = 32;
|
||||
}
|
||||
|
||||
if (m->count == 16) {
|
||||
uint8_t start = (m->data >> 14) & 0x3;
|
||||
uint8_t op = (m->data >> 12) & 0x3;
|
||||
uint8_t ta = (m->data) & 0x3;
|
||||
|
||||
if (start == 1 && op == MDIO_OP_WRITE && ta == 2) {
|
||||
m->state = MDIO_STATE_WRITING;
|
||||
} else if (start == 1 && op == MDIO_OP_READ && (ta & 1) == 0) {
|
||||
m->state = MDIO_STATE_READING;
|
||||
} else {
|
||||
m->state = MDIO_STATE_IDLE;
|
||||
}
|
||||
|
||||
if (m->state != MDIO_STATE_IDLE) {
|
||||
m->phy_addr = (m->data >> 7) & 0x1f;
|
||||
m->reg_addr = (m->data >> 2) & 0x1f;
|
||||
}
|
||||
|
||||
if (m->state == MDIO_STATE_READING) {
|
||||
m->data_out = minimac2_mdio_read_reg(s, m->phy_addr,
|
||||
m->reg_addr);
|
||||
}
|
||||
}
|
||||
|
||||
if (m->count < 16 && m->state == MDIO_STATE_READING) {
|
||||
int bit = (m->data_out & 0x8000) ? 1 : 0;
|
||||
m->data_out <<= 1;
|
||||
|
||||
if (bit) {
|
||||
s->regs[R_MDIO] |= MDIO_DI;
|
||||
} else {
|
||||
s->regs[R_MDIO] &= ~MDIO_DI;
|
||||
}
|
||||
}
|
||||
|
||||
if (m->count == 0 && m->state) {
|
||||
if (m->state == MDIO_STATE_WRITING) {
|
||||
uint16_t data = m->data & 0xffff;
|
||||
minimac2_mdio_write_reg(s, m->phy_addr, m->reg_addr, data);
|
||||
}
|
||||
m->state = MDIO_STATE_IDLE;
|
||||
}
|
||||
m->count--;
|
||||
}
|
||||
|
||||
m->last_clk = (s->regs[R_MDIO] & MDIO_CLK) ? 1 : 0;
|
||||
}
|
||||
|
||||
static size_t assemble_frame(uint8_t *buf, size_t size,
|
||||
const uint8_t *payload, size_t payload_size)
|
||||
{
|
||||
uint32_t crc;
|
||||
|
||||
if (size < payload_size + 12) {
|
||||
error_report("milkymist_minimac2: received too big ethernet frame");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* prepend preamble and sfd */
|
||||
memcpy(buf, preamble_sfd, 8);
|
||||
|
||||
/* now copy the payload */
|
||||
memcpy(buf + 8, payload, payload_size);
|
||||
|
||||
/* pad frame if needed */
|
||||
if (payload_size < 60) {
|
||||
memset(buf + payload_size + 8, 0, 60 - payload_size);
|
||||
payload_size = 60;
|
||||
}
|
||||
|
||||
/* append fcs */
|
||||
crc = cpu_to_le32(crc32(0, buf + 8, payload_size));
|
||||
memcpy(buf + payload_size + 8, &crc, 4);
|
||||
|
||||
return payload_size + 12;
|
||||
}
|
||||
|
||||
static void minimac2_tx(MilkymistMinimac2State *s)
|
||||
{
|
||||
uint32_t txcount = s->regs[R_TXCOUNT];
|
||||
uint8_t *buf = s->tx_buf;
|
||||
|
||||
if (txcount < 64) {
|
||||
error_report("milkymist_minimac2: ethernet frame too small (%u < %u)",
|
||||
txcount, 64);
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (txcount > MINIMAC2_MTU) {
|
||||
error_report("milkymist_minimac2: MTU exceeded (%u > %u)",
|
||||
txcount, MINIMAC2_MTU);
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (memcmp(buf, preamble_sfd, 8) != 0) {
|
||||
error_report("milkymist_minimac2: frame doesn't contain the preamble "
|
||||
"and/or the SFD (%02x %02x %02x %02x %02x %02x %02x %02x)",
|
||||
buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7]);
|
||||
goto err;
|
||||
}
|
||||
|
||||
trace_milkymist_minimac2_tx_frame(txcount - 12);
|
||||
|
||||
/* send packet, skipping preamble and sfd */
|
||||
qemu_send_packet_raw(qemu_get_queue(s->nic), buf + 8, txcount - 12);
|
||||
|
||||
s->regs[R_TXCOUNT] = 0;
|
||||
|
||||
err:
|
||||
trace_milkymist_minimac2_pulse_irq_tx();
|
||||
qemu_irq_pulse(s->tx_irq);
|
||||
}
|
||||
|
||||
static void update_rx_interrupt(MilkymistMinimac2State *s)
|
||||
{
|
||||
if (s->regs[R_STATE0] == STATE_PENDING
|
||||
|| s->regs[R_STATE1] == STATE_PENDING) {
|
||||
trace_milkymist_minimac2_raise_irq_rx();
|
||||
qemu_irq_raise(s->rx_irq);
|
||||
} else {
|
||||
trace_milkymist_minimac2_lower_irq_rx();
|
||||
qemu_irq_lower(s->rx_irq);
|
||||
}
|
||||
}
|
||||
|
||||
static ssize_t minimac2_rx(NetClientState *nc, const uint8_t *buf, size_t size)
|
||||
{
|
||||
MilkymistMinimac2State *s = qemu_get_nic_opaque(nc);
|
||||
|
||||
uint32_t r_count;
|
||||
uint32_t r_state;
|
||||
uint8_t *rx_buf;
|
||||
|
||||
size_t frame_size;
|
||||
|
||||
trace_milkymist_minimac2_rx_frame(buf, size);
|
||||
|
||||
/* choose appropriate slot */
|
||||
if (s->regs[R_STATE0] == STATE_LOADED) {
|
||||
r_count = R_COUNT0;
|
||||
r_state = R_STATE0;
|
||||
rx_buf = s->rx0_buf;
|
||||
} else if (s->regs[R_STATE1] == STATE_LOADED) {
|
||||
r_count = R_COUNT1;
|
||||
r_state = R_STATE1;
|
||||
rx_buf = s->rx1_buf;
|
||||
} else {
|
||||
trace_milkymist_minimac2_drop_rx_frame(buf);
|
||||
return size;
|
||||
}
|
||||
|
||||
/* assemble frame */
|
||||
frame_size = assemble_frame(rx_buf, MINIMAC2_BUFFER_SIZE, buf, size);
|
||||
|
||||
if (frame_size == 0) {
|
||||
return size;
|
||||
}
|
||||
|
||||
trace_milkymist_minimac2_rx_transfer(rx_buf, frame_size);
|
||||
|
||||
/* update slot */
|
||||
s->regs[r_count] = frame_size;
|
||||
s->regs[r_state] = STATE_PENDING;
|
||||
|
||||
update_rx_interrupt(s);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static uint64_t
|
||||
minimac2_read(void *opaque, hwaddr addr, unsigned size)
|
||||
{
|
||||
MilkymistMinimac2State *s = opaque;
|
||||
uint32_t r = 0;
|
||||
|
||||
addr >>= 2;
|
||||
switch (addr) {
|
||||
case R_SETUP:
|
||||
case R_MDIO:
|
||||
case R_STATE0:
|
||||
case R_COUNT0:
|
||||
case R_STATE1:
|
||||
case R_COUNT1:
|
||||
case R_TXCOUNT:
|
||||
r = s->regs[addr];
|
||||
break;
|
||||
|
||||
default:
|
||||
error_report("milkymist_minimac2: read access to unknown register 0x"
|
||||
TARGET_FMT_plx, addr << 2);
|
||||
break;
|
||||
}
|
||||
|
||||
trace_milkymist_minimac2_memory_read(addr << 2, r);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
static void
|
||||
minimac2_write(void *opaque, hwaddr addr, uint64_t value,
|
||||
unsigned size)
|
||||
{
|
||||
MilkymistMinimac2State *s = opaque;
|
||||
|
||||
trace_milkymist_minimac2_memory_read(addr, value);
|
||||
|
||||
addr >>= 2;
|
||||
switch (addr) {
|
||||
case R_MDIO:
|
||||
{
|
||||
/* MDIO_DI is read only */
|
||||
int mdio_di = (s->regs[R_MDIO] & MDIO_DI);
|
||||
s->regs[R_MDIO] = value;
|
||||
if (mdio_di) {
|
||||
s->regs[R_MDIO] |= mdio_di;
|
||||
} else {
|
||||
s->regs[R_MDIO] &= ~mdio_di;
|
||||
}
|
||||
|
||||
minimac2_update_mdio(s);
|
||||
} break;
|
||||
case R_TXCOUNT:
|
||||
s->regs[addr] = value;
|
||||
if (value > 0) {
|
||||
minimac2_tx(s);
|
||||
}
|
||||
break;
|
||||
case R_STATE0:
|
||||
case R_STATE1:
|
||||
s->regs[addr] = value;
|
||||
update_rx_interrupt(s);
|
||||
break;
|
||||
case R_SETUP:
|
||||
case R_COUNT0:
|
||||
case R_COUNT1:
|
||||
s->regs[addr] = value;
|
||||
break;
|
||||
|
||||
default:
|
||||
error_report("milkymist_minimac2: write access to unknown register 0x"
|
||||
TARGET_FMT_plx, addr << 2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static const MemoryRegionOps minimac2_ops = {
|
||||
.read = minimac2_read,
|
||||
.write = minimac2_write,
|
||||
.valid = {
|
||||
.min_access_size = 4,
|
||||
.max_access_size = 4,
|
||||
},
|
||||
.endianness = DEVICE_NATIVE_ENDIAN,
|
||||
};
|
||||
|
||||
static int minimac2_can_rx(NetClientState *nc)
|
||||
{
|
||||
MilkymistMinimac2State *s = qemu_get_nic_opaque(nc);
|
||||
|
||||
if (s->regs[R_STATE0] == STATE_LOADED) {
|
||||
return 1;
|
||||
}
|
||||
if (s->regs[R_STATE1] == STATE_LOADED) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void minimac2_cleanup(NetClientState *nc)
|
||||
{
|
||||
MilkymistMinimac2State *s = qemu_get_nic_opaque(nc);
|
||||
|
||||
s->nic = NULL;
|
||||
}
|
||||
|
||||
static void milkymist_minimac2_reset(DeviceState *d)
|
||||
{
|
||||
MilkymistMinimac2State *s =
|
||||
container_of(d, MilkymistMinimac2State, busdev.qdev);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < R_MAX; i++) {
|
||||
s->regs[i] = 0;
|
||||
}
|
||||
for (i = 0; i < R_PHY_MAX; i++) {
|
||||
s->phy_regs[i] = 0;
|
||||
}
|
||||
|
||||
/* defaults */
|
||||
s->phy_regs[R_PHY_ID1] = 0x0022; /* Micrel KSZ8001L */
|
||||
s->phy_regs[R_PHY_ID2] = 0x161a;
|
||||
}
|
||||
|
||||
static NetClientInfo net_milkymist_minimac2_info = {
|
||||
.type = NET_CLIENT_OPTIONS_KIND_NIC,
|
||||
.size = sizeof(NICState),
|
||||
.can_receive = minimac2_can_rx,
|
||||
.receive = minimac2_rx,
|
||||
.cleanup = minimac2_cleanup,
|
||||
};
|
||||
|
||||
static int milkymist_minimac2_init(SysBusDevice *dev)
|
||||
{
|
||||
MilkymistMinimac2State *s = FROM_SYSBUS(typeof(*s), dev);
|
||||
size_t buffers_size = TARGET_PAGE_ALIGN(3 * MINIMAC2_BUFFER_SIZE);
|
||||
|
||||
sysbus_init_irq(dev, &s->rx_irq);
|
||||
sysbus_init_irq(dev, &s->tx_irq);
|
||||
|
||||
memory_region_init_io(&s->regs_region, &minimac2_ops, s,
|
||||
"milkymist-minimac2", R_MAX * 4);
|
||||
sysbus_init_mmio(dev, &s->regs_region);
|
||||
|
||||
/* register buffers memory */
|
||||
memory_region_init_ram(&s->buffers, "milkymist-minimac2.buffers",
|
||||
buffers_size);
|
||||
vmstate_register_ram_global(&s->buffers);
|
||||
s->rx0_buf = memory_region_get_ram_ptr(&s->buffers);
|
||||
s->rx1_buf = s->rx0_buf + MINIMAC2_BUFFER_SIZE;
|
||||
s->tx_buf = s->rx1_buf + MINIMAC2_BUFFER_SIZE;
|
||||
|
||||
sysbus_init_mmio(dev, &s->buffers);
|
||||
|
||||
qemu_macaddr_default_if_unset(&s->conf.macaddr);
|
||||
s->nic = qemu_new_nic(&net_milkymist_minimac2_info, &s->conf,
|
||||
object_get_typename(OBJECT(dev)), dev->qdev.id, s);
|
||||
qemu_format_nic_info_str(qemu_get_queue(s->nic), s->conf.macaddr.a);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const VMStateDescription vmstate_milkymist_minimac2_mdio = {
|
||||
.name = "milkymist-minimac2-mdio",
|
||||
.version_id = 1,
|
||||
.minimum_version_id = 1,
|
||||
.minimum_version_id_old = 1,
|
||||
.fields = (VMStateField[]) {
|
||||
VMSTATE_INT32(last_clk, MilkymistMinimac2MdioState),
|
||||
VMSTATE_INT32(count, MilkymistMinimac2MdioState),
|
||||
VMSTATE_UINT32(data, MilkymistMinimac2MdioState),
|
||||
VMSTATE_UINT16(data_out, MilkymistMinimac2MdioState),
|
||||
VMSTATE_INT32(state, MilkymistMinimac2MdioState),
|
||||
VMSTATE_UINT8(phy_addr, MilkymistMinimac2MdioState),
|
||||
VMSTATE_UINT8(reg_addr, MilkymistMinimac2MdioState),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
static const VMStateDescription vmstate_milkymist_minimac2 = {
|
||||
.name = "milkymist-minimac2",
|
||||
.version_id = 1,
|
||||
.minimum_version_id = 1,
|
||||
.minimum_version_id_old = 1,
|
||||
.fields = (VMStateField[]) {
|
||||
VMSTATE_UINT32_ARRAY(regs, MilkymistMinimac2State, R_MAX),
|
||||
VMSTATE_UINT16_ARRAY(phy_regs, MilkymistMinimac2State, R_PHY_MAX),
|
||||
VMSTATE_STRUCT(mdio, MilkymistMinimac2State, 0,
|
||||
vmstate_milkymist_minimac2_mdio, MilkymistMinimac2MdioState),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
static Property milkymist_minimac2_properties[] = {
|
||||
DEFINE_NIC_PROPERTIES(MilkymistMinimac2State, conf),
|
||||
DEFINE_PROP_STRING("phy_model", MilkymistMinimac2State, phy_model),
|
||||
DEFINE_PROP_END_OF_LIST(),
|
||||
};
|
||||
|
||||
static void milkymist_minimac2_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
|
||||
|
||||
k->init = milkymist_minimac2_init;
|
||||
dc->reset = milkymist_minimac2_reset;
|
||||
dc->vmsd = &vmstate_milkymist_minimac2;
|
||||
dc->props = milkymist_minimac2_properties;
|
||||
}
|
||||
|
||||
static const TypeInfo milkymist_minimac2_info = {
|
||||
.name = "milkymist-minimac2",
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(MilkymistMinimac2State),
|
||||
.class_init = milkymist_minimac2_class_init,
|
||||
};
|
||||
|
||||
static void milkymist_minimac2_register_types(void)
|
||||
{
|
||||
type_register_static(&milkymist_minimac2_info);
|
||||
}
|
||||
|
||||
type_init(milkymist_minimac2_register_types)
|
531
hw/net/spapr_llan.c
Normal file
531
hw/net/spapr_llan.c
Normal file
|
@ -0,0 +1,531 @@
|
|||
/*
|
||||
* QEMU PowerPC pSeries Logical Partition (aka sPAPR) hardware System Emulator
|
||||
*
|
||||
* PAPR Inter-VM Logical Lan, aka ibmveth
|
||||
*
|
||||
* Copyright (c) 2010,2011 David Gibson, IBM Corporation.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
#include "hw/hw.h"
|
||||
#include "net/net.h"
|
||||
#include "hw/qdev.h"
|
||||
#include "hw/ppc/spapr.h"
|
||||
#include "hw/ppc/spapr_vio.h"
|
||||
|
||||
#include <libfdt.h>
|
||||
|
||||
#define ETH_ALEN 6
|
||||
#define MAX_PACKET_SIZE 65536
|
||||
|
||||
/*#define DEBUG*/
|
||||
|
||||
#ifdef DEBUG
|
||||
#define dprintf(fmt...) do { fprintf(stderr, fmt); } while (0)
|
||||
#else
|
||||
#define dprintf(fmt...)
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Virtual LAN device
|
||||
*/
|
||||
|
||||
typedef uint64_t vlan_bd_t;
|
||||
|
||||
#define VLAN_BD_VALID 0x8000000000000000ULL
|
||||
#define VLAN_BD_TOGGLE 0x4000000000000000ULL
|
||||
#define VLAN_BD_NO_CSUM 0x0200000000000000ULL
|
||||
#define VLAN_BD_CSUM_GOOD 0x0100000000000000ULL
|
||||
#define VLAN_BD_LEN_MASK 0x00ffffff00000000ULL
|
||||
#define VLAN_BD_LEN(bd) (((bd) & VLAN_BD_LEN_MASK) >> 32)
|
||||
#define VLAN_BD_ADDR_MASK 0x00000000ffffffffULL
|
||||
#define VLAN_BD_ADDR(bd) ((bd) & VLAN_BD_ADDR_MASK)
|
||||
|
||||
#define VLAN_VALID_BD(addr, len) (VLAN_BD_VALID | \
|
||||
(((len) << 32) & VLAN_BD_LEN_MASK) | \
|
||||
(addr & VLAN_BD_ADDR_MASK))
|
||||
|
||||
#define VLAN_RXQC_TOGGLE 0x80
|
||||
#define VLAN_RXQC_VALID 0x40
|
||||
#define VLAN_RXQC_NO_CSUM 0x02
|
||||
#define VLAN_RXQC_CSUM_GOOD 0x01
|
||||
|
||||
#define VLAN_RQ_ALIGNMENT 16
|
||||
#define VLAN_RXQ_BD_OFF 0
|
||||
#define VLAN_FILTER_BD_OFF 8
|
||||
#define VLAN_RX_BDS_OFF 16
|
||||
#define VLAN_MAX_BUFS ((SPAPR_TCE_PAGE_SIZE - VLAN_RX_BDS_OFF) / 8)
|
||||
|
||||
typedef struct VIOsPAPRVLANDevice {
|
||||
VIOsPAPRDevice sdev;
|
||||
NICConf nicconf;
|
||||
NICState *nic;
|
||||
int isopen;
|
||||
target_ulong buf_list;
|
||||
int add_buf_ptr, use_buf_ptr, rx_bufs;
|
||||
target_ulong rxq_ptr;
|
||||
} VIOsPAPRVLANDevice;
|
||||
|
||||
static int spapr_vlan_can_receive(NetClientState *nc)
|
||||
{
|
||||
VIOsPAPRVLANDevice *dev = qemu_get_nic_opaque(nc);
|
||||
|
||||
return (dev->isopen && dev->rx_bufs > 0);
|
||||
}
|
||||
|
||||
static ssize_t spapr_vlan_receive(NetClientState *nc, const uint8_t *buf,
|
||||
size_t size)
|
||||
{
|
||||
VIOsPAPRDevice *sdev = qemu_get_nic_opaque(nc);
|
||||
VIOsPAPRVLANDevice *dev = (VIOsPAPRVLANDevice *)sdev;
|
||||
vlan_bd_t rxq_bd = vio_ldq(sdev, dev->buf_list + VLAN_RXQ_BD_OFF);
|
||||
vlan_bd_t bd;
|
||||
int buf_ptr = dev->use_buf_ptr;
|
||||
uint64_t handle;
|
||||
uint8_t control;
|
||||
|
||||
dprintf("spapr_vlan_receive() [%s] rx_bufs=%d\n", sdev->qdev.id,
|
||||
dev->rx_bufs);
|
||||
|
||||
if (!dev->isopen) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!dev->rx_bufs) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
do {
|
||||
buf_ptr += 8;
|
||||
if (buf_ptr >= SPAPR_TCE_PAGE_SIZE) {
|
||||
buf_ptr = VLAN_RX_BDS_OFF;
|
||||
}
|
||||
|
||||
bd = vio_ldq(sdev, dev->buf_list + buf_ptr);
|
||||
dprintf("use_buf_ptr=%d bd=0x%016llx\n",
|
||||
buf_ptr, (unsigned long long)bd);
|
||||
} while ((!(bd & VLAN_BD_VALID) || (VLAN_BD_LEN(bd) < (size + 8)))
|
||||
&& (buf_ptr != dev->use_buf_ptr));
|
||||
|
||||
if (!(bd & VLAN_BD_VALID) || (VLAN_BD_LEN(bd) < (size + 8))) {
|
||||
/* Failed to find a suitable buffer */
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Remove the buffer from the pool */
|
||||
dev->rx_bufs--;
|
||||
dev->use_buf_ptr = buf_ptr;
|
||||
vio_stq(sdev, dev->buf_list + dev->use_buf_ptr, 0);
|
||||
|
||||
dprintf("Found buffer: ptr=%d num=%d\n", dev->use_buf_ptr, dev->rx_bufs);
|
||||
|
||||
/* Transfer the packet data */
|
||||
if (spapr_vio_dma_write(sdev, VLAN_BD_ADDR(bd) + 8, buf, size) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
dprintf("spapr_vlan_receive: DMA write completed\n");
|
||||
|
||||
/* Update the receive queue */
|
||||
control = VLAN_RXQC_TOGGLE | VLAN_RXQC_VALID;
|
||||
if (rxq_bd & VLAN_BD_TOGGLE) {
|
||||
control ^= VLAN_RXQC_TOGGLE;
|
||||
}
|
||||
|
||||
handle = vio_ldq(sdev, VLAN_BD_ADDR(bd));
|
||||
vio_stq(sdev, VLAN_BD_ADDR(rxq_bd) + dev->rxq_ptr + 8, handle);
|
||||
vio_stl(sdev, VLAN_BD_ADDR(rxq_bd) + dev->rxq_ptr + 4, size);
|
||||
vio_sth(sdev, VLAN_BD_ADDR(rxq_bd) + dev->rxq_ptr + 2, 8);
|
||||
vio_stb(sdev, VLAN_BD_ADDR(rxq_bd) + dev->rxq_ptr, control);
|
||||
|
||||
dprintf("wrote rxq entry (ptr=0x%llx): 0x%016llx 0x%016llx\n",
|
||||
(unsigned long long)dev->rxq_ptr,
|
||||
(unsigned long long)vio_ldq(sdev, VLAN_BD_ADDR(rxq_bd) +
|
||||
dev->rxq_ptr),
|
||||
(unsigned long long)vio_ldq(sdev, VLAN_BD_ADDR(rxq_bd) +
|
||||
dev->rxq_ptr + 8));
|
||||
|
||||
dev->rxq_ptr += 16;
|
||||
if (dev->rxq_ptr >= VLAN_BD_LEN(rxq_bd)) {
|
||||
dev->rxq_ptr = 0;
|
||||
vio_stq(sdev, dev->buf_list + VLAN_RXQ_BD_OFF, rxq_bd ^ VLAN_BD_TOGGLE);
|
||||
}
|
||||
|
||||
if (sdev->signal_state & 1) {
|
||||
qemu_irq_pulse(spapr_vio_qirq(sdev));
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static void spapr_vlan_cleanup(NetClientState *nc)
|
||||
{
|
||||
VIOsPAPRVLANDevice *dev = qemu_get_nic_opaque(nc);
|
||||
|
||||
dev->nic = NULL;
|
||||
}
|
||||
|
||||
static NetClientInfo net_spapr_vlan_info = {
|
||||
.type = NET_CLIENT_OPTIONS_KIND_NIC,
|
||||
.size = sizeof(NICState),
|
||||
.can_receive = spapr_vlan_can_receive,
|
||||
.receive = spapr_vlan_receive,
|
||||
.cleanup = spapr_vlan_cleanup,
|
||||
};
|
||||
|
||||
static void spapr_vlan_reset(VIOsPAPRDevice *sdev)
|
||||
{
|
||||
VIOsPAPRVLANDevice *dev = DO_UPCAST(VIOsPAPRVLANDevice, sdev, sdev);
|
||||
|
||||
dev->buf_list = 0;
|
||||
dev->rx_bufs = 0;
|
||||
dev->isopen = 0;
|
||||
}
|
||||
|
||||
static int spapr_vlan_init(VIOsPAPRDevice *sdev)
|
||||
{
|
||||
VIOsPAPRVLANDevice *dev = (VIOsPAPRVLANDevice *)sdev;
|
||||
|
||||
qemu_macaddr_default_if_unset(&dev->nicconf.macaddr);
|
||||
|
||||
dev->nic = qemu_new_nic(&net_spapr_vlan_info, &dev->nicconf,
|
||||
object_get_typename(OBJECT(sdev)), sdev->qdev.id, dev);
|
||||
qemu_format_nic_info_str(qemu_get_queue(dev->nic), dev->nicconf.macaddr.a);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void spapr_vlan_create(VIOsPAPRBus *bus, NICInfo *nd)
|
||||
{
|
||||
DeviceState *dev;
|
||||
|
||||
dev = qdev_create(&bus->bus, "spapr-vlan");
|
||||
|
||||
qdev_set_nic_properties(dev, nd);
|
||||
|
||||
qdev_init_nofail(dev);
|
||||
}
|
||||
|
||||
static int spapr_vlan_devnode(VIOsPAPRDevice *dev, void *fdt, int node_off)
|
||||
{
|
||||
VIOsPAPRVLANDevice *vdev = (VIOsPAPRVLANDevice *)dev;
|
||||
uint8_t padded_mac[8] = {0, 0};
|
||||
int ret;
|
||||
|
||||
/* Some old phyp versions give the mac address in an 8-byte
|
||||
* property. The kernel driver has an insane workaround for this;
|
||||
* rather than doing the obvious thing and checking the property
|
||||
* length, it checks whether the first byte has 0b10 in the low
|
||||
* bits. If a correct 6-byte property has a different first byte
|
||||
* the kernel will get the wrong mac address, overrunning its
|
||||
* buffer in the process (read only, thank goodness).
|
||||
*
|
||||
* Here we workaround the kernel workaround by always supplying an
|
||||
* 8-byte property, with the mac address in the last six bytes */
|
||||
memcpy(&padded_mac[2], &vdev->nicconf.macaddr, ETH_ALEN);
|
||||
ret = fdt_setprop(fdt, node_off, "local-mac-address",
|
||||
padded_mac, sizeof(padded_mac));
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = fdt_setprop_cell(fdt, node_off, "ibm,mac-address-filters", 0);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int check_bd(VIOsPAPRVLANDevice *dev, vlan_bd_t bd,
|
||||
target_ulong alignment)
|
||||
{
|
||||
if ((VLAN_BD_ADDR(bd) % alignment)
|
||||
|| (VLAN_BD_LEN(bd) % alignment)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!spapr_vio_dma_valid(&dev->sdev, VLAN_BD_ADDR(bd),
|
||||
VLAN_BD_LEN(bd), DMA_DIRECTION_FROM_DEVICE)
|
||||
|| !spapr_vio_dma_valid(&dev->sdev, VLAN_BD_ADDR(bd),
|
||||
VLAN_BD_LEN(bd), DMA_DIRECTION_TO_DEVICE)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static target_ulong h_register_logical_lan(PowerPCCPU *cpu,
|
||||
sPAPREnvironment *spapr,
|
||||
target_ulong opcode,
|
||||
target_ulong *args)
|
||||
{
|
||||
target_ulong reg = args[0];
|
||||
target_ulong buf_list = args[1];
|
||||
target_ulong rec_queue = args[2];
|
||||
target_ulong filter_list = args[3];
|
||||
VIOsPAPRDevice *sdev = spapr_vio_find_by_reg(spapr->vio_bus, reg);
|
||||
VIOsPAPRVLANDevice *dev = (VIOsPAPRVLANDevice *)sdev;
|
||||
vlan_bd_t filter_list_bd;
|
||||
|
||||
if (!dev) {
|
||||
return H_PARAMETER;
|
||||
}
|
||||
|
||||
if (dev->isopen) {
|
||||
hcall_dprintf("H_REGISTER_LOGICAL_LAN called twice without "
|
||||
"H_FREE_LOGICAL_LAN\n");
|
||||
return H_RESOURCE;
|
||||
}
|
||||
|
||||
if (check_bd(dev, VLAN_VALID_BD(buf_list, SPAPR_TCE_PAGE_SIZE),
|
||||
SPAPR_TCE_PAGE_SIZE) < 0) {
|
||||
hcall_dprintf("Bad buf_list 0x" TARGET_FMT_lx "\n", buf_list);
|
||||
return H_PARAMETER;
|
||||
}
|
||||
|
||||
filter_list_bd = VLAN_VALID_BD(filter_list, SPAPR_TCE_PAGE_SIZE);
|
||||
if (check_bd(dev, filter_list_bd, SPAPR_TCE_PAGE_SIZE) < 0) {
|
||||
hcall_dprintf("Bad filter_list 0x" TARGET_FMT_lx "\n", filter_list);
|
||||
return H_PARAMETER;
|
||||
}
|
||||
|
||||
if (!(rec_queue & VLAN_BD_VALID)
|
||||
|| (check_bd(dev, rec_queue, VLAN_RQ_ALIGNMENT) < 0)) {
|
||||
hcall_dprintf("Bad receive queue\n");
|
||||
return H_PARAMETER;
|
||||
}
|
||||
|
||||
dev->buf_list = buf_list;
|
||||
sdev->signal_state = 0;
|
||||
|
||||
rec_queue &= ~VLAN_BD_TOGGLE;
|
||||
|
||||
/* Initialize the buffer list */
|
||||
vio_stq(sdev, buf_list, rec_queue);
|
||||
vio_stq(sdev, buf_list + 8, filter_list_bd);
|
||||
spapr_vio_dma_set(sdev, buf_list + VLAN_RX_BDS_OFF, 0,
|
||||
SPAPR_TCE_PAGE_SIZE - VLAN_RX_BDS_OFF);
|
||||
dev->add_buf_ptr = VLAN_RX_BDS_OFF - 8;
|
||||
dev->use_buf_ptr = VLAN_RX_BDS_OFF - 8;
|
||||
dev->rx_bufs = 0;
|
||||
dev->rxq_ptr = 0;
|
||||
|
||||
/* Initialize the receive queue */
|
||||
spapr_vio_dma_set(sdev, VLAN_BD_ADDR(rec_queue), 0, VLAN_BD_LEN(rec_queue));
|
||||
|
||||
dev->isopen = 1;
|
||||
return H_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
static target_ulong h_free_logical_lan(PowerPCCPU *cpu, sPAPREnvironment *spapr,
|
||||
target_ulong opcode, target_ulong *args)
|
||||
{
|
||||
target_ulong reg = args[0];
|
||||
VIOsPAPRDevice *sdev = spapr_vio_find_by_reg(spapr->vio_bus, reg);
|
||||
VIOsPAPRVLANDevice *dev = (VIOsPAPRVLANDevice *)sdev;
|
||||
|
||||
if (!dev) {
|
||||
return H_PARAMETER;
|
||||
}
|
||||
|
||||
if (!dev->isopen) {
|
||||
hcall_dprintf("H_FREE_LOGICAL_LAN called without "
|
||||
"H_REGISTER_LOGICAL_LAN\n");
|
||||
return H_RESOURCE;
|
||||
}
|
||||
|
||||
spapr_vlan_reset(sdev);
|
||||
return H_SUCCESS;
|
||||
}
|
||||
|
||||
static target_ulong h_add_logical_lan_buffer(PowerPCCPU *cpu,
|
||||
sPAPREnvironment *spapr,
|
||||
target_ulong opcode,
|
||||
target_ulong *args)
|
||||
{
|
||||
target_ulong reg = args[0];
|
||||
target_ulong buf = args[1];
|
||||
VIOsPAPRDevice *sdev = spapr_vio_find_by_reg(spapr->vio_bus, reg);
|
||||
VIOsPAPRVLANDevice *dev = (VIOsPAPRVLANDevice *)sdev;
|
||||
vlan_bd_t bd;
|
||||
|
||||
dprintf("H_ADD_LOGICAL_LAN_BUFFER(0x" TARGET_FMT_lx
|
||||
", 0x" TARGET_FMT_lx ")\n", reg, buf);
|
||||
|
||||
if (!sdev) {
|
||||
hcall_dprintf("Bad device\n");
|
||||
return H_PARAMETER;
|
||||
}
|
||||
|
||||
if ((check_bd(dev, buf, 4) < 0)
|
||||
|| (VLAN_BD_LEN(buf) < 16)) {
|
||||
hcall_dprintf("Bad buffer enqueued\n");
|
||||
return H_PARAMETER;
|
||||
}
|
||||
|
||||
if (!dev->isopen || dev->rx_bufs >= VLAN_MAX_BUFS) {
|
||||
return H_RESOURCE;
|
||||
}
|
||||
|
||||
do {
|
||||
dev->add_buf_ptr += 8;
|
||||
if (dev->add_buf_ptr >= SPAPR_TCE_PAGE_SIZE) {
|
||||
dev->add_buf_ptr = VLAN_RX_BDS_OFF;
|
||||
}
|
||||
|
||||
bd = vio_ldq(sdev, dev->buf_list + dev->add_buf_ptr);
|
||||
} while (bd & VLAN_BD_VALID);
|
||||
|
||||
vio_stq(sdev, dev->buf_list + dev->add_buf_ptr, buf);
|
||||
|
||||
dev->rx_bufs++;
|
||||
|
||||
dprintf("h_add_logical_lan_buffer(): Added buf ptr=%d rx_bufs=%d"
|
||||
" bd=0x%016llx\n", dev->add_buf_ptr, dev->rx_bufs,
|
||||
(unsigned long long)buf);
|
||||
|
||||
return H_SUCCESS;
|
||||
}
|
||||
|
||||
static target_ulong h_send_logical_lan(PowerPCCPU *cpu, sPAPREnvironment *spapr,
|
||||
target_ulong opcode, target_ulong *args)
|
||||
{
|
||||
target_ulong reg = args[0];
|
||||
target_ulong *bufs = args + 1;
|
||||
target_ulong continue_token = args[7];
|
||||
VIOsPAPRDevice *sdev = spapr_vio_find_by_reg(spapr->vio_bus, reg);
|
||||
VIOsPAPRVLANDevice *dev = (VIOsPAPRVLANDevice *)sdev;
|
||||
unsigned total_len;
|
||||
uint8_t *lbuf, *p;
|
||||
int i, nbufs;
|
||||
int ret;
|
||||
|
||||
dprintf("H_SEND_LOGICAL_LAN(0x" TARGET_FMT_lx ", <bufs>, 0x"
|
||||
TARGET_FMT_lx ")\n", reg, continue_token);
|
||||
|
||||
if (!sdev) {
|
||||
return H_PARAMETER;
|
||||
}
|
||||
|
||||
dprintf("rxbufs = %d\n", dev->rx_bufs);
|
||||
|
||||
if (!dev->isopen) {
|
||||
return H_DROPPED;
|
||||
}
|
||||
|
||||
if (continue_token) {
|
||||
return H_HARDWARE; /* FIXME actually handle this */
|
||||
}
|
||||
|
||||
total_len = 0;
|
||||
for (i = 0; i < 6; i++) {
|
||||
dprintf(" buf desc: 0x" TARGET_FMT_lx "\n", bufs[i]);
|
||||
if (!(bufs[i] & VLAN_BD_VALID)) {
|
||||
break;
|
||||
}
|
||||
total_len += VLAN_BD_LEN(bufs[i]);
|
||||
}
|
||||
|
||||
nbufs = i;
|
||||
dprintf("h_send_logical_lan() %d buffers, total length 0x%x\n",
|
||||
nbufs, total_len);
|
||||
|
||||
if (total_len == 0) {
|
||||
return H_SUCCESS;
|
||||
}
|
||||
|
||||
if (total_len > MAX_PACKET_SIZE) {
|
||||
/* Don't let the guest force too large an allocation */
|
||||
return H_RESOURCE;
|
||||
}
|
||||
|
||||
lbuf = alloca(total_len);
|
||||
p = lbuf;
|
||||
for (i = 0; i < nbufs; i++) {
|
||||
ret = spapr_vio_dma_read(sdev, VLAN_BD_ADDR(bufs[i]),
|
||||
p, VLAN_BD_LEN(bufs[i]));
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
p += VLAN_BD_LEN(bufs[i]);
|
||||
}
|
||||
|
||||
qemu_send_packet(qemu_get_queue(dev->nic), lbuf, total_len);
|
||||
|
||||
return H_SUCCESS;
|
||||
}
|
||||
|
||||
static target_ulong h_multicast_ctrl(PowerPCCPU *cpu, sPAPREnvironment *spapr,
|
||||
target_ulong opcode, target_ulong *args)
|
||||
{
|
||||
target_ulong reg = args[0];
|
||||
VIOsPAPRDevice *dev = spapr_vio_find_by_reg(spapr->vio_bus, reg);
|
||||
|
||||
if (!dev) {
|
||||
return H_PARAMETER;
|
||||
}
|
||||
|
||||
return H_SUCCESS;
|
||||
}
|
||||
|
||||
static Property spapr_vlan_properties[] = {
|
||||
DEFINE_SPAPR_PROPERTIES(VIOsPAPRVLANDevice, sdev),
|
||||
DEFINE_NIC_PROPERTIES(VIOsPAPRVLANDevice, nicconf),
|
||||
DEFINE_PROP_END_OF_LIST(),
|
||||
};
|
||||
|
||||
static void spapr_vlan_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
VIOsPAPRDeviceClass *k = VIO_SPAPR_DEVICE_CLASS(klass);
|
||||
|
||||
k->init = spapr_vlan_init;
|
||||
k->reset = spapr_vlan_reset;
|
||||
k->devnode = spapr_vlan_devnode;
|
||||
k->dt_name = "l-lan";
|
||||
k->dt_type = "network";
|
||||
k->dt_compatible = "IBM,l-lan";
|
||||
k->signal_mask = 0x1;
|
||||
dc->props = spapr_vlan_properties;
|
||||
k->rtce_window_size = 0x10000000;
|
||||
}
|
||||
|
||||
static const TypeInfo spapr_vlan_info = {
|
||||
.name = "spapr-vlan",
|
||||
.parent = TYPE_VIO_SPAPR_DEVICE,
|
||||
.instance_size = sizeof(VIOsPAPRVLANDevice),
|
||||
.class_init = spapr_vlan_class_init,
|
||||
};
|
||||
|
||||
static void spapr_vlan_register_types(void)
|
||||
{
|
||||
spapr_register_hypercall(H_REGISTER_LOGICAL_LAN, h_register_logical_lan);
|
||||
spapr_register_hypercall(H_FREE_LOGICAL_LAN, h_free_logical_lan);
|
||||
spapr_register_hypercall(H_SEND_LOGICAL_LAN, h_send_logical_lan);
|
||||
spapr_register_hypercall(H_ADD_LOGICAL_LAN_BUFFER,
|
||||
h_add_logical_lan_buffer);
|
||||
spapr_register_hypercall(H_MULTICAST_CTRL, h_multicast_ctrl);
|
||||
type_register_static(&spapr_vlan_info);
|
||||
}
|
||||
|
||||
type_init(spapr_vlan_register_types)
|
450
hw/net/stellaris_enet.c
Normal file
450
hw/net/stellaris_enet.c
Normal file
|
@ -0,0 +1,450 @@
|
|||
/*
|
||||
* Luminary Micro Stellaris Ethernet Controller
|
||||
*
|
||||
* Copyright (c) 2007 CodeSourcery.
|
||||
* Written by Paul Brook
|
||||
*
|
||||
* This code is licensed under the GPL.
|
||||
*/
|
||||
#include "hw/sysbus.h"
|
||||
#include "net/net.h"
|
||||
#include <zlib.h>
|
||||
|
||||
//#define DEBUG_STELLARIS_ENET 1
|
||||
|
||||
#ifdef DEBUG_STELLARIS_ENET
|
||||
#define DPRINTF(fmt, ...) \
|
||||
do { printf("stellaris_enet: " fmt , ## __VA_ARGS__); } while (0)
|
||||
#define BADF(fmt, ...) \
|
||||
do { fprintf(stderr, "stellaris_enet: error: " fmt , ## __VA_ARGS__); exit(1);} while (0)
|
||||
#else
|
||||
#define DPRINTF(fmt, ...) do {} while(0)
|
||||
#define BADF(fmt, ...) \
|
||||
do { fprintf(stderr, "stellaris_enet: error: " fmt , ## __VA_ARGS__);} while (0)
|
||||
#endif
|
||||
|
||||
#define SE_INT_RX 0x01
|
||||
#define SE_INT_TXER 0x02
|
||||
#define SE_INT_TXEMP 0x04
|
||||
#define SE_INT_FOV 0x08
|
||||
#define SE_INT_RXER 0x10
|
||||
#define SE_INT_MD 0x20
|
||||
#define SE_INT_PHY 0x40
|
||||
|
||||
#define SE_RCTL_RXEN 0x01
|
||||
#define SE_RCTL_AMUL 0x02
|
||||
#define SE_RCTL_PRMS 0x04
|
||||
#define SE_RCTL_BADCRC 0x08
|
||||
#define SE_RCTL_RSTFIFO 0x10
|
||||
|
||||
#define SE_TCTL_TXEN 0x01
|
||||
#define SE_TCTL_PADEN 0x02
|
||||
#define SE_TCTL_CRC 0x04
|
||||
#define SE_TCTL_DUPLEX 0x08
|
||||
|
||||
typedef struct {
|
||||
SysBusDevice busdev;
|
||||
uint32_t ris;
|
||||
uint32_t im;
|
||||
uint32_t rctl;
|
||||
uint32_t tctl;
|
||||
uint32_t thr;
|
||||
uint32_t mctl;
|
||||
uint32_t mdv;
|
||||
uint32_t mtxd;
|
||||
uint32_t mrxd;
|
||||
uint32_t np;
|
||||
int tx_frame_len;
|
||||
int tx_fifo_len;
|
||||
uint8_t tx_fifo[2048];
|
||||
/* Real hardware has a 2k fifo, which works out to be at most 31 packets.
|
||||
We implement a full 31 packet fifo. */
|
||||
struct {
|
||||
uint8_t data[2048];
|
||||
int len;
|
||||
} rx[31];
|
||||
uint8_t *rx_fifo;
|
||||
int rx_fifo_len;
|
||||
int next_packet;
|
||||
NICState *nic;
|
||||
NICConf conf;
|
||||
qemu_irq irq;
|
||||
MemoryRegion mmio;
|
||||
} stellaris_enet_state;
|
||||
|
||||
static void stellaris_enet_update(stellaris_enet_state *s)
|
||||
{
|
||||
qemu_set_irq(s->irq, (s->ris & s->im) != 0);
|
||||
}
|
||||
|
||||
/* TODO: Implement MAC address filtering. */
|
||||
static ssize_t stellaris_enet_receive(NetClientState *nc, const uint8_t *buf, size_t size)
|
||||
{
|
||||
stellaris_enet_state *s = qemu_get_nic_opaque(nc);
|
||||
int n;
|
||||
uint8_t *p;
|
||||
uint32_t crc;
|
||||
|
||||
if ((s->rctl & SE_RCTL_RXEN) == 0)
|
||||
return -1;
|
||||
if (s->np >= 31) {
|
||||
DPRINTF("Packet dropped\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
DPRINTF("Received packet len=%d\n", size);
|
||||
n = s->next_packet + s->np;
|
||||
if (n >= 31)
|
||||
n -= 31;
|
||||
s->np++;
|
||||
|
||||
s->rx[n].len = size + 6;
|
||||
p = s->rx[n].data;
|
||||
*(p++) = (size + 6);
|
||||
*(p++) = (size + 6) >> 8;
|
||||
memcpy (p, buf, size);
|
||||
p += size;
|
||||
crc = crc32(~0, buf, size);
|
||||
*(p++) = crc;
|
||||
*(p++) = crc >> 8;
|
||||
*(p++) = crc >> 16;
|
||||
*(p++) = crc >> 24;
|
||||
/* Clear the remaining bytes in the last word. */
|
||||
if ((size & 3) != 2) {
|
||||
memset(p, 0, (6 - size) & 3);
|
||||
}
|
||||
|
||||
s->ris |= SE_INT_RX;
|
||||
stellaris_enet_update(s);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static int stellaris_enet_can_receive(NetClientState *nc)
|
||||
{
|
||||
stellaris_enet_state *s = qemu_get_nic_opaque(nc);
|
||||
|
||||
if ((s->rctl & SE_RCTL_RXEN) == 0)
|
||||
return 1;
|
||||
|
||||
return (s->np < 31);
|
||||
}
|
||||
|
||||
static uint64_t stellaris_enet_read(void *opaque, hwaddr offset,
|
||||
unsigned size)
|
||||
{
|
||||
stellaris_enet_state *s = (stellaris_enet_state *)opaque;
|
||||
uint32_t val;
|
||||
|
||||
switch (offset) {
|
||||
case 0x00: /* RIS */
|
||||
DPRINTF("IRQ status %02x\n", s->ris);
|
||||
return s->ris;
|
||||
case 0x04: /* IM */
|
||||
return s->im;
|
||||
case 0x08: /* RCTL */
|
||||
return s->rctl;
|
||||
case 0x0c: /* TCTL */
|
||||
return s->tctl;
|
||||
case 0x10: /* DATA */
|
||||
if (s->rx_fifo_len == 0) {
|
||||
if (s->np == 0) {
|
||||
BADF("RX underflow\n");
|
||||
return 0;
|
||||
}
|
||||
s->rx_fifo_len = s->rx[s->next_packet].len;
|
||||
s->rx_fifo = s->rx[s->next_packet].data;
|
||||
DPRINTF("RX FIFO start packet len=%d\n", s->rx_fifo_len);
|
||||
}
|
||||
val = s->rx_fifo[0] | (s->rx_fifo[1] << 8) | (s->rx_fifo[2] << 16)
|
||||
| (s->rx_fifo[3] << 24);
|
||||
s->rx_fifo += 4;
|
||||
s->rx_fifo_len -= 4;
|
||||
if (s->rx_fifo_len <= 0) {
|
||||
s->rx_fifo_len = 0;
|
||||
s->next_packet++;
|
||||
if (s->next_packet >= 31)
|
||||
s->next_packet = 0;
|
||||
s->np--;
|
||||
DPRINTF("RX done np=%d\n", s->np);
|
||||
}
|
||||
return val;
|
||||
case 0x14: /* IA0 */
|
||||
return s->conf.macaddr.a[0] | (s->conf.macaddr.a[1] << 8)
|
||||
| (s->conf.macaddr.a[2] << 16) | (s->conf.macaddr.a[3] << 24);
|
||||
case 0x18: /* IA1 */
|
||||
return s->conf.macaddr.a[4] | (s->conf.macaddr.a[5] << 8);
|
||||
case 0x1c: /* THR */
|
||||
return s->thr;
|
||||
case 0x20: /* MCTL */
|
||||
return s->mctl;
|
||||
case 0x24: /* MDV */
|
||||
return s->mdv;
|
||||
case 0x28: /* MADD */
|
||||
return 0;
|
||||
case 0x2c: /* MTXD */
|
||||
return s->mtxd;
|
||||
case 0x30: /* MRXD */
|
||||
return s->mrxd;
|
||||
case 0x34: /* NP */
|
||||
return s->np;
|
||||
case 0x38: /* TR */
|
||||
return 0;
|
||||
case 0x3c: /* Undocuented: Timestamp? */
|
||||
return 0;
|
||||
default:
|
||||
hw_error("stellaris_enet_read: Bad offset %x\n", (int)offset);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void stellaris_enet_write(void *opaque, hwaddr offset,
|
||||
uint64_t value, unsigned size)
|
||||
{
|
||||
stellaris_enet_state *s = (stellaris_enet_state *)opaque;
|
||||
|
||||
switch (offset) {
|
||||
case 0x00: /* IACK */
|
||||
s->ris &= ~value;
|
||||
DPRINTF("IRQ ack %02x/%02x\n", value, s->ris);
|
||||
stellaris_enet_update(s);
|
||||
/* Clearing TXER also resets the TX fifo. */
|
||||
if (value & SE_INT_TXER)
|
||||
s->tx_frame_len = -1;
|
||||
break;
|
||||
case 0x04: /* IM */
|
||||
DPRINTF("IRQ mask %02x/%02x\n", value, s->ris);
|
||||
s->im = value;
|
||||
stellaris_enet_update(s);
|
||||
break;
|
||||
case 0x08: /* RCTL */
|
||||
s->rctl = value;
|
||||
if (value & SE_RCTL_RSTFIFO) {
|
||||
s->rx_fifo_len = 0;
|
||||
s->np = 0;
|
||||
stellaris_enet_update(s);
|
||||
}
|
||||
break;
|
||||
case 0x0c: /* TCTL */
|
||||
s->tctl = value;
|
||||
break;
|
||||
case 0x10: /* DATA */
|
||||
if (s->tx_frame_len == -1) {
|
||||
s->tx_frame_len = value & 0xffff;
|
||||
if (s->tx_frame_len > 2032) {
|
||||
DPRINTF("TX frame too long (%d)\n", s->tx_frame_len);
|
||||
s->tx_frame_len = 0;
|
||||
s->ris |= SE_INT_TXER;
|
||||
stellaris_enet_update(s);
|
||||
} else {
|
||||
DPRINTF("Start TX frame len=%d\n", s->tx_frame_len);
|
||||
/* The value written does not include the ethernet header. */
|
||||
s->tx_frame_len += 14;
|
||||
if ((s->tctl & SE_TCTL_CRC) == 0)
|
||||
s->tx_frame_len += 4;
|
||||
s->tx_fifo_len = 0;
|
||||
s->tx_fifo[s->tx_fifo_len++] = value >> 16;
|
||||
s->tx_fifo[s->tx_fifo_len++] = value >> 24;
|
||||
}
|
||||
} else {
|
||||
s->tx_fifo[s->tx_fifo_len++] = value;
|
||||
s->tx_fifo[s->tx_fifo_len++] = value >> 8;
|
||||
s->tx_fifo[s->tx_fifo_len++] = value >> 16;
|
||||
s->tx_fifo[s->tx_fifo_len++] = value >> 24;
|
||||
if (s->tx_fifo_len >= s->tx_frame_len) {
|
||||
/* We don't implement explicit CRC, so just chop it off. */
|
||||
if ((s->tctl & SE_TCTL_CRC) == 0)
|
||||
s->tx_frame_len -= 4;
|
||||
if ((s->tctl & SE_TCTL_PADEN) && s->tx_frame_len < 60) {
|
||||
memset(&s->tx_fifo[s->tx_frame_len], 0, 60 - s->tx_frame_len);
|
||||
s->tx_fifo_len = 60;
|
||||
}
|
||||
qemu_send_packet(qemu_get_queue(s->nic), s->tx_fifo,
|
||||
s->tx_frame_len);
|
||||
s->tx_frame_len = -1;
|
||||
s->ris |= SE_INT_TXEMP;
|
||||
stellaris_enet_update(s);
|
||||
DPRINTF("Done TX\n");
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 0x14: /* IA0 */
|
||||
s->conf.macaddr.a[0] = value;
|
||||
s->conf.macaddr.a[1] = value >> 8;
|
||||
s->conf.macaddr.a[2] = value >> 16;
|
||||
s->conf.macaddr.a[3] = value >> 24;
|
||||
break;
|
||||
case 0x18: /* IA1 */
|
||||
s->conf.macaddr.a[4] = value;
|
||||
s->conf.macaddr.a[5] = value >> 8;
|
||||
break;
|
||||
case 0x1c: /* THR */
|
||||
s->thr = value;
|
||||
break;
|
||||
case 0x20: /* MCTL */
|
||||
s->mctl = value;
|
||||
break;
|
||||
case 0x24: /* MDV */
|
||||
s->mdv = value;
|
||||
break;
|
||||
case 0x28: /* MADD */
|
||||
/* ignored. */
|
||||
break;
|
||||
case 0x2c: /* MTXD */
|
||||
s->mtxd = value & 0xff;
|
||||
break;
|
||||
case 0x30: /* MRXD */
|
||||
case 0x34: /* NP */
|
||||
case 0x38: /* TR */
|
||||
/* Ignored. */
|
||||
case 0x3c: /* Undocuented: Timestamp? */
|
||||
/* Ignored. */
|
||||
break;
|
||||
default:
|
||||
hw_error("stellaris_enet_write: Bad offset %x\n", (int)offset);
|
||||
}
|
||||
}
|
||||
|
||||
static const MemoryRegionOps stellaris_enet_ops = {
|
||||
.read = stellaris_enet_read,
|
||||
.write = stellaris_enet_write,
|
||||
.endianness = DEVICE_NATIVE_ENDIAN,
|
||||
};
|
||||
|
||||
static void stellaris_enet_reset(stellaris_enet_state *s)
|
||||
{
|
||||
s->mdv = 0x80;
|
||||
s->rctl = SE_RCTL_BADCRC;
|
||||
s->im = SE_INT_PHY | SE_INT_MD | SE_INT_RXER | SE_INT_FOV | SE_INT_TXEMP
|
||||
| SE_INT_TXER | SE_INT_RX;
|
||||
s->thr = 0x3f;
|
||||
s->tx_frame_len = -1;
|
||||
}
|
||||
|
||||
static void stellaris_enet_save(QEMUFile *f, void *opaque)
|
||||
{
|
||||
stellaris_enet_state *s = (stellaris_enet_state *)opaque;
|
||||
int i;
|
||||
|
||||
qemu_put_be32(f, s->ris);
|
||||
qemu_put_be32(f, s->im);
|
||||
qemu_put_be32(f, s->rctl);
|
||||
qemu_put_be32(f, s->tctl);
|
||||
qemu_put_be32(f, s->thr);
|
||||
qemu_put_be32(f, s->mctl);
|
||||
qemu_put_be32(f, s->mdv);
|
||||
qemu_put_be32(f, s->mtxd);
|
||||
qemu_put_be32(f, s->mrxd);
|
||||
qemu_put_be32(f, s->np);
|
||||
qemu_put_be32(f, s->tx_frame_len);
|
||||
qemu_put_be32(f, s->tx_fifo_len);
|
||||
qemu_put_buffer(f, s->tx_fifo, sizeof(s->tx_fifo));
|
||||
for (i = 0; i < 31; i++) {
|
||||
qemu_put_be32(f, s->rx[i].len);
|
||||
qemu_put_buffer(f, s->rx[i].data, sizeof(s->rx[i].data));
|
||||
|
||||
}
|
||||
qemu_put_be32(f, s->next_packet);
|
||||
qemu_put_be32(f, s->rx_fifo - s->rx[s->next_packet].data);
|
||||
qemu_put_be32(f, s->rx_fifo_len);
|
||||
}
|
||||
|
||||
static int stellaris_enet_load(QEMUFile *f, void *opaque, int version_id)
|
||||
{
|
||||
stellaris_enet_state *s = (stellaris_enet_state *)opaque;
|
||||
int i;
|
||||
|
||||
if (version_id != 1)
|
||||
return -EINVAL;
|
||||
|
||||
s->ris = qemu_get_be32(f);
|
||||
s->im = qemu_get_be32(f);
|
||||
s->rctl = qemu_get_be32(f);
|
||||
s->tctl = qemu_get_be32(f);
|
||||
s->thr = qemu_get_be32(f);
|
||||
s->mctl = qemu_get_be32(f);
|
||||
s->mdv = qemu_get_be32(f);
|
||||
s->mtxd = qemu_get_be32(f);
|
||||
s->mrxd = qemu_get_be32(f);
|
||||
s->np = qemu_get_be32(f);
|
||||
s->tx_frame_len = qemu_get_be32(f);
|
||||
s->tx_fifo_len = qemu_get_be32(f);
|
||||
qemu_get_buffer(f, s->tx_fifo, sizeof(s->tx_fifo));
|
||||
for (i = 0; i < 31; i++) {
|
||||
s->rx[i].len = qemu_get_be32(f);
|
||||
qemu_get_buffer(f, s->rx[i].data, sizeof(s->rx[i].data));
|
||||
|
||||
}
|
||||
s->next_packet = qemu_get_be32(f);
|
||||
s->rx_fifo = s->rx[s->next_packet].data + qemu_get_be32(f);
|
||||
s->rx_fifo_len = qemu_get_be32(f);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void stellaris_enet_cleanup(NetClientState *nc)
|
||||
{
|
||||
stellaris_enet_state *s = qemu_get_nic_opaque(nc);
|
||||
|
||||
unregister_savevm(&s->busdev.qdev, "stellaris_enet", s);
|
||||
|
||||
memory_region_destroy(&s->mmio);
|
||||
|
||||
g_free(s);
|
||||
}
|
||||
|
||||
static NetClientInfo net_stellaris_enet_info = {
|
||||
.type = NET_CLIENT_OPTIONS_KIND_NIC,
|
||||
.size = sizeof(NICState),
|
||||
.can_receive = stellaris_enet_can_receive,
|
||||
.receive = stellaris_enet_receive,
|
||||
.cleanup = stellaris_enet_cleanup,
|
||||
};
|
||||
|
||||
static int stellaris_enet_init(SysBusDevice *dev)
|
||||
{
|
||||
stellaris_enet_state *s = FROM_SYSBUS(stellaris_enet_state, dev);
|
||||
|
||||
memory_region_init_io(&s->mmio, &stellaris_enet_ops, s, "stellaris_enet",
|
||||
0x1000);
|
||||
sysbus_init_mmio(dev, &s->mmio);
|
||||
sysbus_init_irq(dev, &s->irq);
|
||||
qemu_macaddr_default_if_unset(&s->conf.macaddr);
|
||||
|
||||
s->nic = qemu_new_nic(&net_stellaris_enet_info, &s->conf,
|
||||
object_get_typename(OBJECT(dev)), dev->qdev.id, s);
|
||||
qemu_format_nic_info_str(qemu_get_queue(s->nic), s->conf.macaddr.a);
|
||||
|
||||
stellaris_enet_reset(s);
|
||||
register_savevm(&s->busdev.qdev, "stellaris_enet", -1, 1,
|
||||
stellaris_enet_save, stellaris_enet_load, s);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static Property stellaris_enet_properties[] = {
|
||||
DEFINE_NIC_PROPERTIES(stellaris_enet_state, conf),
|
||||
DEFINE_PROP_END_OF_LIST(),
|
||||
};
|
||||
|
||||
static void stellaris_enet_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
|
||||
|
||||
k->init = stellaris_enet_init;
|
||||
dc->props = stellaris_enet_properties;
|
||||
}
|
||||
|
||||
static const TypeInfo stellaris_enet_info = {
|
||||
.name = "stellaris_enet",
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(stellaris_enet_state),
|
||||
.class_init = stellaris_enet_class_init,
|
||||
};
|
||||
|
||||
static void stellaris_enet_register_types(void)
|
||||
{
|
||||
type_register_static(&stellaris_enet_info);
|
||||
}
|
||||
|
||||
type_init(stellaris_enet_register_types)
|
263
hw/net/xilinx_ethlite.c
Normal file
263
hw/net/xilinx_ethlite.c
Normal file
|
@ -0,0 +1,263 @@
|
|||
/*
|
||||
* QEMU model of the Xilinx Ethernet Lite MAC.
|
||||
*
|
||||
* Copyright (c) 2009 Edgar E. Iglesias.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "hw/sysbus.h"
|
||||
#include "hw/hw.h"
|
||||
#include "net/net.h"
|
||||
|
||||
#define D(x)
|
||||
#define R_TX_BUF0 0
|
||||
#define R_TX_LEN0 (0x07f4 / 4)
|
||||
#define R_TX_GIE0 (0x07f8 / 4)
|
||||
#define R_TX_CTRL0 (0x07fc / 4)
|
||||
#define R_TX_BUF1 (0x0800 / 4)
|
||||
#define R_TX_LEN1 (0x0ff4 / 4)
|
||||
#define R_TX_CTRL1 (0x0ffc / 4)
|
||||
|
||||
#define R_RX_BUF0 (0x1000 / 4)
|
||||
#define R_RX_CTRL0 (0x17fc / 4)
|
||||
#define R_RX_BUF1 (0x1800 / 4)
|
||||
#define R_RX_CTRL1 (0x1ffc / 4)
|
||||
#define R_MAX (0x2000 / 4)
|
||||
|
||||
#define GIE_GIE 0x80000000
|
||||
|
||||
#define CTRL_I 0x8
|
||||
#define CTRL_P 0x2
|
||||
#define CTRL_S 0x1
|
||||
|
||||
struct xlx_ethlite
|
||||
{
|
||||
SysBusDevice busdev;
|
||||
MemoryRegion mmio;
|
||||
qemu_irq irq;
|
||||
NICState *nic;
|
||||
NICConf conf;
|
||||
|
||||
uint32_t c_tx_pingpong;
|
||||
uint32_t c_rx_pingpong;
|
||||
unsigned int txbuf;
|
||||
unsigned int rxbuf;
|
||||
|
||||
uint32_t regs[R_MAX];
|
||||
};
|
||||
|
||||
static inline void eth_pulse_irq(struct xlx_ethlite *s)
|
||||
{
|
||||
/* Only the first gie reg is active. */
|
||||
if (s->regs[R_TX_GIE0] & GIE_GIE) {
|
||||
qemu_irq_pulse(s->irq);
|
||||
}
|
||||
}
|
||||
|
||||
static uint64_t
|
||||
eth_read(void *opaque, hwaddr addr, unsigned int size)
|
||||
{
|
||||
struct xlx_ethlite *s = opaque;
|
||||
uint32_t r = 0;
|
||||
|
||||
addr >>= 2;
|
||||
|
||||
switch (addr)
|
||||
{
|
||||
case R_TX_GIE0:
|
||||
case R_TX_LEN0:
|
||||
case R_TX_LEN1:
|
||||
case R_TX_CTRL1:
|
||||
case R_TX_CTRL0:
|
||||
case R_RX_CTRL1:
|
||||
case R_RX_CTRL0:
|
||||
r = s->regs[addr];
|
||||
D(qemu_log("%s " TARGET_FMT_plx "=%x\n", __func__, addr * 4, r));
|
||||
break;
|
||||
|
||||
default:
|
||||
r = tswap32(s->regs[addr]);
|
||||
break;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
static void
|
||||
eth_write(void *opaque, hwaddr addr,
|
||||
uint64_t val64, unsigned int size)
|
||||
{
|
||||
struct xlx_ethlite *s = opaque;
|
||||
unsigned int base = 0;
|
||||
uint32_t value = val64;
|
||||
|
||||
addr >>= 2;
|
||||
switch (addr)
|
||||
{
|
||||
case R_TX_CTRL0:
|
||||
case R_TX_CTRL1:
|
||||
if (addr == R_TX_CTRL1)
|
||||
base = 0x800 / 4;
|
||||
|
||||
D(qemu_log("%s addr=" TARGET_FMT_plx " val=%x\n",
|
||||
__func__, addr * 4, value));
|
||||
if ((value & (CTRL_P | CTRL_S)) == CTRL_S) {
|
||||
qemu_send_packet(qemu_get_queue(s->nic),
|
||||
(void *) &s->regs[base],
|
||||
s->regs[base + R_TX_LEN0]);
|
||||
D(qemu_log("eth_tx %d\n", s->regs[base + R_TX_LEN0]));
|
||||
if (s->regs[base + R_TX_CTRL0] & CTRL_I)
|
||||
eth_pulse_irq(s);
|
||||
} else if ((value & (CTRL_P | CTRL_S)) == (CTRL_P | CTRL_S)) {
|
||||
memcpy(&s->conf.macaddr.a[0], &s->regs[base], 6);
|
||||
if (s->regs[base + R_TX_CTRL0] & CTRL_I)
|
||||
eth_pulse_irq(s);
|
||||
}
|
||||
|
||||
/* We are fast and get ready pretty much immediately so
|
||||
we actually never flip the S nor P bits to one. */
|
||||
s->regs[addr] = value & ~(CTRL_P | CTRL_S);
|
||||
break;
|
||||
|
||||
/* Keep these native. */
|
||||
case R_RX_CTRL0:
|
||||
case R_RX_CTRL1:
|
||||
if (!(value & CTRL_S)) {
|
||||
qemu_flush_queued_packets(qemu_get_queue(s->nic));
|
||||
}
|
||||
case R_TX_LEN0:
|
||||
case R_TX_LEN1:
|
||||
case R_TX_GIE0:
|
||||
D(qemu_log("%s addr=" TARGET_FMT_plx " val=%x\n",
|
||||
__func__, addr * 4, value));
|
||||
s->regs[addr] = value;
|
||||
break;
|
||||
|
||||
default:
|
||||
s->regs[addr] = tswap32(value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static const MemoryRegionOps eth_ops = {
|
||||
.read = eth_read,
|
||||
.write = eth_write,
|
||||
.endianness = DEVICE_NATIVE_ENDIAN,
|
||||
.valid = {
|
||||
.min_access_size = 4,
|
||||
.max_access_size = 4
|
||||
}
|
||||
};
|
||||
|
||||
static int eth_can_rx(NetClientState *nc)
|
||||
{
|
||||
struct xlx_ethlite *s = qemu_get_nic_opaque(nc);
|
||||
unsigned int rxbase = s->rxbuf * (0x800 / 4);
|
||||
|
||||
return !(s->regs[rxbase + R_RX_CTRL0] & CTRL_S);
|
||||
}
|
||||
|
||||
static ssize_t eth_rx(NetClientState *nc, const uint8_t *buf, size_t size)
|
||||
{
|
||||
struct xlx_ethlite *s = qemu_get_nic_opaque(nc);
|
||||
unsigned int rxbase = s->rxbuf * (0x800 / 4);
|
||||
|
||||
/* DA filter. */
|
||||
if (!(buf[0] & 0x80) && memcmp(&s->conf.macaddr.a[0], buf, 6))
|
||||
return size;
|
||||
|
||||
if (s->regs[rxbase + R_RX_CTRL0] & CTRL_S) {
|
||||
D(qemu_log("ethlite lost packet %x\n", s->regs[R_RX_CTRL0]));
|
||||
return -1;
|
||||
}
|
||||
|
||||
D(qemu_log("%s %zd rxbase=%x\n", __func__, size, rxbase));
|
||||
memcpy(&s->regs[rxbase + R_RX_BUF0], buf, size);
|
||||
|
||||
s->regs[rxbase + R_RX_CTRL0] |= CTRL_S;
|
||||
if (s->regs[rxbase + R_RX_CTRL0] & CTRL_I)
|
||||
eth_pulse_irq(s);
|
||||
|
||||
/* If c_rx_pingpong was set flip buffers. */
|
||||
s->rxbuf ^= s->c_rx_pingpong;
|
||||
return size;
|
||||
}
|
||||
|
||||
static void eth_cleanup(NetClientState *nc)
|
||||
{
|
||||
struct xlx_ethlite *s = qemu_get_nic_opaque(nc);
|
||||
|
||||
s->nic = NULL;
|
||||
}
|
||||
|
||||
static NetClientInfo net_xilinx_ethlite_info = {
|
||||
.type = NET_CLIENT_OPTIONS_KIND_NIC,
|
||||
.size = sizeof(NICState),
|
||||
.can_receive = eth_can_rx,
|
||||
.receive = eth_rx,
|
||||
.cleanup = eth_cleanup,
|
||||
};
|
||||
|
||||
static int xilinx_ethlite_init(SysBusDevice *dev)
|
||||
{
|
||||
struct xlx_ethlite *s = FROM_SYSBUS(typeof (*s), dev);
|
||||
|
||||
sysbus_init_irq(dev, &s->irq);
|
||||
s->rxbuf = 0;
|
||||
|
||||
memory_region_init_io(&s->mmio, ð_ops, s, "xlnx.xps-ethernetlite",
|
||||
R_MAX * 4);
|
||||
sysbus_init_mmio(dev, &s->mmio);
|
||||
|
||||
qemu_macaddr_default_if_unset(&s->conf.macaddr);
|
||||
s->nic = qemu_new_nic(&net_xilinx_ethlite_info, &s->conf,
|
||||
object_get_typename(OBJECT(dev)), dev->qdev.id, s);
|
||||
qemu_format_nic_info_str(qemu_get_queue(s->nic), s->conf.macaddr.a);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static Property xilinx_ethlite_properties[] = {
|
||||
DEFINE_PROP_UINT32("tx-ping-pong", struct xlx_ethlite, c_tx_pingpong, 1),
|
||||
DEFINE_PROP_UINT32("rx-ping-pong", struct xlx_ethlite, c_rx_pingpong, 1),
|
||||
DEFINE_NIC_PROPERTIES(struct xlx_ethlite, conf),
|
||||
DEFINE_PROP_END_OF_LIST(),
|
||||
};
|
||||
|
||||
static void xilinx_ethlite_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
|
||||
|
||||
k->init = xilinx_ethlite_init;
|
||||
dc->props = xilinx_ethlite_properties;
|
||||
}
|
||||
|
||||
static const TypeInfo xilinx_ethlite_info = {
|
||||
.name = "xlnx.xps-ethernetlite",
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(struct xlx_ethlite),
|
||||
.class_init = xilinx_ethlite_class_init,
|
||||
};
|
||||
|
||||
static void xilinx_ethlite_register_types(void)
|
||||
{
|
||||
type_register_static(&xilinx_ethlite_info);
|
||||
}
|
||||
|
||||
type_init(xilinx_ethlite_register_types)
|
Loading…
Add table
Add a link
Reference in a new issue