| /* | 
 |  * 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, const 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) |