| /* |
| * QEMU Sun Happy Meal Ethernet emulation |
| * |
| * Copyright (c) 2017 Mark Cave-Ayland |
| * |
| * 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 "hw/pci/pci_device.h" |
| #include "hw/qdev-properties.h" |
| #include "migration/vmstate.h" |
| #include "hw/net/mii.h" |
| #include "net/net.h" |
| #include "qemu/module.h" |
| #include "net/checksum.h" |
| #include "net/eth.h" |
| #include "sysemu/sysemu.h" |
| #include "trace.h" |
| #include "qom/object.h" |
| |
| #define HME_REG_SIZE 0x8000 |
| |
| #define HME_SEB_REG_SIZE 0x2000 |
| |
| #define HME_SEBI_RESET 0x0 |
| #define HME_SEB_RESET_ETX 0x1 |
| #define HME_SEB_RESET_ERX 0x2 |
| |
| #define HME_SEBI_STAT 0x100 |
| #define HME_SEBI_STAT_LINUXBUG 0x108 |
| #define HME_SEB_STAT_RXTOHOST 0x10000 |
| #define HME_SEB_STAT_NORXD 0x20000 |
| #define HME_SEB_STAT_MIFIRQ 0x800000 |
| #define HME_SEB_STAT_HOSTTOTX 0x1000000 |
| #define HME_SEB_STAT_TXALL 0x2000000 |
| |
| #define HME_SEBI_IMASK 0x104 |
| #define HME_SEBI_IMASK_LINUXBUG 0x10c |
| |
| #define HME_ETX_REG_SIZE 0x2000 |
| |
| #define HME_ETXI_PENDING 0x0 |
| |
| #define HME_ETXI_RING 0x8 |
| #define HME_ETXI_RING_ADDR 0xffffff00 |
| #define HME_ETXI_RING_OFFSET 0xff |
| |
| #define HME_ETXI_RSIZE 0x2c |
| |
| #define HME_ERX_REG_SIZE 0x2000 |
| |
| #define HME_ERXI_CFG 0x0 |
| #define HME_ERX_CFG_RINGSIZE 0x600 |
| #define HME_ERX_CFG_RINGSIZE_SHIFT 9 |
| #define HME_ERX_CFG_BYTEOFFSET 0x38 |
| #define HME_ERX_CFG_BYTEOFFSET_SHIFT 3 |
| #define HME_ERX_CFG_CSUMSTART 0x7f0000 |
| #define HME_ERX_CFG_CSUMSHIFT 16 |
| |
| #define HME_ERXI_RING 0x4 |
| #define HME_ERXI_RING_ADDR 0xffffff00 |
| #define HME_ERXI_RING_OFFSET 0xff |
| |
| #define HME_MAC_REG_SIZE 0x1000 |
| |
| #define HME_MACI_TXCFG 0x20c |
| #define HME_MAC_TXCFG_ENABLE 0x1 |
| |
| #define HME_MACI_RXCFG 0x30c |
| #define HME_MAC_RXCFG_ENABLE 0x1 |
| #define HME_MAC_RXCFG_PMISC 0x40 |
| #define HME_MAC_RXCFG_HENABLE 0x800 |
| |
| #define HME_MACI_MACADDR2 0x318 |
| #define HME_MACI_MACADDR1 0x31c |
| #define HME_MACI_MACADDR0 0x320 |
| |
| #define HME_MACI_HASHTAB3 0x340 |
| #define HME_MACI_HASHTAB2 0x344 |
| #define HME_MACI_HASHTAB1 0x348 |
| #define HME_MACI_HASHTAB0 0x34c |
| |
| #define HME_MIF_REG_SIZE 0x20 |
| |
| #define HME_MIFI_FO 0xc |
| #define HME_MIF_FO_ST 0xc0000000 |
| #define HME_MIF_FO_ST_SHIFT 30 |
| #define HME_MIF_FO_OPC 0x30000000 |
| #define HME_MIF_FO_OPC_SHIFT 28 |
| #define HME_MIF_FO_PHYAD 0x0f800000 |
| #define HME_MIF_FO_PHYAD_SHIFT 23 |
| #define HME_MIF_FO_REGAD 0x007c0000 |
| #define HME_MIF_FO_REGAD_SHIFT 18 |
| #define HME_MIF_FO_TAMSB 0x20000 |
| #define HME_MIF_FO_TALSB 0x10000 |
| #define HME_MIF_FO_DATA 0xffff |
| |
| #define HME_MIFI_CFG 0x10 |
| #define HME_MIF_CFG_MDI0 0x100 |
| #define HME_MIF_CFG_MDI1 0x200 |
| |
| #define HME_MIFI_IMASK 0x14 |
| |
| #define HME_MIFI_STAT 0x18 |
| |
| |
| /* Wired HME PHY addresses */ |
| #define HME_PHYAD_INTERNAL 1 |
| #define HME_PHYAD_EXTERNAL 0 |
| |
| #define MII_COMMAND_START 0x1 |
| #define MII_COMMAND_READ 0x2 |
| #define MII_COMMAND_WRITE 0x1 |
| |
| #define TYPE_SUNHME "sunhme" |
| OBJECT_DECLARE_SIMPLE_TYPE(SunHMEState, SUNHME) |
| |
| /* Maximum size of buffer */ |
| #define HME_FIFO_SIZE 0x800 |
| |
| /* Size of TX/RX descriptor */ |
| #define HME_DESC_SIZE 0x8 |
| |
| #define HME_XD_OWN 0x80000000 |
| #define HME_XD_OFL 0x40000000 |
| #define HME_XD_SOP 0x40000000 |
| #define HME_XD_EOP 0x20000000 |
| #define HME_XD_RXLENMSK 0x3fff0000 |
| #define HME_XD_RXLENSHIFT 16 |
| #define HME_XD_RXCKSUM 0xffff |
| #define HME_XD_TXLENMSK 0x00001fff |
| #define HME_XD_TXCKSUM 0x10000000 |
| #define HME_XD_TXCSSTUFF 0xff00000 |
| #define HME_XD_TXCSSTUFFSHIFT 20 |
| #define HME_XD_TXCSSTART 0xfc000 |
| #define HME_XD_TXCSSTARTSHIFT 14 |
| |
| #define HME_MII_REGS_SIZE 0x20 |
| |
| struct SunHMEState { |
| /*< private >*/ |
| PCIDevice parent_obj; |
| |
| NICState *nic; |
| NICConf conf; |
| |
| MemoryRegion hme; |
| MemoryRegion sebreg; |
| MemoryRegion etxreg; |
| MemoryRegion erxreg; |
| MemoryRegion macreg; |
| MemoryRegion mifreg; |
| |
| uint32_t sebregs[HME_SEB_REG_SIZE >> 2]; |
| uint32_t etxregs[HME_ETX_REG_SIZE >> 2]; |
| uint32_t erxregs[HME_ERX_REG_SIZE >> 2]; |
| uint32_t macregs[HME_MAC_REG_SIZE >> 2]; |
| uint32_t mifregs[HME_MIF_REG_SIZE >> 2]; |
| |
| uint16_t miiregs[HME_MII_REGS_SIZE]; |
| }; |
| |
| static Property sunhme_properties[] = { |
| DEFINE_NIC_PROPERTIES(SunHMEState, conf), |
| DEFINE_PROP_END_OF_LIST(), |
| }; |
| |
| static void sunhme_reset_tx(SunHMEState *s) |
| { |
| /* Indicate TX reset complete */ |
| s->sebregs[HME_SEBI_RESET] &= ~HME_SEB_RESET_ETX; |
| } |
| |
| static void sunhme_reset_rx(SunHMEState *s) |
| { |
| /* Indicate RX reset complete */ |
| s->sebregs[HME_SEBI_RESET] &= ~HME_SEB_RESET_ERX; |
| } |
| |
| static void sunhme_update_irq(SunHMEState *s) |
| { |
| PCIDevice *d = PCI_DEVICE(s); |
| int level; |
| |
| /* MIF interrupt mask (16-bit) */ |
| uint32_t mifmask = ~(s->mifregs[HME_MIFI_IMASK >> 2]) & 0xffff; |
| uint32_t mif = s->mifregs[HME_MIFI_STAT >> 2] & mifmask; |
| |
| /* Main SEB interrupt mask (include MIF status from above) */ |
| uint32_t sebmask = ~(s->sebregs[HME_SEBI_IMASK >> 2]) & |
| ~HME_SEB_STAT_MIFIRQ; |
| uint32_t seb = s->sebregs[HME_SEBI_STAT >> 2] & sebmask; |
| if (mif) { |
| seb |= HME_SEB_STAT_MIFIRQ; |
| } |
| |
| level = (seb ? 1 : 0); |
| trace_sunhme_update_irq(mifmask, mif, sebmask, seb, level); |
| |
| pci_set_irq(d, level); |
| } |
| |
| static void sunhme_seb_write(void *opaque, hwaddr addr, |
| uint64_t val, unsigned size) |
| { |
| SunHMEState *s = SUNHME(opaque); |
| |
| trace_sunhme_seb_write(addr, val); |
| |
| /* Handly buggy Linux drivers before 4.13 which have |
| the wrong offsets for HME_SEBI_STAT and HME_SEBI_IMASK */ |
| switch (addr) { |
| case HME_SEBI_STAT_LINUXBUG: |
| addr = HME_SEBI_STAT; |
| break; |
| case HME_SEBI_IMASK_LINUXBUG: |
| addr = HME_SEBI_IMASK; |
| break; |
| default: |
| break; |
| } |
| |
| switch (addr) { |
| case HME_SEBI_RESET: |
| if (val & HME_SEB_RESET_ETX) { |
| sunhme_reset_tx(s); |
| } |
| if (val & HME_SEB_RESET_ERX) { |
| sunhme_reset_rx(s); |
| } |
| val = s->sebregs[HME_SEBI_RESET >> 2]; |
| break; |
| } |
| |
| s->sebregs[addr >> 2] = val; |
| } |
| |
| static uint64_t sunhme_seb_read(void *opaque, hwaddr addr, |
| unsigned size) |
| { |
| SunHMEState *s = SUNHME(opaque); |
| uint64_t val; |
| |
| /* Handly buggy Linux drivers before 4.13 which have |
| the wrong offsets for HME_SEBI_STAT and HME_SEBI_IMASK */ |
| switch (addr) { |
| case HME_SEBI_STAT_LINUXBUG: |
| addr = HME_SEBI_STAT; |
| break; |
| case HME_SEBI_IMASK_LINUXBUG: |
| addr = HME_SEBI_IMASK; |
| break; |
| default: |
| break; |
| } |
| |
| val = s->sebregs[addr >> 2]; |
| |
| switch (addr) { |
| case HME_SEBI_STAT: |
| /* Autoclear status (except MIF) */ |
| s->sebregs[HME_SEBI_STAT >> 2] &= HME_SEB_STAT_MIFIRQ; |
| sunhme_update_irq(s); |
| break; |
| } |
| |
| trace_sunhme_seb_read(addr, val); |
| |
| return val; |
| } |
| |
| static const MemoryRegionOps sunhme_seb_ops = { |
| .read = sunhme_seb_read, |
| .write = sunhme_seb_write, |
| .endianness = DEVICE_LITTLE_ENDIAN, |
| .valid = { |
| .min_access_size = 4, |
| .max_access_size = 4, |
| }, |
| }; |
| |
| static void sunhme_transmit(SunHMEState *s); |
| |
| static void sunhme_etx_write(void *opaque, hwaddr addr, |
| uint64_t val, unsigned size) |
| { |
| SunHMEState *s = SUNHME(opaque); |
| |
| trace_sunhme_etx_write(addr, val); |
| |
| switch (addr) { |
| case HME_ETXI_PENDING: |
| if (val) { |
| sunhme_transmit(s); |
| } |
| break; |
| } |
| |
| s->etxregs[addr >> 2] = val; |
| } |
| |
| static uint64_t sunhme_etx_read(void *opaque, hwaddr addr, |
| unsigned size) |
| { |
| SunHMEState *s = SUNHME(opaque); |
| uint64_t val; |
| |
| val = s->etxregs[addr >> 2]; |
| |
| trace_sunhme_etx_read(addr, val); |
| |
| return val; |
| } |
| |
| static const MemoryRegionOps sunhme_etx_ops = { |
| .read = sunhme_etx_read, |
| .write = sunhme_etx_write, |
| .endianness = DEVICE_LITTLE_ENDIAN, |
| .valid = { |
| .min_access_size = 4, |
| .max_access_size = 4, |
| }, |
| }; |
| |
| static void sunhme_erx_write(void *opaque, hwaddr addr, |
| uint64_t val, unsigned size) |
| { |
| SunHMEState *s = SUNHME(opaque); |
| |
| trace_sunhme_erx_write(addr, val); |
| |
| s->erxregs[addr >> 2] = val; |
| } |
| |
| static uint64_t sunhme_erx_read(void *opaque, hwaddr addr, |
| unsigned size) |
| { |
| SunHMEState *s = SUNHME(opaque); |
| uint64_t val; |
| |
| val = s->erxregs[addr >> 2]; |
| |
| trace_sunhme_erx_read(addr, val); |
| |
| return val; |
| } |
| |
| static const MemoryRegionOps sunhme_erx_ops = { |
| .read = sunhme_erx_read, |
| .write = sunhme_erx_write, |
| .endianness = DEVICE_LITTLE_ENDIAN, |
| .valid = { |
| .min_access_size = 4, |
| .max_access_size = 4, |
| }, |
| }; |
| |
| static void sunhme_mac_write(void *opaque, hwaddr addr, |
| uint64_t val, unsigned size) |
| { |
| SunHMEState *s = SUNHME(opaque); |
| uint64_t oldval = s->macregs[addr >> 2]; |
| |
| trace_sunhme_mac_write(addr, val); |
| |
| s->macregs[addr >> 2] = val; |
| |
| switch (addr) { |
| case HME_MACI_RXCFG: |
| if (!(oldval & HME_MAC_RXCFG_ENABLE) && |
| (val & HME_MAC_RXCFG_ENABLE)) { |
| qemu_flush_queued_packets(qemu_get_queue(s->nic)); |
| } |
| break; |
| } |
| } |
| |
| static uint64_t sunhme_mac_read(void *opaque, hwaddr addr, |
| unsigned size) |
| { |
| SunHMEState *s = SUNHME(opaque); |
| uint64_t val; |
| |
| val = s->macregs[addr >> 2]; |
| |
| trace_sunhme_mac_read(addr, val); |
| |
| return val; |
| } |
| |
| static const MemoryRegionOps sunhme_mac_ops = { |
| .read = sunhme_mac_read, |
| .write = sunhme_mac_write, |
| .endianness = DEVICE_LITTLE_ENDIAN, |
| .valid = { |
| .min_access_size = 4, |
| .max_access_size = 4, |
| }, |
| }; |
| |
| static void sunhme_mii_write(SunHMEState *s, uint8_t reg, uint16_t data) |
| { |
| trace_sunhme_mii_write(reg, data); |
| |
| switch (reg) { |
| case MII_BMCR: |
| if (data & MII_BMCR_RESET) { |
| /* Autoclear reset bit, enable auto negotiation */ |
| data &= ~MII_BMCR_RESET; |
| data |= MII_BMCR_AUTOEN; |
| } |
| if (data & MII_BMCR_ANRESTART) { |
| /* Autoclear auto negotiation restart */ |
| data &= ~MII_BMCR_ANRESTART; |
| |
| /* Indicate negotiation complete */ |
| s->miiregs[MII_BMSR] |= MII_BMSR_AN_COMP; |
| |
| if (!qemu_get_queue(s->nic)->link_down) { |
| s->miiregs[MII_ANLPAR] |= MII_ANLPAR_TXFD; |
| s->miiregs[MII_BMSR] |= MII_BMSR_LINK_ST; |
| } |
| } |
| break; |
| } |
| |
| s->miiregs[reg] = data; |
| } |
| |
| static uint16_t sunhme_mii_read(SunHMEState *s, uint8_t reg) |
| { |
| uint16_t data = s->miiregs[reg]; |
| |
| trace_sunhme_mii_read(reg, data); |
| |
| return data; |
| } |
| |
| static void sunhme_mif_write(void *opaque, hwaddr addr, |
| uint64_t val, unsigned size) |
| { |
| SunHMEState *s = SUNHME(opaque); |
| uint8_t cmd, reg; |
| uint16_t data; |
| |
| trace_sunhme_mif_write(addr, val); |
| |
| switch (addr) { |
| case HME_MIFI_CFG: |
| /* Mask the read-only bits */ |
| val &= ~(HME_MIF_CFG_MDI0 | HME_MIF_CFG_MDI1); |
| val |= s->mifregs[HME_MIFI_CFG >> 2] & |
| (HME_MIF_CFG_MDI0 | HME_MIF_CFG_MDI1); |
| break; |
| case HME_MIFI_FO: |
| /* Detect start of MII command */ |
| if ((val & HME_MIF_FO_ST) >> HME_MIF_FO_ST_SHIFT |
| != MII_COMMAND_START) { |
| val |= HME_MIF_FO_TALSB; |
| break; |
| } |
| |
| /* Internal phy only */ |
| if ((val & HME_MIF_FO_PHYAD) >> HME_MIF_FO_PHYAD_SHIFT |
| != HME_PHYAD_INTERNAL) { |
| val |= HME_MIF_FO_TALSB; |
| break; |
| } |
| |
| cmd = (val & HME_MIF_FO_OPC) >> HME_MIF_FO_OPC_SHIFT; |
| reg = (val & HME_MIF_FO_REGAD) >> HME_MIF_FO_REGAD_SHIFT; |
| data = (val & HME_MIF_FO_DATA); |
| |
| switch (cmd) { |
| case MII_COMMAND_WRITE: |
| sunhme_mii_write(s, reg, data); |
| break; |
| |
| case MII_COMMAND_READ: |
| val &= ~HME_MIF_FO_DATA; |
| val |= sunhme_mii_read(s, reg); |
| break; |
| } |
| |
| val |= HME_MIF_FO_TALSB; |
| break; |
| } |
| |
| s->mifregs[addr >> 2] = val; |
| } |
| |
| static uint64_t sunhme_mif_read(void *opaque, hwaddr addr, |
| unsigned size) |
| { |
| SunHMEState *s = SUNHME(opaque); |
| uint64_t val; |
| |
| val = s->mifregs[addr >> 2]; |
| |
| switch (addr) { |
| case HME_MIFI_STAT: |
| /* Autoclear MIF interrupt status */ |
| s->mifregs[HME_MIFI_STAT >> 2] = 0; |
| sunhme_update_irq(s); |
| break; |
| } |
| |
| trace_sunhme_mif_read(addr, val); |
| |
| return val; |
| } |
| |
| static const MemoryRegionOps sunhme_mif_ops = { |
| .read = sunhme_mif_read, |
| .write = sunhme_mif_write, |
| .endianness = DEVICE_LITTLE_ENDIAN, |
| .valid = { |
| .min_access_size = 4, |
| .max_access_size = 4, |
| }, |
| }; |
| |
| static void sunhme_transmit_frame(SunHMEState *s, uint8_t *buf, int size) |
| { |
| qemu_send_packet(qemu_get_queue(s->nic), buf, size); |
| } |
| |
| static inline int sunhme_get_tx_ring_count(SunHMEState *s) |
| { |
| return (s->etxregs[HME_ETXI_RSIZE >> 2] + 1) << 4; |
| } |
| |
| static inline int sunhme_get_tx_ring_nr(SunHMEState *s) |
| { |
| return s->etxregs[HME_ETXI_RING >> 2] & HME_ETXI_RING_OFFSET; |
| } |
| |
| static inline void sunhme_set_tx_ring_nr(SunHMEState *s, int i) |
| { |
| uint32_t ring = s->etxregs[HME_ETXI_RING >> 2] & ~HME_ETXI_RING_OFFSET; |
| ring |= i & HME_ETXI_RING_OFFSET; |
| |
| s->etxregs[HME_ETXI_RING >> 2] = ring; |
| } |
| |
| static void sunhme_transmit(SunHMEState *s) |
| { |
| PCIDevice *d = PCI_DEVICE(s); |
| dma_addr_t tb, addr; |
| uint32_t intstatus, status, buffer, sum = 0; |
| int cr, nr, len, xmit_pos, csum_offset = 0, csum_stuff_offset = 0; |
| uint16_t csum = 0; |
| uint8_t xmit_buffer[HME_FIFO_SIZE]; |
| |
| tb = s->etxregs[HME_ETXI_RING >> 2] & HME_ETXI_RING_ADDR; |
| nr = sunhme_get_tx_ring_count(s); |
| cr = sunhme_get_tx_ring_nr(s); |
| |
| pci_dma_read(d, tb + cr * HME_DESC_SIZE, &status, 4); |
| pci_dma_read(d, tb + cr * HME_DESC_SIZE + 4, &buffer, 4); |
| |
| xmit_pos = 0; |
| while (status & HME_XD_OWN) { |
| trace_sunhme_tx_desc(buffer, status, cr, nr); |
| |
| /* Copy data into transmit buffer */ |
| addr = buffer; |
| len = status & HME_XD_TXLENMSK; |
| |
| if (xmit_pos + len > HME_FIFO_SIZE) { |
| len = HME_FIFO_SIZE - xmit_pos; |
| } |
| |
| pci_dma_read(d, addr, &xmit_buffer[xmit_pos], len); |
| xmit_pos += len; |
| |
| /* Detect start of packet for TX checksum */ |
| if (status & HME_XD_SOP) { |
| sum = 0; |
| csum_offset = (status & HME_XD_TXCSSTART) >> HME_XD_TXCSSTARTSHIFT; |
| csum_stuff_offset = (status & HME_XD_TXCSSTUFF) >> |
| HME_XD_TXCSSTUFFSHIFT; |
| } |
| |
| if (status & HME_XD_TXCKSUM) { |
| /* Only start calculation from csum_offset */ |
| if (xmit_pos - len <= csum_offset && xmit_pos > csum_offset) { |
| sum += net_checksum_add(xmit_pos - csum_offset, |
| xmit_buffer + csum_offset); |
| trace_sunhme_tx_xsum_add(csum_offset, xmit_pos - csum_offset); |
| } else { |
| sum += net_checksum_add(len, xmit_buffer + xmit_pos - len); |
| trace_sunhme_tx_xsum_add(xmit_pos - len, len); |
| } |
| } |
| |
| /* Detect end of packet for TX checksum */ |
| if (status & HME_XD_EOP) { |
| /* Stuff the checksum if required */ |
| if (status & HME_XD_TXCKSUM) { |
| csum = net_checksum_finish(sum); |
| stw_be_p(xmit_buffer + csum_stuff_offset, csum); |
| trace_sunhme_tx_xsum_stuff(csum, csum_stuff_offset); |
| } |
| |
| if (s->macregs[HME_MACI_TXCFG >> 2] & HME_MAC_TXCFG_ENABLE) { |
| sunhme_transmit_frame(s, xmit_buffer, xmit_pos); |
| trace_sunhme_tx_done(xmit_pos); |
| } |
| } |
| |
| /* Update status */ |
| status &= ~HME_XD_OWN; |
| pci_dma_write(d, tb + cr * HME_DESC_SIZE, &status, 4); |
| |
| /* Move onto next descriptor */ |
| cr++; |
| if (cr >= nr) { |
| cr = 0; |
| } |
| sunhme_set_tx_ring_nr(s, cr); |
| |
| pci_dma_read(d, tb + cr * HME_DESC_SIZE, &status, 4); |
| pci_dma_read(d, tb + cr * HME_DESC_SIZE + 4, &buffer, 4); |
| |
| /* Indicate TX complete */ |
| intstatus = s->sebregs[HME_SEBI_STAT >> 2]; |
| intstatus |= HME_SEB_STAT_HOSTTOTX; |
| s->sebregs[HME_SEBI_STAT >> 2] = intstatus; |
| |
| /* Autoclear TX pending */ |
| s->etxregs[HME_ETXI_PENDING >> 2] = 0; |
| |
| sunhme_update_irq(s); |
| } |
| |
| /* TX FIFO now clear */ |
| intstatus = s->sebregs[HME_SEBI_STAT >> 2]; |
| intstatus |= HME_SEB_STAT_TXALL; |
| s->sebregs[HME_SEBI_STAT >> 2] = intstatus; |
| sunhme_update_irq(s); |
| } |
| |
| static bool sunhme_can_receive(NetClientState *nc) |
| { |
| SunHMEState *s = qemu_get_nic_opaque(nc); |
| |
| return !!(s->macregs[HME_MACI_RXCFG >> 2] & HME_MAC_RXCFG_ENABLE); |
| } |
| |
| static void sunhme_link_status_changed(NetClientState *nc) |
| { |
| SunHMEState *s = qemu_get_nic_opaque(nc); |
| |
| if (nc->link_down) { |
| s->miiregs[MII_ANLPAR] &= ~MII_ANLPAR_TXFD; |
| s->miiregs[MII_BMSR] &= ~MII_BMSR_LINK_ST; |
| } else { |
| s->miiregs[MII_ANLPAR] |= MII_ANLPAR_TXFD; |
| s->miiregs[MII_BMSR] |= MII_BMSR_LINK_ST; |
| } |
| |
| /* Exact bits unknown */ |
| s->mifregs[HME_MIFI_STAT >> 2] = 0xffff; |
| sunhme_update_irq(s); |
| } |
| |
| static inline int sunhme_get_rx_ring_count(SunHMEState *s) |
| { |
| uint32_t rings = (s->erxregs[HME_ERXI_CFG >> 2] & HME_ERX_CFG_RINGSIZE) |
| >> HME_ERX_CFG_RINGSIZE_SHIFT; |
| |
| switch (rings) { |
| case 0: |
| return 32; |
| case 1: |
| return 64; |
| case 2: |
| return 128; |
| case 3: |
| return 256; |
| } |
| |
| return 0; |
| } |
| |
| static inline int sunhme_get_rx_ring_nr(SunHMEState *s) |
| { |
| return s->erxregs[HME_ERXI_RING >> 2] & HME_ERXI_RING_OFFSET; |
| } |
| |
| static inline void sunhme_set_rx_ring_nr(SunHMEState *s, int i) |
| { |
| uint32_t ring = s->erxregs[HME_ERXI_RING >> 2] & ~HME_ERXI_RING_OFFSET; |
| ring |= i & HME_ERXI_RING_OFFSET; |
| |
| s->erxregs[HME_ERXI_RING >> 2] = ring; |
| } |
| |
| static ssize_t sunhme_receive(NetClientState *nc, const uint8_t *buf, |
| size_t size) |
| { |
| SunHMEState *s = qemu_get_nic_opaque(nc); |
| PCIDevice *d = PCI_DEVICE(s); |
| dma_addr_t rb, addr; |
| uint32_t intstatus, status, buffer, buffersize, sum; |
| uint16_t csum; |
| int nr, cr, len, rxoffset, csum_offset; |
| |
| trace_sunhme_rx_incoming(size); |
| |
| /* Do nothing if MAC RX disabled */ |
| if (!(s->macregs[HME_MACI_RXCFG >> 2] & HME_MAC_RXCFG_ENABLE)) { |
| return 0; |
| } |
| |
| trace_sunhme_rx_filter_destmac(buf[0], buf[1], buf[2], |
| buf[3], buf[4], buf[5]); |
| |
| /* Check destination MAC address */ |
| if (!(s->macregs[HME_MACI_RXCFG >> 2] & HME_MAC_RXCFG_PMISC)) { |
| /* Try and match local MAC address */ |
| if (((s->macregs[HME_MACI_MACADDR0 >> 2] & 0xff00) >> 8) == buf[0] && |
| (s->macregs[HME_MACI_MACADDR0 >> 2] & 0xff) == buf[1] && |
| ((s->macregs[HME_MACI_MACADDR1 >> 2] & 0xff00) >> 8) == buf[2] && |
| (s->macregs[HME_MACI_MACADDR1 >> 2] & 0xff) == buf[3] && |
| ((s->macregs[HME_MACI_MACADDR2 >> 2] & 0xff00) >> 8) == buf[4] && |
| (s->macregs[HME_MACI_MACADDR2 >> 2] & 0xff) == buf[5]) { |
| /* Matched local MAC address */ |
| trace_sunhme_rx_filter_local_match(); |
| } else if (buf[0] == 0xff && buf[1] == 0xff && buf[2] == 0xff && |
| buf[3] == 0xff && buf[4] == 0xff && buf[5] == 0xff) { |
| /* Matched broadcast address */ |
| trace_sunhme_rx_filter_bcast_match(); |
| } else if (s->macregs[HME_MACI_RXCFG >> 2] & HME_MAC_RXCFG_HENABLE) { |
| /* Didn't match local address, check hash filter */ |
| int mcast_idx = net_crc32_le(buf, ETH_ALEN) >> 26; |
| if (!(s->macregs[(HME_MACI_HASHTAB0 >> 2) - (mcast_idx >> 4)] & |
| (1 << (mcast_idx & 0xf)))) { |
| /* Didn't match hash filter */ |
| trace_sunhme_rx_filter_hash_nomatch(); |
| trace_sunhme_rx_filter_reject(); |
| return -1; |
| } else { |
| trace_sunhme_rx_filter_hash_match(); |
| } |
| } else { |
| /* Not for us */ |
| trace_sunhme_rx_filter_reject(); |
| return -1; |
| } |
| } else { |
| trace_sunhme_rx_filter_promisc_match(); |
| } |
| |
| trace_sunhme_rx_filter_accept(); |
| |
| rb = s->erxregs[HME_ERXI_RING >> 2] & HME_ERXI_RING_ADDR; |
| nr = sunhme_get_rx_ring_count(s); |
| cr = sunhme_get_rx_ring_nr(s); |
| |
| pci_dma_read(d, rb + cr * HME_DESC_SIZE, &status, 4); |
| pci_dma_read(d, rb + cr * HME_DESC_SIZE + 4, &buffer, 4); |
| |
| /* If we don't own the current descriptor then indicate overflow error */ |
| if (!(status & HME_XD_OWN)) { |
| s->sebregs[HME_SEBI_STAT >> 2] |= HME_SEB_STAT_NORXD; |
| sunhme_update_irq(s); |
| trace_sunhme_rx_norxd(); |
| return -1; |
| } |
| |
| rxoffset = (s->erxregs[HME_ERXI_CFG >> 2] & HME_ERX_CFG_BYTEOFFSET) >> |
| HME_ERX_CFG_BYTEOFFSET_SHIFT; |
| |
| addr = buffer + rxoffset; |
| buffersize = (status & HME_XD_RXLENMSK) >> HME_XD_RXLENSHIFT; |
| |
| /* Detect receive overflow */ |
| len = size; |
| if (size > buffersize) { |
| status |= HME_XD_OFL; |
| len = buffersize; |
| } |
| |
| pci_dma_write(d, addr, buf, len); |
| |
| trace_sunhme_rx_desc(buffer, rxoffset, status, len, cr, nr); |
| |
| /* Calculate the receive checksum */ |
| csum_offset = (s->erxregs[HME_ERXI_CFG >> 2] & HME_ERX_CFG_CSUMSTART) >> |
| HME_ERX_CFG_CSUMSHIFT << 1; |
| sum = 0; |
| sum += net_checksum_add(len - csum_offset, (uint8_t *)buf + csum_offset); |
| csum = net_checksum_finish(sum); |
| |
| trace_sunhme_rx_xsum_calc(csum); |
| |
| /* Update status */ |
| status &= ~HME_XD_OWN; |
| status &= ~HME_XD_RXLENMSK; |
| status |= len << HME_XD_RXLENSHIFT; |
| status &= ~HME_XD_RXCKSUM; |
| status |= csum; |
| |
| pci_dma_write(d, rb + cr * HME_DESC_SIZE, &status, 4); |
| |
| cr++; |
| if (cr >= nr) { |
| cr = 0; |
| } |
| |
| sunhme_set_rx_ring_nr(s, cr); |
| |
| /* Indicate RX complete */ |
| intstatus = s->sebregs[HME_SEBI_STAT >> 2]; |
| intstatus |= HME_SEB_STAT_RXTOHOST; |
| s->sebregs[HME_SEBI_STAT >> 2] = intstatus; |
| |
| sunhme_update_irq(s); |
| |
| return len; |
| } |
| |
| static NetClientInfo net_sunhme_info = { |
| .type = NET_CLIENT_DRIVER_NIC, |
| .size = sizeof(NICState), |
| .can_receive = sunhme_can_receive, |
| .receive = sunhme_receive, |
| .link_status_changed = sunhme_link_status_changed, |
| }; |
| |
| static void sunhme_realize(PCIDevice *pci_dev, Error **errp) |
| { |
| SunHMEState *s = SUNHME(pci_dev); |
| DeviceState *d = DEVICE(pci_dev); |
| uint8_t *pci_conf; |
| |
| pci_conf = pci_dev->config; |
| pci_conf[PCI_INTERRUPT_PIN] = 1; /* interrupt pin A */ |
| |
| memory_region_init(&s->hme, OBJECT(pci_dev), "sunhme", HME_REG_SIZE); |
| pci_register_bar(pci_dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->hme); |
| |
| memory_region_init_io(&s->sebreg, OBJECT(pci_dev), &sunhme_seb_ops, s, |
| "sunhme.seb", HME_SEB_REG_SIZE); |
| memory_region_add_subregion(&s->hme, 0, &s->sebreg); |
| |
| memory_region_init_io(&s->etxreg, OBJECT(pci_dev), &sunhme_etx_ops, s, |
| "sunhme.etx", HME_ETX_REG_SIZE); |
| memory_region_add_subregion(&s->hme, 0x2000, &s->etxreg); |
| |
| memory_region_init_io(&s->erxreg, OBJECT(pci_dev), &sunhme_erx_ops, s, |
| "sunhme.erx", HME_ERX_REG_SIZE); |
| memory_region_add_subregion(&s->hme, 0x4000, &s->erxreg); |
| |
| memory_region_init_io(&s->macreg, OBJECT(pci_dev), &sunhme_mac_ops, s, |
| "sunhme.mac", HME_MAC_REG_SIZE); |
| memory_region_add_subregion(&s->hme, 0x6000, &s->macreg); |
| |
| memory_region_init_io(&s->mifreg, OBJECT(pci_dev), &sunhme_mif_ops, s, |
| "sunhme.mif", HME_MIF_REG_SIZE); |
| memory_region_add_subregion(&s->hme, 0x7000, &s->mifreg); |
| |
| qemu_macaddr_default_if_unset(&s->conf.macaddr); |
| s->nic = qemu_new_nic(&net_sunhme_info, &s->conf, |
| object_get_typename(OBJECT(d)), d->id, |
| &d->mem_reentrancy_guard, s); |
| qemu_format_nic_info_str(qemu_get_queue(s->nic), s->conf.macaddr.a); |
| } |
| |
| static void sunhme_instance_init(Object *obj) |
| { |
| SunHMEState *s = SUNHME(obj); |
| |
| device_add_bootindex_property(obj, &s->conf.bootindex, |
| "bootindex", "/ethernet-phy@0", |
| DEVICE(obj)); |
| } |
| |
| static void sunhme_reset(DeviceState *ds) |
| { |
| SunHMEState *s = SUNHME(ds); |
| |
| /* Configure internal transceiver */ |
| s->mifregs[HME_MIFI_CFG >> 2] |= HME_MIF_CFG_MDI0; |
| |
| /* Advertise auto, 100Mbps FD */ |
| s->miiregs[MII_ANAR] = MII_ANAR_TXFD; |
| s->miiregs[MII_BMSR] = MII_BMSR_AUTONEG | MII_BMSR_100TX_FD | |
| MII_BMSR_AN_COMP; |
| |
| if (!qemu_get_queue(s->nic)->link_down) { |
| s->miiregs[MII_ANLPAR] |= MII_ANLPAR_TXFD; |
| s->miiregs[MII_BMSR] |= MII_BMSR_LINK_ST; |
| } |
| |
| /* Set manufacturer */ |
| s->miiregs[MII_PHYID1] = DP83840_PHYID1; |
| s->miiregs[MII_PHYID2] = DP83840_PHYID2; |
| |
| /* Configure default interrupt mask */ |
| s->mifregs[HME_MIFI_IMASK >> 2] = 0xffff; |
| s->sebregs[HME_SEBI_IMASK >> 2] = 0xff7fffff; |
| } |
| |
| static const VMStateDescription vmstate_hme = { |
| .name = "sunhme", |
| .version_id = 0, |
| .minimum_version_id = 0, |
| .fields = (VMStateField[]) { |
| VMSTATE_PCI_DEVICE(parent_obj, SunHMEState), |
| VMSTATE_MACADDR(conf.macaddr, SunHMEState), |
| VMSTATE_UINT32_ARRAY(sebregs, SunHMEState, (HME_SEB_REG_SIZE >> 2)), |
| VMSTATE_UINT32_ARRAY(etxregs, SunHMEState, (HME_ETX_REG_SIZE >> 2)), |
| VMSTATE_UINT32_ARRAY(erxregs, SunHMEState, (HME_ERX_REG_SIZE >> 2)), |
| VMSTATE_UINT32_ARRAY(macregs, SunHMEState, (HME_MAC_REG_SIZE >> 2)), |
| VMSTATE_UINT32_ARRAY(mifregs, SunHMEState, (HME_MIF_REG_SIZE >> 2)), |
| VMSTATE_UINT16_ARRAY(miiregs, SunHMEState, HME_MII_REGS_SIZE), |
| VMSTATE_END_OF_LIST() |
| } |
| }; |
| |
| static void sunhme_class_init(ObjectClass *klass, void *data) |
| { |
| DeviceClass *dc = DEVICE_CLASS(klass); |
| PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); |
| |
| k->realize = sunhme_realize; |
| k->vendor_id = PCI_VENDOR_ID_SUN; |
| k->device_id = PCI_DEVICE_ID_SUN_HME; |
| k->class_id = PCI_CLASS_NETWORK_ETHERNET; |
| dc->vmsd = &vmstate_hme; |
| dc->reset = sunhme_reset; |
| device_class_set_props(dc, sunhme_properties); |
| set_bit(DEVICE_CATEGORY_NETWORK, dc->categories); |
| } |
| |
| static const TypeInfo sunhme_info = { |
| .name = TYPE_SUNHME, |
| .parent = TYPE_PCI_DEVICE, |
| .class_init = sunhme_class_init, |
| .instance_size = sizeof(SunHMEState), |
| .instance_init = sunhme_instance_init, |
| .interfaces = (InterfaceInfo[]) { |
| { INTERFACE_CONVENTIONAL_PCI_DEVICE }, |
| { } |
| } |
| }; |
| |
| static void sunhme_register_types(void) |
| { |
| type_register_static(&sunhme_info); |
| } |
| |
| type_init(sunhme_register_types) |