mirror of
https://github.com/qemu/qemu.git
synced 2024-12-14 23:13:29 +08:00
57407ea44c
All NICs have a cleanup function that, in most cases, zeroes the pointer to the NICState. In some cases, it frees data belonging to the NIC. However, this function is never called except when exiting from QEMU. It is not necessary to NULL pointers and free data here; the right place to do that would be in the device's unrealize function, after calling qemu_del_nic. Zeroing the NIC multiple times is also wrong for multiqueue devices. This cleanup function gets in the way of making the NetClientStates for the NIC hold an object_ref reference to the object, so get rid of it. Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
542 lines
14 KiB
C
542 lines
14 KiB
C
/*
|
|
* 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 <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;
|
|
|
|
#define TYPE_MILKYMIST_MINIMAC2 "milkymist-minimac2"
|
|
#define MILKYMIST_MINIMAC2(obj) \
|
|
OBJECT_CHECK(MilkymistMinimac2State, (obj), TYPE_MILKYMIST_MINIMAC2)
|
|
|
|
struct MilkymistMinimac2State {
|
|
SysBusDevice parent_obj;
|
|
|
|
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_write(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 milkymist_minimac2_reset(DeviceState *d)
|
|
{
|
|
MilkymistMinimac2State *s = MILKYMIST_MINIMAC2(d);
|
|
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,
|
|
};
|
|
|
|
static int milkymist_minimac2_init(SysBusDevice *sbd)
|
|
{
|
|
DeviceState *dev = DEVICE(sbd);
|
|
MilkymistMinimac2State *s = MILKYMIST_MINIMAC2(dev);
|
|
size_t buffers_size = TARGET_PAGE_ALIGN(3 * MINIMAC2_BUFFER_SIZE);
|
|
|
|
sysbus_init_irq(sbd, &s->rx_irq);
|
|
sysbus_init_irq(sbd, &s->tx_irq);
|
|
|
|
memory_region_init_io(&s->regs_region, OBJECT(dev), &minimac2_ops, s,
|
|
"milkymist-minimac2", R_MAX * 4);
|
|
sysbus_init_mmio(sbd, &s->regs_region);
|
|
|
|
/* register buffers memory */
|
|
memory_region_init_ram(&s->buffers, OBJECT(dev), "milkymist-minimac2.buffers",
|
|
buffers_size, &error_abort);
|
|
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(sbd, &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->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,
|
|
.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,
|
|
.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 = TYPE_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)
|