/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * Marvell MV88W8618 / Freecom MusicPal emulation. * * Copyright (c) 2008 Jan Kiszka */ #include "qemu/osdep.h" #include "qapi/error.h" #include "hw/qdev-properties.h" #include "hw/sysbus.h" #include "hw/irq.h" #include "hw/net/mv88w8618_eth.h" #include "migration/vmstate.h" #include "sysemu/dma.h" #include "net/net.h" #define MP_ETH_SIZE 0x00001000 /* Ethernet register offsets */ #define MP_ETH_SMIR 0x010 #define MP_ETH_PCXR 0x408 #define MP_ETH_SDCMR 0x448 #define MP_ETH_ICR 0x450 #define MP_ETH_IMR 0x458 #define MP_ETH_FRDP0 0x480 #define MP_ETH_FRDP1 0x484 #define MP_ETH_FRDP2 0x488 #define MP_ETH_FRDP3 0x48C #define MP_ETH_CRDP0 0x4A0 #define MP_ETH_CRDP1 0x4A4 #define MP_ETH_CRDP2 0x4A8 #define MP_ETH_CRDP3 0x4AC #define MP_ETH_CTDP0 0x4E0 #define MP_ETH_CTDP1 0x4E4 /* MII PHY access */ #define MP_ETH_SMIR_DATA 0x0000FFFF #define MP_ETH_SMIR_ADDR 0x03FF0000 #define MP_ETH_SMIR_OPCODE (1 << 26) /* Read value */ #define MP_ETH_SMIR_RDVALID (1 << 27) /* PHY registers */ #define MP_ETH_PHY1_BMSR 0x00210000 #define MP_ETH_PHY1_PHYSID1 0x00410000 #define MP_ETH_PHY1_PHYSID2 0x00610000 #define MP_PHY_BMSR_LINK 0x0004 #define MP_PHY_BMSR_AUTONEG 0x0008 #define MP_PHY_88E3015 0x01410E20 /* TX descriptor status */ #define MP_ETH_TX_OWN (1U << 31) /* RX descriptor status */ #define MP_ETH_RX_OWN (1U << 31) /* Interrupt cause/mask bits */ #define MP_ETH_IRQ_RX_BIT 0 #define MP_ETH_IRQ_RX (1 << MP_ETH_IRQ_RX_BIT) #define MP_ETH_IRQ_TXHI_BIT 2 #define MP_ETH_IRQ_TXLO_BIT 3 /* Port config bits */ #define MP_ETH_PCXR_2BSM_BIT 28 /* 2-byte incoming suffix */ /* SDMA command bits */ #define MP_ETH_CMD_TXHI (1 << 23) #define MP_ETH_CMD_TXLO (1 << 22) typedef struct mv88w8618_tx_desc { uint32_t cmdstat; uint16_t res; uint16_t bytes; uint32_t buffer; uint32_t next; } mv88w8618_tx_desc; typedef struct mv88w8618_rx_desc { uint32_t cmdstat; uint16_t bytes; uint16_t buffer_size; uint32_t buffer; uint32_t next; } mv88w8618_rx_desc; OBJECT_DECLARE_SIMPLE_TYPE(mv88w8618_eth_state, MV88W8618_ETH) struct mv88w8618_eth_state { /*< private >*/ SysBusDevice parent_obj; /*< public >*/ MemoryRegion iomem; qemu_irq irq; MemoryRegion *dma_mr; AddressSpace dma_as; uint32_t smir; uint32_t icr; uint32_t imr; int mmio_index; uint32_t vlan_header; uint32_t tx_queue[2]; uint32_t rx_queue[4]; uint32_t frx_queue[4]; uint32_t cur_rx[4]; NICState *nic; NICConf conf; }; static void eth_rx_desc_put(AddressSpace *dma_as, uint32_t addr, mv88w8618_rx_desc *desc) { cpu_to_le32s(&desc->cmdstat); cpu_to_le16s(&desc->bytes); cpu_to_le16s(&desc->buffer_size); cpu_to_le32s(&desc->buffer); cpu_to_le32s(&desc->next); dma_memory_write(dma_as, addr, desc, sizeof(*desc), MEMTXATTRS_UNSPECIFIED); } static void eth_rx_desc_get(AddressSpace *dma_as, uint32_t addr, mv88w8618_rx_desc *desc) { dma_memory_read(dma_as, addr, desc, sizeof(*desc), MEMTXATTRS_UNSPECIFIED); le32_to_cpus(&desc->cmdstat); le16_to_cpus(&desc->bytes); le16_to_cpus(&desc->buffer_size); le32_to_cpus(&desc->buffer); le32_to_cpus(&desc->next); } static ssize_t eth_receive(NetClientState *nc, const uint8_t *buf, size_t size) { mv88w8618_eth_state *s = qemu_get_nic_opaque(nc); uint32_t desc_addr; mv88w8618_rx_desc desc; int i; for (i = 0; i < 4; i++) { desc_addr = s->cur_rx[i]; if (!desc_addr) { continue; } do { eth_rx_desc_get(&s->dma_as, desc_addr, &desc); if ((desc.cmdstat & MP_ETH_RX_OWN) && desc.buffer_size >= size) { dma_memory_write(&s->dma_as, desc.buffer + s->vlan_header, buf, size, MEMTXATTRS_UNSPECIFIED); desc.bytes = size + s->vlan_header; desc.cmdstat &= ~MP_ETH_RX_OWN; s->cur_rx[i] = desc.next; s->icr |= MP_ETH_IRQ_RX; if (s->icr & s->imr) { qemu_irq_raise(s->irq); } eth_rx_desc_put(&s->dma_as, desc_addr, &desc); return size; } desc_addr = desc.next; } while (desc_addr != s->rx_queue[i]); } return size; } static void eth_tx_desc_put(AddressSpace *dma_as, uint32_t addr, mv88w8618_tx_desc *desc) { cpu_to_le32s(&desc->cmdstat); cpu_to_le16s(&desc->res); cpu_to_le16s(&desc->bytes); cpu_to_le32s(&desc->buffer); cpu_to_le32s(&desc->next); dma_memory_write(dma_as, addr, desc, sizeof(*desc), MEMTXATTRS_UNSPECIFIED); } static void eth_tx_desc_get(AddressSpace *dma_as, uint32_t addr, mv88w8618_tx_desc *desc) { dma_memory_read(dma_as, addr, desc, sizeof(*desc), MEMTXATTRS_UNSPECIFIED); le32_to_cpus(&desc->cmdstat); le16_to_cpus(&desc->res); le16_to_cpus(&desc->bytes); le32_to_cpus(&desc->buffer); le32_to_cpus(&desc->next); } static void eth_send(mv88w8618_eth_state *s, int queue_index) { uint32_t desc_addr = s->tx_queue[queue_index]; mv88w8618_tx_desc desc; uint32_t next_desc; uint8_t buf[2048]; int len; do { eth_tx_desc_get(&s->dma_as, desc_addr, &desc); next_desc = desc.next; if (desc.cmdstat & MP_ETH_TX_OWN) { len = desc.bytes; if (len < 2048) { dma_memory_read(&s->dma_as, desc.buffer, buf, len, MEMTXATTRS_UNSPECIFIED); qemu_send_packet(qemu_get_queue(s->nic), buf, len); } desc.cmdstat &= ~MP_ETH_TX_OWN; s->icr |= 1 << (MP_ETH_IRQ_TXLO_BIT - queue_index); eth_tx_desc_put(&s->dma_as, desc_addr, &desc); } desc_addr = next_desc; } while (desc_addr != s->tx_queue[queue_index]); } static uint64_t mv88w8618_eth_read(void *opaque, hwaddr offset, unsigned size) { mv88w8618_eth_state *s = opaque; switch (offset) { case MP_ETH_SMIR: if (s->smir & MP_ETH_SMIR_OPCODE) { switch (s->smir & MP_ETH_SMIR_ADDR) { case MP_ETH_PHY1_BMSR: return MP_PHY_BMSR_LINK | MP_PHY_BMSR_AUTONEG | MP_ETH_SMIR_RDVALID; case MP_ETH_PHY1_PHYSID1: return (MP_PHY_88E3015 >> 16) | MP_ETH_SMIR_RDVALID; case MP_ETH_PHY1_PHYSID2: return (MP_PHY_88E3015 & 0xFFFF) | MP_ETH_SMIR_RDVALID; default: return MP_ETH_SMIR_RDVALID; } } return 0; case MP_ETH_ICR: return s->icr; case MP_ETH_IMR: return s->imr; case MP_ETH_FRDP0 ... MP_ETH_FRDP3: return s->frx_queue[(offset - MP_ETH_FRDP0) / 4]; case MP_ETH_CRDP0 ... MP_ETH_CRDP3: return s->rx_queue[(offset - MP_ETH_CRDP0) / 4]; case MP_ETH_CTDP0 ... MP_ETH_CTDP1: return s->tx_queue[(offset - MP_ETH_CTDP0) / 4]; default: return 0; } } static void mv88w8618_eth_write(void *opaque, hwaddr offset, uint64_t value, unsigned size) { mv88w8618_eth_state *s = opaque; switch (offset) { case MP_ETH_SMIR: s->smir = value; break; case MP_ETH_PCXR: s->vlan_header = ((value >> MP_ETH_PCXR_2BSM_BIT) & 1) * 2; break; case MP_ETH_SDCMR: if (value & MP_ETH_CMD_TXHI) { eth_send(s, 1); } if (value & MP_ETH_CMD_TXLO) { eth_send(s, 0); } if (value & (MP_ETH_CMD_TXHI | MP_ETH_CMD_TXLO) && s->icr & s->imr) { qemu_irq_raise(s->irq); } break; case MP_ETH_ICR: s->icr &= value; break; case MP_ETH_IMR: s->imr = value; if (s->icr & s->imr) { qemu_irq_raise(s->irq); } break; case MP_ETH_FRDP0 ... MP_ETH_FRDP3: s->frx_queue[(offset - MP_ETH_FRDP0) / 4] = value; break; case MP_ETH_CRDP0 ... MP_ETH_CRDP3: s->rx_queue[(offset - MP_ETH_CRDP0) / 4] = s->cur_rx[(offset - MP_ETH_CRDP0) / 4] = value; break; case MP_ETH_CTDP0 ... MP_ETH_CTDP1: s->tx_queue[(offset - MP_ETH_CTDP0) / 4] = value; break; } } static const MemoryRegionOps mv88w8618_eth_ops = { .read = mv88w8618_eth_read, .write = mv88w8618_eth_write, .endianness = DEVICE_NATIVE_ENDIAN, }; static void eth_cleanup(NetClientState *nc) { mv88w8618_eth_state *s = qemu_get_nic_opaque(nc); s->nic = NULL; } static NetClientInfo net_mv88w8618_info = { .type = NET_CLIENT_DRIVER_NIC, .size = sizeof(NICState), .receive = eth_receive, .cleanup = eth_cleanup, }; static void mv88w8618_eth_init(Object *obj) { SysBusDevice *sbd = SYS_BUS_DEVICE(obj); DeviceState *dev = DEVICE(sbd); mv88w8618_eth_state *s = MV88W8618_ETH(dev); sysbus_init_irq(sbd, &s->irq); memory_region_init_io(&s->iomem, obj, &mv88w8618_eth_ops, s, "mv88w8618-eth", MP_ETH_SIZE); sysbus_init_mmio(sbd, &s->iomem); } static void mv88w8618_eth_realize(DeviceState *dev, Error **errp) { mv88w8618_eth_state *s = MV88W8618_ETH(dev); if (!s->dma_mr) { error_setg(errp, TYPE_MV88W8618_ETH " 'dma-memory' link not set"); return; } address_space_init(&s->dma_as, s->dma_mr, "emac-dma"); s->nic = qemu_new_nic(&net_mv88w8618_info, &s->conf, object_get_typename(OBJECT(dev)), dev->id, &dev->mem_reentrancy_guard, s); } static const VMStateDescription mv88w8618_eth_vmsd = { .name = "mv88w8618_eth", .version_id = 1, .minimum_version_id = 1, .fields = (VMStateField[]) { VMSTATE_UINT32(smir, mv88w8618_eth_state), VMSTATE_UINT32(icr, mv88w8618_eth_state), VMSTATE_UINT32(imr, mv88w8618_eth_state), VMSTATE_UINT32(vlan_header, mv88w8618_eth_state), VMSTATE_UINT32_ARRAY(tx_queue, mv88w8618_eth_state, 2), VMSTATE_UINT32_ARRAY(rx_queue, mv88w8618_eth_state, 4), VMSTATE_UINT32_ARRAY(frx_queue, mv88w8618_eth_state, 4), VMSTATE_UINT32_ARRAY(cur_rx, mv88w8618_eth_state, 4), VMSTATE_END_OF_LIST() } }; static Property mv88w8618_eth_properties[] = { DEFINE_NIC_PROPERTIES(mv88w8618_eth_state, conf), DEFINE_PROP_LINK("dma-memory", mv88w8618_eth_state, dma_mr, TYPE_MEMORY_REGION, MemoryRegion *), DEFINE_PROP_END_OF_LIST(), }; static void mv88w8618_eth_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); dc->vmsd = &mv88w8618_eth_vmsd; device_class_set_props(dc, mv88w8618_eth_properties); dc->realize = mv88w8618_eth_realize; } static const TypeInfo mv88w8618_eth_info = { .name = TYPE_MV88W8618_ETH, .parent = TYPE_SYS_BUS_DEVICE, .instance_size = sizeof(mv88w8618_eth_state), .instance_init = mv88w8618_eth_init, .class_init = mv88w8618_eth_class_init, }; static void musicpal_register_types(void) { type_register_static(&mv88w8618_eth_info); } type_init(musicpal_register_types)