qemu/hw/net/milkymist-minimac2.c

545 lines
14 KiB
C
Raw Normal View History

/*
* 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 "qemu/osdep.h"
2016-03-14 11:01:28 +03:00
#include "qapi/error.h"
#include "qemu-common.h"
#include "cpu.h" /* FIXME: why does this use TARGET_PAGE_ALIGN? */
#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 {
return 0;
}
/* 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 int minimac2_can_rx(MilkymistMinimac2State *s)
{
if (s->regs[R_STATE0] == STATE_LOADED) {
return 1;
}
if (s->regs[R_STATE1] == STATE_LOADED) {
return 1;
}
return 0;
}
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);
if (minimac2_can_rx(s)) {
qemu_flush_queued_packets(qemu_get_queue(s->nic));
}
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 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 = {
qapi: Change Netdev into a flat union This is a mostly-mechanical conversion that creates a new flat union 'Netdev' QAPI type that covers all the branches of the former 'NetClientOptions' simple union, where the branches are now listed in a new 'NetClientDriver' enum rather than generated from the simple union. The existence of a flat union has no change to the command line syntax accepted for new code, and will make it possible for a future patch to switch the QMP command to parse a boxed union for no change to valid QMP; but it does have some ripple effect on the C code when dealing with the new types. While making the conversion, note that the 'NetLegacy' type remains unchanged: it applies only to legacy command line options, and will not be ported to QMP, so it should remain a wrapper around a simple union; to avoid confusion, the type named 'NetClientOptions' is now gone, and we introduce 'NetLegacyOptions' in its place. Then, in the C code, we convert from NetLegacy to Netdev as soon as possible, so that the bulk of the net stack only has to deal with one QAPI type, not two. Note that since the old legacy code always rejected 'hubport', we can just omit that branch from the new 'NetLegacyOptions' simple union. Based on an idea originally by Zoltán Kővágó <DirtY.iCE.hu@gmail.com>: Message-Id: <01a527fbf1a5de880091f98cf011616a78adeeee.1441627176.git.DirtY.iCE.hu@gmail.com> although the sed script in that patch no longer applies due to other changes in the tree since then, and I also did some manual cleanups (such as fixing whitespace to keep checkpatch happy). Signed-off-by: Eric Blake <eblake@redhat.com> Message-Id: <1468468228-27827-13-git-send-email-eblake@redhat.com> Reviewed-by: Markus Armbruster <armbru@redhat.com> [Fixup from Eric squashed in] Signed-off-by: Markus Armbruster <armbru@redhat.com>
2016-07-14 06:50:23 +03:00
.type = NET_CLIENT_DRIVER_NIC,
.size = sizeof(NICState),
.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_nomigrate(&s->buffers, OBJECT(dev), "milkymist-minimac2.buffers",
Fix bad error handling after memory_region_init_ram() Symptom: $ qemu-system-x86_64 -m 10000000 Unexpected error in ram_block_add() at /work/armbru/qemu/exec.c:1456: upstream-qemu: cannot set up guest memory 'pc.ram': Cannot allocate memory Aborted (core dumped) Root cause: commit ef701d7 screwed up handling of out-of-memory conditions. Before the commit, we report the error and exit(1), in one place, ram_block_add(). The commit lifts the error handling up the call chain some, to three places. Fine. Except it uses &error_abort in these places, changing the behavior from exit(1) to abort(), and thus undoing the work of commit 3922825 "exec: Don't abort when we can't allocate guest memory". The three places are: * memory_region_init_ram() Commit 4994653 (right after commit ef701d7) lifted the error handling further, through memory_region_init_ram(), multiplying the incorrect use of &error_abort. Later on, imitation of existing (bad) code may have created more. * memory_region_init_ram_ptr() The &error_abort is still there. * memory_region_init_rom_device() Doesn't need fixing, because commit 33e0eb5 (soon after commit ef701d7) lifted the error handling further, and in the process changed it from &error_abort to passing it up the call chain. Correct, because the callers are realize() methods. Fix the error handling after memory_region_init_ram() with a Coccinelle semantic patch: @r@ expression mr, owner, name, size, err; position p; @@ memory_region_init_ram(mr, owner, name, size, ( - &error_abort + &error_fatal | err@p ) ); @script:python@ p << r.p; @@ print "%s:%s:%s" % (p[0].file, p[0].line, p[0].column) When the last argument is &error_abort, it gets replaced by &error_fatal. This is the fix. If the last argument is anything else, its position is reported. This lets us check the fix is complete. Four positions get reported: * ram_backend_memory_alloc() Error is passed up the call chain, ultimately through user_creatable_complete(). As far as I can tell, it's callers all handle the error sanely. * fsl_imx25_realize(), fsl_imx31_realize(), dp8393x_realize() DeviceClass.realize() methods, errors handled sanely further up the call chain. We're good. Test case again behaves: $ qemu-system-x86_64 -m 10000000 qemu-system-x86_64: cannot set up guest memory 'pc.ram': Cannot allocate memory [Exit 1 ] The next commits will repair the rest of commit ef701d7's damage. Signed-off-by: Markus Armbruster <armbru@redhat.com> Message-Id: <1441983105-26376-3-git-send-email-armbru@redhat.com> Reviewed-by: Peter Crosthwaite <crosthwaite.peter@gmail.com>
2015-09-11 17:51:43 +03:00
buffers_size, &error_fatal);
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)