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