hw: move NICs to hw/net/, configure via default-configs/

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
This commit is contained in:
Paolo Bonzini 2013-02-05 12:33:56 +01:00
parent 0ddfaf7fe4
commit d7e35d4a84
25 changed files with 22 additions and 10 deletions

View file

@ -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
View 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(&eth->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, &eth_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
View 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
View 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
View 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
View 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
View 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
View 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, &eth_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)