/* * QEMU model of the Xilinx Ethernet Lite MAC. * * Copyright (c) 2009 Edgar E. Iglesias. * Copyright (c) 2024 Linaro, Ltd * * DS580: https://docs.amd.com/v/u/en-US/xps_ethernetlite * LogiCORE IP XPS Ethernet Lite Media Access Controller * * 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 "qemu/osdep.h" #include "qemu/module.h" #include "qemu/bitops.h" #include "qom/object.h" #include "qapi/error.h" #include "hw/sysbus.h" #include "hw/irq.h" #include "hw/qdev-properties.h" #include "hw/qdev-properties-system.h" #include "hw/misc/unimp.h" #include "net/net.h" #include "trace.h" #define BUFSZ_MAX 0x07e4 #define A_MDIO_BASE 0x07e4 #define A_TX_BASE0 0x07f4 #define A_TX_BASE1 0x0ff4 #define A_RX_BASE0 0x17fc #define A_RX_BASE1 0x1ffc enum { TX_LEN = 0, TX_GIE = 1, TX_CTRL = 2, TX_MAX }; enum { RX_CTRL = 0, RX_MAX }; #define GIE_GIE 0x80000000 #define CTRL_I 0x8 #define CTRL_P 0x2 #define CTRL_S 0x1 typedef struct XlnxXpsEthLitePort { MemoryRegion txio; MemoryRegion rxio; MemoryRegion txbuf; MemoryRegion rxbuf; struct { uint32_t tx_len; uint32_t tx_gie; uint32_t tx_ctrl; uint32_t rx_ctrl; } reg; } XlnxXpsEthLitePort; #define TYPE_XILINX_ETHLITE "xlnx.xps-ethernetlite" OBJECT_DECLARE_SIMPLE_TYPE(XlnxXpsEthLite, XILINX_ETHLITE) struct XlnxXpsEthLite { SysBusDevice parent_obj; EndianMode model_endianness; MemoryRegion container; qemu_irq irq; NICState *nic; NICConf conf; uint32_t c_tx_pingpong; uint32_t c_rx_pingpong; unsigned int port_index; /* dual port RAM index */ UnimplementedDeviceState rsvd; UnimplementedDeviceState mdio; XlnxXpsEthLitePort port[2]; }; static inline void eth_pulse_irq(XlnxXpsEthLite *s) { /* Only the first gie reg is active. */ if (s->port[0].reg.tx_gie & GIE_GIE) { qemu_irq_pulse(s->irq); } } static unsigned addr_to_port_index(hwaddr addr) { return extract64(addr, 11, 1); } static void *txbuf_ptr(XlnxXpsEthLite *s, unsigned port_index) { return memory_region_get_ram_ptr(&s->port[port_index].txbuf); } static void *rxbuf_ptr(XlnxXpsEthLite *s, unsigned port_index) { return memory_region_get_ram_ptr(&s->port[port_index].rxbuf); } static uint64_t port_tx_read(void *opaque, hwaddr addr, unsigned int size) { XlnxXpsEthLite *s = opaque; unsigned port_index = addr_to_port_index(addr); uint32_t r = 0; switch (addr >> 2) { case TX_LEN: r = s->port[port_index].reg.tx_len; break; case TX_GIE: r = s->port[port_index].reg.tx_gie; break; case TX_CTRL: r = s->port[port_index].reg.tx_ctrl; break; default: g_assert_not_reached(); } return r; } static void port_tx_write(void *opaque, hwaddr addr, uint64_t value, unsigned int size) { XlnxXpsEthLite *s = opaque; unsigned port_index = addr_to_port_index(addr); switch (addr >> 2) { case TX_LEN: s->port[port_index].reg.tx_len = value; break; case TX_GIE: s->port[port_index].reg.tx_gie = value; break; case TX_CTRL: if ((value & (CTRL_P | CTRL_S)) == CTRL_S) { qemu_send_packet(qemu_get_queue(s->nic), txbuf_ptr(s, port_index), s->port[port_index].reg.tx_len); if (s->port[port_index].reg.tx_ctrl & CTRL_I) { eth_pulse_irq(s); } } else if ((value & (CTRL_P | CTRL_S)) == (CTRL_P | CTRL_S)) { memcpy(&s->conf.macaddr.a[0], txbuf_ptr(s, port_index), 6); if (s->port[port_index].reg.tx_ctrl & 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->port[port_index].reg.tx_ctrl = value & ~(CTRL_P | CTRL_S); break; default: g_assert_not_reached(); } } static const MemoryRegionOps eth_porttx_ops[2] = { [0 ... 1] = { .read = port_tx_read, .write = port_tx_write, .impl = { .min_access_size = 4, .max_access_size = 4, }, .valid = { .min_access_size = 4, .max_access_size = 4, }, }, [0].endianness = DEVICE_LITTLE_ENDIAN, [1].endianness = DEVICE_BIG_ENDIAN, }; static uint64_t port_rx_read(void *opaque, hwaddr addr, unsigned int size) { XlnxXpsEthLite *s = opaque; unsigned port_index = addr_to_port_index(addr); uint32_t r = 0; switch (addr >> 2) { case RX_CTRL: r = s->port[port_index].reg.rx_ctrl; break; default: g_assert_not_reached(); } return r; } static void port_rx_write(void *opaque, hwaddr addr, uint64_t value, unsigned int size) { XlnxXpsEthLite *s = opaque; unsigned port_index = addr_to_port_index(addr); switch (addr >> 2) { case RX_CTRL: if (!(value & CTRL_S)) { qemu_flush_queued_packets(qemu_get_queue(s->nic)); } s->port[port_index].reg.rx_ctrl = value; break; default: g_assert_not_reached(); } } static const MemoryRegionOps eth_portrx_ops[2] = { [0 ... 1] = { .read = port_rx_read, .write = port_rx_write, .impl = { .min_access_size = 4, .max_access_size = 4, }, .valid = { .min_access_size = 4, .max_access_size = 4, }, }, [0].endianness = DEVICE_LITTLE_ENDIAN, [1].endianness = DEVICE_BIG_ENDIAN, }; static bool eth_can_rx(NetClientState *nc) { XlnxXpsEthLite *s = qemu_get_nic_opaque(nc); return !(s->port[s->port_index].reg.rx_ctrl & CTRL_S); } static ssize_t eth_rx(NetClientState *nc, const uint8_t *buf, size_t size) { XlnxXpsEthLite *s = qemu_get_nic_opaque(nc); unsigned int port_index = s->port_index; /* DA filter. */ if (!(buf[0] & 0x80) && memcmp(&s->conf.macaddr.a[0], buf, 6)) return size; if (s->port[port_index].reg.rx_ctrl & CTRL_S) { trace_ethlite_pkt_lost(s->port[port_index].reg.rx_ctrl); return -1; } if (size >= BUFSZ_MAX) { trace_ethlite_pkt_size_too_big(size); return -1; } memcpy(rxbuf_ptr(s, port_index), buf, size); s->port[port_index].reg.rx_ctrl |= CTRL_S; if (s->port[port_index].reg.rx_ctrl & CTRL_I) { eth_pulse_irq(s); } /* If c_rx_pingpong was set flip buffers. */ s->port_index ^= s->c_rx_pingpong; return size; } static void xilinx_ethlite_reset(DeviceState *dev) { XlnxXpsEthLite *s = XILINX_ETHLITE(dev); s->port_index = 0; } static NetClientInfo net_xilinx_ethlite_info = { .type = NET_CLIENT_DRIVER_NIC, .size = sizeof(NICState), .can_receive = eth_can_rx, .receive = eth_rx, }; static void xilinx_ethlite_realize(DeviceState *dev, Error **errp) { XlnxXpsEthLite *s = XILINX_ETHLITE(dev); unsigned ops_index; if (s->model_endianness == ENDIAN_MODE_UNSPECIFIED) { error_setg(errp, TYPE_XILINX_ETHLITE " property 'endianness'" " must be set to 'big' or 'little'"); return; } ops_index = s->model_endianness == ENDIAN_MODE_BIG ? 1 : 0; memory_region_init(&s->container, OBJECT(dev), "xlnx.xps-ethernetlite", 0x2000); object_initialize_child(OBJECT(dev), "ethlite.reserved", &s->rsvd, TYPE_UNIMPLEMENTED_DEVICE); qdev_prop_set_string(DEVICE(&s->rsvd), "name", "ethlite.reserved"); qdev_prop_set_uint64(DEVICE(&s->rsvd), "size", memory_region_size(&s->container)); sysbus_realize(SYS_BUS_DEVICE(&s->rsvd), &error_fatal); memory_region_add_subregion_overlap(&s->container, 0, sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->rsvd), 0), -1); object_initialize_child(OBJECT(dev), "ethlite.mdio", &s->mdio, TYPE_UNIMPLEMENTED_DEVICE); qdev_prop_set_string(DEVICE(&s->mdio), "name", "ethlite.mdio"); qdev_prop_set_uint64(DEVICE(&s->mdio), "size", 4 * 4); sysbus_realize(SYS_BUS_DEVICE(&s->mdio), &error_fatal); memory_region_add_subregion(&s->container, A_MDIO_BASE, sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->mdio), 0)); for (unsigned i = 0; i < 2; i++) { memory_region_init_ram(&s->port[i].txbuf, OBJECT(dev), i ? "ethlite.tx[1]buf" : "ethlite.tx[0]buf", BUFSZ_MAX, &error_abort); memory_region_add_subregion(&s->container, 0x0800 * i, &s->port[i].txbuf); memory_region_init_io(&s->port[i].txio, OBJECT(dev), ð_porttx_ops[ops_index], s, i ? "ethlite.tx[1]io" : "ethlite.tx[0]io", 4 * TX_MAX); memory_region_add_subregion(&s->container, i ? A_TX_BASE1 : A_TX_BASE0, &s->port[i].txio); memory_region_init_ram(&s->port[i].rxbuf, OBJECT(dev), i ? "ethlite.rx[1]buf" : "ethlite.rx[0]buf", BUFSZ_MAX, &error_abort); memory_region_add_subregion(&s->container, 0x1000 + 0x0800 * i, &s->port[i].rxbuf); memory_region_init_io(&s->port[i].rxio, OBJECT(dev), ð_portrx_ops[ops_index], s, i ? "ethlite.rx[1]io" : "ethlite.rx[0]io", 4 * RX_MAX); memory_region_add_subregion(&s->container, i ? A_RX_BASE1 : A_RX_BASE0, &s->port[i].rxio); } 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->id, &dev->mem_reentrancy_guard, s); qemu_format_nic_info_str(qemu_get_queue(s->nic), s->conf.macaddr.a); } static void xilinx_ethlite_init(Object *obj) { XlnxXpsEthLite *s = XILINX_ETHLITE(obj); sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq); sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->container); } static const Property xilinx_ethlite_properties[] = { DEFINE_PROP_ENDIAN_NODEFAULT("endianness", XlnxXpsEthLite, model_endianness), DEFINE_PROP_UINT32("tx-ping-pong", XlnxXpsEthLite, c_tx_pingpong, 1), DEFINE_PROP_UINT32("rx-ping-pong", XlnxXpsEthLite, c_rx_pingpong, 1), DEFINE_NIC_PROPERTIES(XlnxXpsEthLite, conf), }; static void xilinx_ethlite_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); dc->realize = xilinx_ethlite_realize; device_class_set_legacy_reset(dc, xilinx_ethlite_reset); device_class_set_props(dc, xilinx_ethlite_properties); } static const TypeInfo xilinx_ethlite_types[] = { { .name = TYPE_XILINX_ETHLITE, .parent = TYPE_SYS_BUS_DEVICE, .instance_size = sizeof(XlnxXpsEthLite), .instance_init = xilinx_ethlite_init, .class_init = xilinx_ethlite_class_init, }, }; DEFINE_TYPES(xilinx_ethlite_types)