qemu/hw/net/fsl_etsec/etsec.c

460 lines
13 KiB
C
Raw Normal View History

/*
* QEMU Freescale eTSEC Emulator
*
* Copyright (c) 2011-2013 AdaCore
*
* 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 implementation doesn't include ring priority, TCP/IP Off-Load, QoS.
*/
#include "qemu/osdep.h"
#include "sysemu/sysemu.h"
#include "hw/sysbus.h"
#include "hw/ptimer.h"
#include "etsec.h"
#include "registers.h"
#include "qemu/log.h"
/* #define HEX_DUMP */
/* #define DEBUG_REGISTER */
#ifdef DEBUG_REGISTER
static const int debug_etsec = 1;
#else
static const int debug_etsec;
#endif
#define DPRINTF(fmt, ...) do { \
if (debug_etsec) { \
qemu_log(fmt , ## __VA_ARGS__); \
} \
} while (0)
static uint64_t etsec_read(void *opaque, hwaddr addr, unsigned size)
{
eTSEC *etsec = opaque;
uint32_t reg_index = addr / 4;
eTSEC_Register *reg = NULL;
uint32_t ret = 0x0;
assert(reg_index < ETSEC_REG_NUMBER);
reg = &etsec->regs[reg_index];
switch (reg->access) {
case ACC_WO:
ret = 0x00000000;
break;
case ACC_RW:
case ACC_W1C:
case ACC_RO:
default:
ret = reg->value;
break;
}
DPRINTF("Read 0x%08x @ 0x" TARGET_FMT_plx
" : %s (%s)\n",
ret, addr, reg->name, reg->desc);
return ret;
}
static void write_tstat(eTSEC *etsec,
eTSEC_Register *reg,
uint32_t reg_index,
uint32_t value)
{
int i = 0;
for (i = 0; i < 8; i++) {
/* Check THLTi flag in TSTAT */
if (value & (1 << (31 - i))) {
etsec_walk_tx_ring(etsec, i);
}
}
/* Write 1 to clear */
reg->value &= ~value;
}
static void write_rstat(eTSEC *etsec,
eTSEC_Register *reg,
uint32_t reg_index,
uint32_t value)
{
int i = 0;
for (i = 0; i < 8; i++) {
/* Check QHLTi flag in RSTAT */
if (value & (1 << (23 - i)) && !(reg->value & (1 << (23 - i)))) {
etsec_walk_rx_ring(etsec, i);
}
}
/* Write 1 to clear */
reg->value &= ~value;
}
static void write_tbasex(eTSEC *etsec,
eTSEC_Register *reg,
uint32_t reg_index,
uint32_t value)
{
reg->value = value & ~0x7;
/* Copy this value in the ring's TxBD pointer */
etsec->regs[TBPTR0 + (reg_index - TBASE0)].value = value & ~0x7;
}
static void write_rbasex(eTSEC *etsec,
eTSEC_Register *reg,
uint32_t reg_index,
uint32_t value)
{
reg->value = value & ~0x7;
/* Copy this value in the ring's RxBD pointer */
etsec->regs[RBPTR0 + (reg_index - RBASE0)].value = value & ~0x7;
}
static void write_ievent(eTSEC *etsec,
eTSEC_Register *reg,
uint32_t reg_index,
uint32_t value)
{
/* Write 1 to clear */
reg->value &= ~value;
if (!(reg->value & (IEVENT_TXF | IEVENT_TXF))) {
qemu_irq_lower(etsec->tx_irq);
}
if (!(reg->value & (IEVENT_RXF | IEVENT_RXF))) {
qemu_irq_lower(etsec->rx_irq);
}
if (!(reg->value & (IEVENT_MAG | IEVENT_GTSC | IEVENT_GRSC | IEVENT_TXC |
IEVENT_RXC | IEVENT_BABR | IEVENT_BABT | IEVENT_LC |
IEVENT_CRL | IEVENT_FGPI | IEVENT_FIR | IEVENT_FIQ |
IEVENT_DPE | IEVENT_PERR | IEVENT_EBERR | IEVENT_TXE |
IEVENT_XFUN | IEVENT_BSY | IEVENT_MSRO | IEVENT_MMRD |
IEVENT_MMRW))) {
qemu_irq_lower(etsec->err_irq);
}
}
static void write_dmactrl(eTSEC *etsec,
eTSEC_Register *reg,
uint32_t reg_index,
uint32_t value)
{
reg->value = value;
if (value & DMACTRL_GRS) {
if (etsec->rx_buffer_len != 0) {
/* Graceful receive stop delayed until end of frame */
} else {
/* Graceful receive stop now */
etsec->regs[IEVENT].value |= IEVENT_GRSC;
if (etsec->regs[IMASK].value & IMASK_GRSCEN) {
qemu_irq_raise(etsec->err_irq);
}
}
}
if (value & DMACTRL_GTS) {
if (etsec->tx_buffer_len != 0) {
/* Graceful transmit stop delayed until end of frame */
} else {
/* Graceful transmit stop now */
etsec->regs[IEVENT].value |= IEVENT_GTSC;
if (etsec->regs[IMASK].value & IMASK_GTSCEN) {
qemu_irq_raise(etsec->err_irq);
}
}
}
if (!(value & DMACTRL_WOP)) {
/* Start polling */
ptimer_stop(etsec->ptimer);
ptimer_set_count(etsec->ptimer, 1);
ptimer_run(etsec->ptimer, 1);
}
}
static void etsec_write(void *opaque,
hwaddr addr,
uint64_t value,
unsigned size)
{
eTSEC *etsec = opaque;
uint32_t reg_index = addr / 4;
eTSEC_Register *reg = NULL;
uint32_t before = 0x0;
assert(reg_index < ETSEC_REG_NUMBER);
reg = &etsec->regs[reg_index];
before = reg->value;
switch (reg_index) {
case IEVENT:
write_ievent(etsec, reg, reg_index, value);
break;
case DMACTRL:
write_dmactrl(etsec, reg, reg_index, value);
break;
case TSTAT:
write_tstat(etsec, reg, reg_index, value);
break;
case RSTAT:
write_rstat(etsec, reg, reg_index, value);
break;
case TBASE0 ... TBASE7:
write_tbasex(etsec, reg, reg_index, value);
break;
case RBASE0 ... RBASE7:
write_rbasex(etsec, reg, reg_index, value);
break;
case MIIMCFG ... MIIMIND:
etsec_write_miim(etsec, reg, reg_index, value);
break;
default:
/* Default handling */
switch (reg->access) {
case ACC_RW:
case ACC_WO:
reg->value = value;
break;
case ACC_W1C:
reg->value &= ~value;
break;
case ACC_RO:
default:
/* Read Only or Unknown register */
break;
}
}
DPRINTF("Write 0x%08x @ 0x" TARGET_FMT_plx
" val:0x%08x->0x%08x : %s (%s)\n",
(unsigned int)value, addr, before, reg->value,
reg->name, reg->desc);
}
static const MemoryRegionOps etsec_ops = {
.read = etsec_read,
.write = etsec_write,
.endianness = DEVICE_NATIVE_ENDIAN,
.impl = {
.min_access_size = 4,
.max_access_size = 4,
},
};
static void etsec_timer_hit(void *opaque)
{
eTSEC *etsec = opaque;
ptimer_stop(etsec->ptimer);
if (!(etsec->regs[DMACTRL].value & DMACTRL_WOP)) {
if (!(etsec->regs[DMACTRL].value & DMACTRL_GTS)) {
etsec_walk_tx_ring(etsec, 0);
}
ptimer_set_count(etsec->ptimer, 1);
ptimer_run(etsec->ptimer, 1);
}
}
static void etsec_reset(DeviceState *d)
{
eTSEC *etsec = ETSEC_COMMON(d);
int i = 0;
int reg_index = 0;
/* Default value for all registers */
for (i = 0; i < ETSEC_REG_NUMBER; i++) {
etsec->regs[i].name = "Reserved";
etsec->regs[i].desc = "";
etsec->regs[i].access = ACC_UNKNOWN;
etsec->regs[i].value = 0x00000000;
}
/* Set-up known registers */
for (i = 0; eTSEC_registers_def[i].name != NULL; i++) {
reg_index = eTSEC_registers_def[i].offset / 4;
etsec->regs[reg_index].name = eTSEC_registers_def[i].name;
etsec->regs[reg_index].desc = eTSEC_registers_def[i].desc;
etsec->regs[reg_index].access = eTSEC_registers_def[i].access;
etsec->regs[reg_index].value = eTSEC_registers_def[i].reset;
}
etsec->tx_buffer = NULL;
etsec->tx_buffer_len = 0;
etsec->rx_buffer = NULL;
etsec->rx_buffer_len = 0;
etsec->phy_status =
MII_SR_EXTENDED_CAPS | MII_SR_LINK_STATUS | MII_SR_AUTONEG_CAPS |
MII_SR_AUTONEG_COMPLETE | MII_SR_PREAMBLE_SUPPRESS |
MII_SR_EXTENDED_STATUS | MII_SR_100T2_HD_CAPS | MII_SR_100T2_FD_CAPS |
MII_SR_10T_HD_CAPS | MII_SR_10T_FD_CAPS | MII_SR_100X_HD_CAPS |
MII_SR_100X_FD_CAPS | MII_SR_100T4_CAPS;
}
static ssize_t etsec_receive(NetClientState *nc,
const uint8_t *buf,
size_t size)
{
ssize_t ret;
eTSEC *etsec = qemu_get_nic_opaque(nc);
#if defined(HEX_DUMP)
fprintf(stderr, "%s receive size:%zd\n", nc->name, size);
qemu_hexdump((void *)buf, stderr, "", size);
#endif
/* Flush is unnecessary as are already in receiving path */
etsec->need_flush = false;
ret = etsec_rx_ring_write(etsec, buf, size);
if (ret == 0) {
/* The packet will be queued, let's flush it when buffer is available
* again. */
etsec->need_flush = true;
}
return ret;
}
static void etsec_set_link_status(NetClientState *nc)
{
eTSEC *etsec = qemu_get_nic_opaque(nc);
etsec_miim_link_status(etsec, nc);
}
static NetClientInfo net_etsec_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 = etsec_receive,
.link_status_changed = etsec_set_link_status,
};
static void etsec_realize(DeviceState *dev, Error **errp)
{
eTSEC *etsec = ETSEC_COMMON(dev);
etsec->nic = qemu_new_nic(&net_etsec_info, &etsec->conf,
object_get_typename(OBJECT(dev)), dev->id, etsec);
qemu_format_nic_info_str(qemu_get_queue(etsec->nic), etsec->conf.macaddr.a);
etsec->bh = qemu_bh_new(etsec_timer_hit, etsec);
etsec->ptimer = ptimer_init(etsec->bh, PTIMER_POLICY_DEFAULT);
ptimer_set_freq(etsec->ptimer, 100);
}
static void etsec_instance_init(Object *obj)
{
eTSEC *etsec = ETSEC_COMMON(obj);
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
memory_region_init_io(&etsec->io_area, OBJECT(etsec), &etsec_ops, etsec,
"eTSEC", 0x1000);
sysbus_init_mmio(sbd, &etsec->io_area);
sysbus_init_irq(sbd, &etsec->tx_irq);
sysbus_init_irq(sbd, &etsec->rx_irq);
sysbus_init_irq(sbd, &etsec->err_irq);
}
static Property etsec_properties[] = {
DEFINE_NIC_PROPERTIES(eTSEC, conf),
DEFINE_PROP_END_OF_LIST(),
};
static void etsec_class_init(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
dc->realize = etsec_realize;
dc->reset = etsec_reset;
dc->props = etsec_properties;
sysbus: Set user_creatable=false by default on TYPE_SYS_BUS_DEVICE commit 33cd52b5d7b9adfd009e95f07e6c64dd88ae2a31 unset cannot_instantiate_with_device_add_yet in TYPE_SYSBUS, making all sysbus devices appear on "-device help" and lack the "no-user" flag in "info qdm". To fix this, we can set user_creatable=false by default on TYPE_SYS_BUS_DEVICE, but this requires setting user_creatable=true explicitly on the sysbus devices that actually work with -device. Fortunately today we have just a few has_dynamic_sysbus=1 machines: virt, pc-q35-*, ppce500, and spapr. virt, ppce500, and spapr have extra checks to ensure just a few device types can be instantiated: * virt supports only TYPE_VFIO_CALXEDA_XGMAC, TYPE_VFIO_AMD_XGBE. * ppce500 supports only TYPE_ETSEC_COMMON. * spapr supports only TYPE_SPAPR_PCI_HOST_BRIDGE. This patch sets user_creatable=true explicitly on those 4 device classes. Now, the more complex cases: pc-q35-*: q35 has no sysbus device whitelist yet (which is a separate bug). We are in the process of fixing it and building a sysbus whitelist on q35, but in the meantime we can fix the "-device help" and "info qdm" bugs mentioned above. Also, despite not being strictly necessary for fixing the q35 bug, reducing the list of user_creatable=true devices will help us be more confident when building the q35 whitelist. xen: We also have a hack at xen_set_dynamic_sysbus(), that sets has_dynamic_sysbus=true at runtime when using the Xen accelerator. This hack is only used to allow xen-backend devices to be dynamically plugged/unplugged. This means today we can use -device with the following 22 device types, that are the ones compiled into the qemu-system-x86_64 and qemu-system-i386 binaries: * allwinner-ahci * amd-iommu * cfi.pflash01 * esp * fw_cfg_io * fw_cfg_mem * generic-sdhci * hpet * intel-iommu * ioapic * isabus-bridge * kvmclock * kvm-ioapic * kvmvapic * SUNW,fdtwo * sysbus-ahci * sysbus-fdc * sysbus-ohci * unimplemented-device * virtio-mmio * xen-backend * xen-sysdev This patch adds user_creatable=true explicitly to those devices, temporarily, just to keep 100% compatibility with existing behavior of q35. Subsequent patches will remove user_creatable=true from the devices that are really not meant to user-creatable on any machine, and remove the FIXME comment from the ones that are really supposed to be user-creatable. This is being done in separate patches because we still don't have an obvious list of devices that will be whitelisted by q35, and I would like to get each device reviewed individually. Cc: Alexander Graf <agraf@suse.de> Cc: Alex Williamson <alex.williamson@redhat.com> Cc: Alistair Francis <alistair.francis@xilinx.com> Cc: Beniamino Galvani <b.galvani@gmail.com> Cc: Christian Borntraeger <borntraeger@de.ibm.com> Cc: Cornelia Huck <cornelia.huck@de.ibm.com> Cc: David Gibson <david@gibson.dropbear.id.au> Cc: "Edgar E. Iglesias" <edgar.iglesias@gmail.com> Cc: Eduardo Habkost <ehabkost@redhat.com> Cc: Frank Blaschka <frank.blaschka@de.ibm.com> Cc: Gabriel L. Somlo <somlo@cmu.edu> Cc: Gerd Hoffmann <kraxel@redhat.com> Cc: Igor Mammedov <imammedo@redhat.com> Cc: Jason Wang <jasowang@redhat.com> Cc: John Snow <jsnow@redhat.com> Cc: Juergen Gross <jgross@suse.com> Cc: Kevin Wolf <kwolf@redhat.com> Cc: Laszlo Ersek <lersek@redhat.com> Cc: Marcel Apfelbaum <marcel@redhat.com> Cc: Markus Armbruster <armbru@redhat.com> Cc: Max Reitz <mreitz@redhat.com> Cc: "Michael S. Tsirkin" <mst@redhat.com> Cc: Paolo Bonzini <pbonzini@redhat.com> Cc: Peter Maydell <peter.maydell@linaro.org> Cc: Pierre Morel <pmorel@linux.vnet.ibm.com> Cc: Prasad J Pandit <pjp@fedoraproject.org> Cc: qemu-arm@nongnu.org Cc: qemu-block@nongnu.org Cc: qemu-ppc@nongnu.org Cc: Richard Henderson <rth@twiddle.net> Cc: Rob Herring <robh@kernel.org> Cc: Shannon Zhao <zhaoshenglong@huawei.com> Cc: sstabellini@kernel.org Cc: Thomas Huth <thuth@redhat.com> Cc: Yi Min Zhao <zyimin@linux.vnet.ibm.com> Acked-by: John Snow <jsnow@redhat.com> Acked-by: Juergen Gross <jgross@suse.com> Acked-by: Marcel Apfelbaum <marcel@redhat.com> Signed-off-by: Eduardo Habkost <ehabkost@redhat.com> Message-Id: <20170503203604.31462-3-ehabkost@redhat.com> Reviewed-by: Markus Armbruster <armbru@redhat.com> [ehabkost: Small changes at sysbus_device_class_init() comments] Signed-off-by: Eduardo Habkost <ehabkost@redhat.com>
2017-05-03 23:35:45 +03:00
/* Supported by ppce500 machine */
dc->user_creatable = true;
}
static TypeInfo etsec_info = {
.name = "eTSEC",
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(eTSEC),
.class_init = etsec_class_init,
.instance_init = etsec_instance_init,
};
static void etsec_register_types(void)
{
type_register_static(&etsec_info);
}
type_init(etsec_register_types)
DeviceState *etsec_create(hwaddr base,
MemoryRegion * mr,
NICInfo * nd,
qemu_irq tx_irq,
qemu_irq rx_irq,
qemu_irq err_irq)
{
DeviceState *dev;
dev = qdev_create(NULL, "eTSEC");
qdev_set_nic_properties(dev, nd);
qdev_init_nofail(dev);
sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, tx_irq);
sysbus_connect_irq(SYS_BUS_DEVICE(dev), 1, rx_irq);
sysbus_connect_irq(SYS_BUS_DEVICE(dev), 2, err_irq);
memory_region_add_subregion(mr, base,
SYS_BUS_DEVICE(dev)->mmio[0].memory);
return dev;
}