From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from eggs.gnu.org ([2001:4830:134:3::10]:36661) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Vjs1L-0001cw-0c for qemu-devel@nongnu.org; Fri, 22 Nov 2013 09:47:52 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1Vjs1F-0006H5-Jr for qemu-devel@nongnu.org; Fri, 22 Nov 2013 09:47:46 -0500 Received: from mail-bk0-x22c.google.com ([2a00:1450:4008:c01::22c]:60465) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Vjs1F-0006Gt-47 for qemu-devel@nongnu.org; Fri, 22 Nov 2013 09:47:41 -0500 Received: by mail-bk0-f44.google.com with SMTP id d7so849874bkh.31 for ; Fri, 22 Nov 2013 06:47:40 -0800 (PST) From: Dmitry Smagin Date: Fri, 22 Nov 2013 18:47:49 +0400 Message-Id: <1385131669-16845-1-git-send-email-dmitry.s.smagin@gmail.com> Subject: [Qemu-devel] [RFC] hw/net: add DEC Tulip NIC emulation List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , To: qemu-devel@nongnu.org Cc: Antony Pavlov , Dmitry Smagin From: Antony Pavlov This patch adds emulation of DEC/Intel Tulip 21143 with some external chips: * Intel LXT971A 10/100 Mbps PHY MII Tranceiver; * Microchip 93LC46B 1K Microwire Compatible Serial EEPROM. Restrictions and TODOs: - Tulip always work in promisc-mode with no packet filtering (TODO: check qemu packet filtering interfaces) - Address errors in packet descriptors are not checked - Tulip is bound to PCI, no other bus is possible for now (TODO: Rewrite code to permit Tulip to live on buses other than PCI) - Internal transceiver is not emulated The reason: internal transceiver works with 10 Mbps but we need 100Mbps which only external transceivers can give. - Link status is not emulated, it's always ON (TODO: use standard qemu interface) - Incorrect behavior of the Tulip driver is not checked and there's no error signaling via register CSR5 - Only transfer/receive (TI/RI) interrupts are emulated - Subsystem IDs in EEPROM image are not good (TODO: find proper IDs) TESTING ------- NOTE: buildroot with tulip-friendly config for qemu is here: https://github.com/dmitrysmagin/buildroot.git : master (configs/qemu_mips_malta_tulip_defconfig) $ git clone https://github.com/dmitrysmagin/qemu.git $ cd qemu $ ./configure --target-list=mips-softmmu ... $ make ... $ ./mips-softmmu/qemu-system-mips \ -M malta \ -m 256 \ -nodefaults \ -nographic \ -kernel qemu-malta-testing/vmlinux \ -initrd qemu-malta-testing/rootfs.cpio \ -serial stdio \ -net nic,vlan=0,model=tulip,macaddr=00:11:22:33:44:ee Linux version 3.12.0 (exmortis@lazar) (gcc version 4.7.3 (Sourcery CodeBench Lite 2013.05-66) ) #1 SMP Thu Nov 14 11:58:04 MSK 2013 ... Welcome to Buildroot buildroot login: root # lspci -d 0x1011: 00:12.0 Ethernet controller: Digital Equipment Corporation DECchip 21142/43 (rev 41) # ifconfig eth0 eth0 Link encap:Ethernet HWaddr 00:11:22:33:44:EE BROADCAST MULTICAST MTU:1500 Metric:1 RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:0 (0.0 B) TX bytes:0 (0.0 B) To test data transfer please use '-net tap,vlan=0,...' or '-net vde,vlan=0,...' (depends on your local network settings). Signed-off-by: Antony Pavlov Signed-off-by: Dmitry Smagin --- default-configs/pci.mak | 1 + hw/net/Makefile.objs | 1 + hw/net/tulip.c | 680 ++++++++++++++++++++++++++++++++++++++++++++ hw/net/tulip_mdio.c | 196 +++++++++++++ hw/net/tulip_mdio.h | 98 +++++++ hw/net/tulip_uwire_eeprom.c | 111 ++++++++ hw/net/tulip_uwire_eeprom.h | 60 ++++ hw/pci/pci.c | 2 + include/hw/pci/pci_ids.h | 1 + 9 files changed, 1150 insertions(+) create mode 100644 hw/net/tulip.c create mode 100644 hw/net/tulip_mdio.c create mode 100644 hw/net/tulip_mdio.h create mode 100644 hw/net/tulip_uwire_eeprom.c create mode 100644 hw/net/tulip_uwire_eeprom.h diff --git a/default-configs/pci.mak b/default-configs/pci.mak index 91b1e92..28a282e 100644 --- a/default-configs/pci.mak +++ b/default-configs/pci.mak @@ -18,6 +18,7 @@ CONFIG_MEGASAS_SCSI_PCI=y CONFIG_RTL8139_PCI=y CONFIG_E1000_PCI=y CONFIG_VMXNET3_PCI=y +CONFIG_TULIP_PCI=y CONFIG_IDE_CORE=y CONFIG_IDE_QDEV=y CONFIG_IDE_PCI=y diff --git a/hw/net/Makefile.objs b/hw/net/Makefile.objs index 951cca3..014c24b 100644 --- a/hw/net/Makefile.objs +++ b/hw/net/Makefile.objs @@ -10,6 +10,7 @@ common-obj-$(CONFIG_E1000_PCI) += e1000.o common-obj-$(CONFIG_RTL8139_PCI) += rtl8139.o common-obj-$(CONFIG_VMXNET3_PCI) += vmxnet_tx_pkt.o vmxnet_rx_pkt.o common-obj-$(CONFIG_VMXNET3_PCI) += vmxnet3.o +common-obj-$(CONFIG_TULIP_PCI) += tulip.o tulip_uwire_eeprom.o tulip_mdio.o common-obj-$(CONFIG_SMC91C111) += smc91c111.o common-obj-$(CONFIG_LAN9118) += lan9118.o diff --git a/hw/net/tulip.c b/hw/net/tulip.c new file mode 100644 index 0000000..bd52413 --- /dev/null +++ b/hw/net/tulip.c @@ -0,0 +1,680 @@ +/* + * QEMU DEC 21143 (Tulip) emulation + * + * Copyright (C) 2011, 2013 Antony Pavlov + * Copyright (C) 2013 Dmitry Smagin + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#include "hw/hw.h" +#include "hw/pci/pci.h" +#include "net/net.h" +#include "sysemu/sysemu.h" +#include "sysemu/dma.h" +#include "qemu/timer.h" + +#include "tulip_uwire_eeprom.h" +#include "tulip_mdio.h" + +#define TULIP_CSR0 0x00 + #define TULIP_CSR0_SWR 0x01 + #define TULIP_CSR0_TAP_MASK (0x03 << 17) +#define TULIP_CSR1 0x08 +#define TULIP_CSR2 0x10 +#define TULIP_CSR3 0x18 +#define TULIP_CSR4 0x20 +#define TULIP_CSR5 0x28 + #define TULIP_CSR5_TI (1 << 0) /* Transmit Interrupt */ + #define TULIP_CSR5_RI (1 << 6) /* Receive Interrupt */ + #define TULIP_CSR5_NIS (1 << 16) /* Or'ed bits 0, 2, 6, 11, 14 */ + #define TULIP_CSR5_NIS_SHIFT (16) + #define TULIP_CSR5_RS_MASK (7 << 17) + #define TULIP_CSR5_TS_MASK (7 << 20) + +#define TULIP_CSR6 0x30 /* Operation Mode Register */ + #define TULIP_CSR6_TXON 0x2000 + #define TULIP_CSR6_RXON 0x0002 + +#define TULIP_CSR7 0x38 /* Interrupt Enable Register */ +#define TULIP_CSR8 0x40 +#define TULIP_CSR9 0x48 /* Boot ROM, Serial ROM, and MII Management Register */ + #define TULIP_CSR9_MDI (1 << 19) /* MDIO Data In */ + #define TULIP_CSR9_MDOM (1 << 18) /* MDIO Operation Mode */ + #define TULIP_CSR9_MDO (1 << 17) /* MDIO Data Out */ + #define TULIP_CSR9_MDC (1 << 16) /* MDIO Clock */ + #define TULIP_CSR9_RD (1 << 14) + #define TULIP_CSR9_WR (1 << 13) + #define TULIP_CSR9_SR (1 << 11) /* Serial ROM Select */ + #define TULIP_CSR9_SRDO (1 << 3) /* Serial ROM Data Out */ + #define TULIP_CSR9_SRDI (1 << 2) /* Serial ROM Data In */ + #define TULIP_CSR9_SRCK (1 << 1) /* Serial ROM Clock */ + #define TULIP_CSR9_SRCS (1) /* Serial ROM Chip Select */ +#define TULIP_CSR10 0x50 +#define TULIP_CSR11 0x58 + +#define TULIP_CSR12 0x60 + +#define TULIP_CSR13 0x68 + +#define TULIP_CSR14 0x70 /* SIA Transmit and Receive Register */ +#define TULIP_CSR15 0x78 + +#define defreg(x) x = (TULIP_##x>>2) +enum { + defreg(CSR0), + defreg(CSR1), + defreg(CSR2), + defreg(CSR3), + defreg(CSR4), + defreg(CSR5), + defreg(CSR6), + defreg(CSR7), + defreg(CSR8), + defreg(CSR9), + defreg(CSR10), + defreg(CSR11), + defreg(CSR12), + defreg(CSR13), + defreg(CSR14), + defreg(CSR15), +}; + +/* The Tulip Receive Descriptor */ +struct tulip_rx_desc { + uint32_t status; + uint32_t length; + uint32_t buffer1; + uint32_t buffer2; +}; + +#define TULIP_RDES0_OWN (1 << 31) +#define TULIP_RDES0_FS (1 << 9) /* First descriptor */ +#define TULIP_RDES0_LS (1 << 8) /* Last descriptor */ + +/* The Tulip Transmit Descriptor */ +struct tulip_tx_desc { + uint32_t status; + uint32_t length; + uint32_t buffer1; + uint32_t buffer2; /* Linux use only buffer 1. */ +}; + +#define TULIP_TDES0_OWN (1 << 31) + +#define TULIP_TDES1_IC (1 << 31) /* Interrupt on Completion */ +#define TULIP_TDES1_LS (1 << 30) /* Last Segment */ +#define TULIP_TDES1_FS (1 << 29) /* First Segment */ +#define TULIP_TDES1_SET (1 << 27) /* Setup Packet */ + +#define TULIP_CSR_REGION_SIZE 0x80 + +typedef struct TulipState_st { + PCIDevice dev; + NICState *nic; + NICConf conf; + MemoryRegion mmio; + MemoryRegion io; + QEMUTimer *timer; + qemu_irq irq; + + struct MicrowireEeprom eeprom; + struct MiiTransceiver mii; + + uint32_t mac_reg[TULIP_CSR_REGION_SIZE >> 2]; + + uint32_t cur_tx_desc; + uint32_t cur_rx_desc; + int tx_polling; +} TulipState; + +#define EEPROM_TEMPLATE_SIZE 64 +#define EEPROM_MACADDR_OFFSET 10 +/* + * DEC has developed its own EEPROM format + * see Digital Semiconductor 21X4 Serial ROM Format ver. 4.05 2-Mar-1998 + * for details. + * + * Also see tulip-diag utility code from nictools-pci package + * http://ftp.debian.org/debian/pool/main/n/nictools-pci/ + */ +static const uint16_t tulip_eeprom_template[EEPROM_TEMPLATE_SIZE] = { + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + /* ^^^^^^^^^^^^^^ Subsystem IDs */ + 0x0000, 0x0104, 0x5554, 0x494c, 0x0050, 0x1e00, 0x0000, 0x0800, + /* ^^^^^^^^^^^^^^^^^^^^^^ MAC address here */ + 0x8d01, 0x0003, 0x0000, 0x7800, 0x01e0, 0x5000, 0x1800, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 + /* we have no Magic Packet block, so checksum is here ^^^^^^ */ +}; + +static void tulip_reset(void *opaque); +static void tulip_update_irq(TulipState *s); + +static inline uint32_t mac_readreg(TulipState *s, int index) +{ + return s->mac_reg[index]; +} + +static inline void mac_writereg(TulipState *s, int index, uint32_t val) +{ + s->mac_reg[index] = val; +} + +/* FIXME: we use external MII tranceiver, so we must change it status here */ +static void tulip_set_link_status(NetClientState *nc) +{ +} + +static int tulip_can_receive(NetClientState *nc) +{ + TulipState *s = qemu_get_nic_opaque(nc); + + return mac_readreg(s, CSR6) & TULIP_CSR6_RXON; +} + +static ssize_t tulip_receive(NetClientState *nc, const uint8_t *buf, + size_t size) +{ + TulipState *s = qemu_get_nic_opaque(nc); + struct tulip_rx_desc desc; + uint32_t status, buffer1, buffer2; + uint32_t cur_rx_desc; + + if (!(mac_readreg(s, CSR6) & TULIP_CSR6_RXON)) { + return -1; + } + + cur_rx_desc = s->cur_rx_desc; + while (1) { + pci_dma_read(&s->dev, cur_rx_desc, (void *)&desc, sizeof(desc)); + status = le32_to_cpu(desc.status); + buffer1 = le32_to_cpu(desc.buffer1); + buffer2 = le32_to_cpu(desc.buffer2); + + /* FIXME: check desc.length too */ + if (!(status & TULIP_RDES0_OWN)) { + return -1; + } + + desc.status = cpu_to_le32( + (((size + 4) << 16) & 0x3fff0000) + | TULIP_RDES0_FS | TULIP_RDES0_LS); + + pci_dma_write(&s->dev, buffer1, buf, size); + pci_dma_write(&s->dev, cur_rx_desc, (void *)&desc, sizeof(desc)); + mac_writereg(s, CSR5, TULIP_CSR5_RI | mac_readreg(s, CSR5)); + + s->cur_rx_desc = buffer2; + break; + } + + tulip_update_irq(s); + + return size; +} + +static inline void tulip_csr0_write(TulipState *s, uint32_t val) +{ + if (val & TULIP_CSR0_SWR) { + tulip_reset(s); + return; + } + + if (val & TULIP_CSR0_TAP_MASK) { + s->tx_polling = 1; + } else { + s->tx_polling = 0; + } + + mac_writereg(s, CSR0, val); +} + +static inline void tulip_csr5_write(TulipState *s, uint32_t val) +{ + uint32_t csr5_write_mask = 0x02fe0000; + + val = val & (~csr5_write_mask); + + mac_writereg(s, CSR5, mac_readreg(s, CSR5) & (~val)); +} + +static inline void tulip_csr6_write(TulipState *s, uint32_t val) +{ + mac_writereg(s, CSR6, val); +} + +static inline void tulip_csr9_write(TulipState *s, uint32_t val) +{ + if (val & TULIP_CSR9_SR) { /* Serial ROM */ + int srdo; + + srdo = microwire_tick(&s->eeprom, + (val & TULIP_CSR9_SRCK) >> 1, + (val & TULIP_CSR9_SRCS), + (val & TULIP_CSR9_SRDI) >> 2); + if (srdo) { + mac_writereg(s, CSR9, val | TULIP_CSR9_SRDO); + } else { + mac_writereg(s, CSR9, val & ~TULIP_CSR9_SRDO); + } + } else { + int mdo; + + mdo = mii_tick(&s->mii, (val & TULIP_CSR9_MDC) >> 16, + (val & TULIP_CSR9_MDO) >> 17); + + if (val & TULIP_CSR9_MDOM) { + if (mdo) { + val |= TULIP_CSR9_MDI; + } else { + val &= ~TULIP_CSR9_MDI; + } + } + mac_writereg(s, CSR9, val); + } +} + +/* Just now we don't use MAC-address filtering (we always use promisc mode), + so just drop any setup frame */ +static void process_setup_frame(TulipState *s, uint8_t *a) +{ +} + +static void process_tx(TulipState *s) +{ + struct tulip_tx_desc desc; + uint32_t status, length, buffer1, buffer2; + uint32_t cur_tx_desc; + + uint8_t a[1600]; + int cur_offset; + int frame_length; + + cur_offset = 0; + frame_length = 0; + + cur_tx_desc = s->cur_tx_desc; + + while (1) { + uint32_t to_copy; + + pci_dma_read(&s->dev, cur_tx_desc, (void *)&desc, sizeof(desc)); + status = le32_to_cpu(desc.status); + length = le32_to_cpu(desc.length); + buffer1 = le32_to_cpu(desc.buffer1); + buffer2 = le32_to_cpu(desc.buffer2); + + if (!(status & TULIP_TDES0_OWN)) { + break; + } + + to_copy = 0x7ff & length; + + /* First Segment */ + if (length & TULIP_TDES1_FS) { + cur_offset = 0; + frame_length = to_copy; + } + + if (length & TULIP_TDES1_SET) { /* Setup Frame */ + /* FIXME: check (to_copy == 192) */ + pci_dma_read(&s->dev, buffer1, (void *)a, to_copy); + + process_setup_frame(s, a); + } else { + + pci_dma_read(&s->dev, buffer1, (void *)(a + cur_offset), to_copy); + + cur_offset += to_copy; + + /* ! First Segment */ + if (!(length & TULIP_TDES1_FS)) { + frame_length += to_copy; + } + + /* Last Segment */ + if (length & TULIP_TDES1_LS) { + qemu_send_packet(qemu_get_queue(s->nic), a, frame_length); + } + } + + desc.status = cpu_to_le32(status & ~TULIP_TDES0_OWN); + pci_dma_write(&s->dev, cur_tx_desc, (void *)&desc, sizeof(desc)); + + /* Last Segment */ + if (length & TULIP_TDES1_LS) { + /* FIXME: check IC */ + /* FIXME: mac_writereg -> tulip_csr5_write */ + mac_writereg(s, CSR5, TULIP_CSR5_TI | mac_readreg(s, CSR5)); + } + + cur_tx_desc = buffer2; + s->cur_tx_desc = cur_tx_desc; + } +} + +static void tulip_csr_write(void *opaque, hwaddr addr, uint64_t val, + unsigned size) +{ + TulipState *s = opaque; + unsigned int index = addr & (TULIP_CSR_REGION_SIZE - 1); + + switch (index) { + case TULIP_CSR0: /* Bus Mode Register */ + tulip_csr0_write(s, (uint32_t)val); + break; + + case TULIP_CSR1: /* Transmit Poll Demand/Current Descriptor Address */ + if (mac_readreg(s, CSR6) & TULIP_CSR6_TXON) { + process_tx(s); + tulip_update_irq(s); + } + break; + + case TULIP_CSR2: /* Receive Poll Demand/Current Descriptor Address */ + break; + + case TULIP_CSR3: /* Start of Receive List */ + mac_writereg(s, CSR3, val); + s->cur_rx_desc = val; + break; + + case TULIP_CSR4: /* Start of Transmit List */ + mac_writereg(s, CSR4, val); + s->cur_tx_desc = val; + break; + + case TULIP_CSR5: /* Status Register */ + tulip_csr5_write(s, (uint32_t)val); + tulip_update_irq(s); + break; + + case TULIP_CSR6: /* Command/Mode Register */ + tulip_csr6_write(s, (uint32_t)val); + break; + + case TULIP_CSR9: /* Boot/Serial ROM and MII Management Register */ + tulip_csr9_write(s, (uint32_t)val); + break; + + case TULIP_CSR13: + mac_writereg(s, CSR13, val); + break; + + case TULIP_CSR14: + case TULIP_CSR15: + break; + + default: + qemu_log_mask(LOG_UNIMP, + "tulip: write access to unknown register 0x" TARGET_FMT_plx, addr); + } +} + +static uint64_t tulip_csr_read(void *opaque, hwaddr addr, unsigned size) +{ + TulipState *s = opaque; + unsigned int index = addr & (TULIP_CSR_REGION_SIZE - 1); + uint64_t ret; + + ret = 0; + + switch (index) { + case TULIP_CSR0: /* Bus Mode Register */ + case TULIP_CSR5: /* Status Register */ + case TULIP_CSR6: /* Command/Mode Register */ + case TULIP_CSR8: + case TULIP_CSR9: /* Boot/Serial ROM and MII Management Register */ + case TULIP_CSR12: + case TULIP_CSR13: + /* FIXME: tulip_csr_read: unimplemented register index = 70 */ + case TULIP_CSR14: + case TULIP_CSR15: + ret = (uint64_t)mac_readreg(s, (index >> 2)); + break; + + default: + qemu_log_mask(LOG_UNIMP, + "tulip: read access to unknown register 0x" TARGET_FMT_plx, addr); + + } + + return ret; +} + +static const MemoryRegionOps tulip_mmio_ops = { + .read = tulip_csr_read, + .write = tulip_csr_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static const VMStateDescription vmstate_tulip = { + .name = "tulip", + .version_id = 2, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_PCI_DEVICE(dev, TulipState), + VMSTATE_MACADDR(conf.macaddr, TulipState), + VMSTATE_END_OF_LIST() + } +}; + +static const uint32_t mac_reg_init[] = { + [CSR0] = 0xfe000000, + [CSR1] = 0xffffffff, + [CSR2] = 0x00000000, + [CSR3] = 0x00000000, + [CSR4] = 0x00000000, + [CSR5] = 0xf0000000, + [CSR6] = 0x00000000, + [CSR7] = 0x00000000, + [CSR8] = 0xe0000000, + [CSR9] = 0xfff483ff, + [CSR10] = 0x00000000, + [CSR11] = 0x00000000, + [CSR12] = 0x00000000, + [CSR13] = 0x00000000, + [CSR14] = 0x00000000, + [CSR15] = 0x00000000, +}; + +static void tulip_cleanup(NetClientState *nc) +{ + TulipState *s = qemu_get_nic_opaque(nc); + + s->nic = NULL; +} + +static void pci_tulip_uninit(PCIDevice *dev) +{ + TulipState *d = DO_UPCAST(TulipState, dev, dev); + + qemu_free_irq(d->irq); + memory_region_destroy(&d->mmio); + memory_region_destroy(&d->io); + timer_del(d->timer); + timer_free(d->timer); + qemu_del_nic(d->nic); +} + +static void tulip_reset(void *opaque) +{ + TulipState *d = opaque; + + memset(d->mac_reg, 0, sizeof d->mac_reg); + memmove(d->mac_reg, mac_reg_init, sizeof mac_reg_init); + + d->tx_polling = 0; +} + +static NetClientInfo net_tulip_info = { + .type = NET_CLIENT_OPTIONS_KIND_NIC, + .size = sizeof(NICState), + .can_receive = tulip_can_receive, + .receive = tulip_receive, + .cleanup = tulip_cleanup, + .link_status_changed = tulip_set_link_status, +}; + +static void tulip_update_irq(TulipState *s) +{ + int isr = 0; + + /* calculate NIS (sum of normal interrupts) */ + s->mac_reg[CSR5] &= ~TULIP_CSR5_NIS; + + if (s->mac_reg[CSR5] & (TULIP_CSR5_TI | TULIP_CSR5_RI)) { + s->mac_reg[CSR5] |= TULIP_CSR5_NIS; + isr = 1; + } + + /* FIXME: when calculating NIS take into account masked interupts */ + + /* FIXME: can we report about Abnormal Interrupt Summary too? */ + + qemu_set_irq(s->irq, isr); +} + +static void tulip_timer(void *opaque) +{ + TulipState *s = opaque; + + if (s->tx_polling) { + process_tx(s); + } + + tulip_update_irq(s); + + timer_mod(s->timer, + qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + get_ticks_per_sec() / 10); +} + +static int pci_tulip_init(PCIDevice *pci_dev) +{ + TulipState *d = DO_UPCAST(TulipState, dev, pci_dev); + uint8_t *pci_conf; + uint8_t *macaddr; + uint16_t eeprom_template_copy[EEPROM_TEMPLATE_SIZE]; + int i; + + pci_conf = d->dev.config; + + /* TODO: RST# value should be 0, PCI spec 6.2.4 */ + pci_conf[PCI_CACHE_LINE_SIZE] = 0x10; + + pci_conf[PCI_INTERRUPT_PIN] = 1; /* interrupt pin A */ + + /* PCI interface */ + memory_region_init_io(&d->mmio, OBJECT(d), &tulip_mmio_ops, d, + "tulip-mmio", TULIP_CSR_REGION_SIZE); + memory_region_init_io(&d->io, OBJECT(d), &tulip_mmio_ops, d, + "tulip-io", TULIP_CSR_REGION_SIZE); + + pci_register_bar(&d->dev, 0, PCI_BASE_ADDRESS_SPACE_IO, &d->io); + pci_register_bar(&d->dev, 1, PCI_BASE_ADDRESS_SPACE_MEMORY, &d->mmio); + + d->irq = pci_allocate_irq(pci_dev); + + lxt971_init(&d->mii, 0); + + qemu_macaddr_default_if_unset(&d->conf.macaddr); + macaddr = d->conf.macaddr.a; + + memmove(eeprom_template_copy, tulip_eeprom_template, + sizeof(eeprom_template_copy)); + + /* copy macaddr to eeprom as linux driver want find it there */ + for (i = 0; i < 3; i++) { + eeprom_template_copy[EEPROM_MACADDR_OFFSET + i] = + (macaddr[2 * i + 1] << 8) | macaddr[2 * i]; + } + + /* + * FIXME: we have to update eeprom checksum too. + * + * see tulip-diag utility code from nictools-pci package + * http://ftp.debian.org/debian/pool/main/n/nictools-pci/ + */ + + microwire_reset(&d->eeprom); + microwire_load_data(&d->eeprom, eeprom_template_copy); + + d->nic = qemu_new_nic(&net_tulip_info, &d->conf, + object_get_typename(OBJECT(pci_dev)), + d->dev.qdev.id, d); + + qemu_format_nic_info_str(qemu_get_queue(d->nic), macaddr); + + add_boot_device_path(d->conf.bootindex, &pci_dev->qdev, "/ethernet-phy@0"); + + d->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, tulip_timer, d); + timer_mod(d->timer, + qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + get_ticks_per_sec()); + + return 0; +} + +static void qdev_tulip_reset(DeviceState *dev) +{ + TulipState *d = DO_UPCAST(TulipState, dev.qdev, dev); + + tulip_reset(d); +} + +static Property tulip_properties[] = { + DEFINE_NIC_PROPERTIES(TulipState, conf), + DEFINE_PROP_END_OF_LIST(), +}; + +static void tulip_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); + + k->init = pci_tulip_init; + k->exit = pci_tulip_uninit; + k->vendor_id = PCI_VENDOR_ID_DEC; + k->device_id = PCI_DEVICE_ID_DEC_21142; + k->revision = 0x41; /* 21143 chip */ + k->class_id = PCI_CLASS_NETWORK_ETHERNET; + dc->desc = "DEC 21143 Tulip"; + dc->reset = qdev_tulip_reset; + dc->vmsd = &vmstate_tulip; + dc->props = tulip_properties; + set_bit(DEVICE_CATEGORY_NETWORK, dc->categories); +} + +static const TypeInfo tulip_info = { + .name = "tulip", + .parent = TYPE_PCI_DEVICE, + .instance_size = sizeof(TulipState), + .class_init = tulip_class_init, +}; + +static void tulip_register_types(void) +{ + type_register_static(&tulip_info); +} + +type_init(tulip_register_types) diff --git a/hw/net/tulip_mdio.c b/hw/net/tulip_mdio.c new file mode 100644 index 0000000..0c7e9b1 --- /dev/null +++ b/hw/net/tulip_mdio.c @@ -0,0 +1,196 @@ +/* + * QEMU Intel LXT971A 10/100 Mbps PHY MII Transceiver emulation + * + * Copyright (C) 2011, 2013 Antony Pavlov + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#include "net/net.h" + +#include "tulip_mdio.h" + +void mii_reset(struct MiiTransceiver *s) +{ + s->state = MII_STATE_IDLE; + s->bit_num = 0; + s->cmd = 0; + s->addr = 0; + s->data = 0; + s->last_clk = 0; + s->last_mdio = 0; + s->last_mdi = 0; + s->selected_phy = 0; +} + +int mii_tick(struct MiiTransceiver *s, int clk, int io) +{ + int ret; + + ret = s->last_mdio; + + if (s->last_clk == 0 && clk == 1) { + switch (s->state) { + case MII_STATE_IDLE: + s->state = MII_STATE_READY; + break; + + case MII_STATE_READY: + if (io == 0 && s->last_mdi == 1) { + s->state = MII_STATE_WAIT_START; + } + break; + + case MII_STATE_WAIT_START: + if (io == 1) { + s->state = MII_STATE_WAIT_CMD; + s->bit_num = 0; + s->cmd = 0; + } else { + s->state = MII_STATE_IDLE; + } + break; + + case MII_STATE_WAIT_CMD: + s->cmd = (s->cmd << 1) | (io & 1); + s->bit_num++; + if (s->bit_num == 2) { + s->state = MII_STATE_WAIT_PHYA; + s->addr = 0; + s->bit_num = 0; + } + break; + + case MII_STATE_WAIT_PHYA: + s->addr = (s->addr << 1) | (io & 1); + s->bit_num++; + + if (s->bit_num == 5) { + s->selected_phy = s->addr; + s->state = MII_STATE_WAIT_REGA; + s->bit_num = 0; + s->addr = 0; + } + + break; + + case MII_STATE_WAIT_REGA: + s->addr = (s->addr << 1) | (io & 1); + s->bit_num++; + + if (s->bit_num == 5) { + s->bit_num = 0; + if (s->cmd == MII_CMD_READ) { + s->state = MII_STATE_READ_SYNC_CYCL; + if (s->selected_phy == s->taddr) { + s->data = s->readreg(s, s->addr); + } else { + s->data = 0xffff; + } + } + if (s->cmd == MII_CMD_WRITE) { + s->state = MII_STATE_WRITE_SYNC_CYCL; + s->data = 0; + } + } + break; + + case MII_STATE_WRITE_SYNC_CYCL: + s->bit_num++; + + if (s->bit_num == 2) { + s->bit_num = 0; + s->state = MII_STATE_GET_DATA; + if (s->selected_phy == s->taddr) { + ret = 0; + } else { + ret = 1; + } + } + break; + + case MII_STATE_READ_SYNC_CYCL: + s->bit_num++; + + if (s->bit_num == 1) { + s->bit_num = 0; + s->state = MII_STATE_PUT_DATA; + ret = 0; + } + break; + + case MII_STATE_PUT_DATA: + ret = ((s->data >> (15 - s->bit_num)) & 1); + s->bit_num++; + if (s->bit_num == 16) { + s->state = MII_STATE_IDLE; + } + break; + + case MII_STATE_GET_DATA: + s->data = (s->data << 1) | (io & 1); + s->bit_num++; + if (s->bit_num == 16) { + s->state = MII_STATE_IDLE; + /* FIXME: check phy_addr */ + s->writereg(s, s->addr, s->data); + } + break; + } + s->last_mdio = ret; + s->last_mdi = io; + } + + s->last_clk = clk; + + return ret; +} + +static uint16_t lxt971_readreg(struct MiiTransceiver *s, int index) +{ + return s->mii_data[index]; +} + +static void lxt971_writereg(struct MiiTransceiver *s, int index, uint16_t val) +{ + s->mii_data[index] = val; +} + +static void lxt971_reset(struct MiiTransceiver *s) +{ + uint16_t a[] = { + 0x1000, 0x782d, 0x0013, 0x78e2, 0x01e1, 0x45e1, 0x0007, 0x2001, + 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0x0084, 0x4780, 0x0000, 0x00f4, 0x0422, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x00c8, 0x0000, 0xffff, 0x0000, 0x0000, 0x3678, + }; + int i; + + mii_reset(s); + + for (i = 0; i < 32; i++) { + s->mii_data[i] = a[i]; + } +} + +void lxt971_init(struct MiiTransceiver *s, int phy_addr) +{ + s->reset = lxt971_reset; + s->readreg = lxt971_readreg; + s->writereg = lxt971_writereg; + + s->taddr = phy_addr; + + lxt971_reset(s); +} diff --git a/hw/net/tulip_mdio.h b/hw/net/tulip_mdio.h new file mode 100644 index 0000000..c87e6bb --- /dev/null +++ b/hw/net/tulip_mdio.h @@ -0,0 +1,98 @@ +/* + * vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab + * + * QEMU LXT971A MII Transceiver emulation + * Copyright (C) 2011, 2013 Antony Pavlov + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#ifndef HW_NET_TULIP_MDIO_H +#define HW_NET_TULIP_MDIO_H + +struct MiiTransceiver { + int state; +#define MII_STATE_IDLE 0 +#define MII_STATE_READY 1 +#define MII_STATE_WAIT_START 2 +#define MII_STATE_WAIT_CMD 3 +#define MII_STATE_WAIT_PHYA 4 +#define MII_STATE_WAIT_REGA 5 +#define MII_STATE_READ_SYNC_CYCL 6 +#define MII_STATE_WRITE_SYNC_CYCL 7 +#define MII_STATE_PUT_DATA 8 +#define MII_STATE_GET_DATA 9 +#define MII_STATE_ERROR 10 + + int taddr; + + int last_mdio; + int last_mdi; + int last_clk; + int bit_num; + int selected_phy; + + int cmd; +#define MII_CMD_READ 2 +#define MII_CMD_WRITE 1 + + int addr; + int data; + + uint16_t mii_data[32]; + + void (*reset)(struct MiiTransceiver *s); + uint16_t (*readreg)(struct MiiTransceiver *, int); + void (*writereg)(struct MiiTransceiver *, int, uint16_t); +}; + +/* PHY Registers defined by IEEE */ +#define PHY_CTRL 0x00 /* Control Register */ +#define PHY_STATUS 0x01 /* Status Regiser */ +#define PHY_ID1 0x02 /* Phy Id Reg (word 1) */ +#define PHY_ID2 0x03 /* Phy Id Reg (word 2) */ +#define PHY_AUTONEG_ADV 0x04 /* Autoneg Advertisement */ +#define PHY_LP_ABILITY 0x05 /* Link Partner Ability (Base Page) */ +#define PHY_AUTONEG_EXP 0x06 /* Autoneg Expansion Reg */ +#define PHY_NEXT_PAGE_TX 0x07 /* Next Page TX */ +#define PHY_LP_NEXT_PAGE 0x08 /* Link Partner Next Page */ +#define PHY_1000T_CTRL 0x09 /* 1000Base-T Control Reg */ +#define PHY_1000T_STATUS 0x0A /* 1000Base-T Status Reg */ +#define PHY_EXT_STATUS 0x0F /* Extended Status Reg */ + +#define MAX_PHY_REG_ADDRESS 0x1F /* 5 bit address bus (0-0x1F) */ +#define MAX_PHY_MULTI_PAGE_REG 0xF /* Registers equal on all pages */ + +/* PHY Status Register */ +#define MII_SR_EXTENDED_CAPS 0x0001 /* Extended register capabilities */ +#define MII_SR_JABBER_DETECT 0x0002 /* Jabber Detected */ +#define MII_SR_LINK_STATUS 0x0004 /* Link Status 1 = link */ +#define MII_SR_AUTONEG_CAPS 0x0008 /* Auto Neg Capable */ +#define MII_SR_REMOTE_FAULT 0x0010 /* Remote Fault Detect */ +#define MII_SR_AUTONEG_COMPLETE 0x0020 /* Auto Neg Complete */ +#define MII_SR_PREAMBLE_SUPPRESS 0x0040 /* Preamble may be suppressed */ +#define MII_SR_EXTENDED_STATUS 0x0100 /* Ext. status info in Reg 0x0F */ +#define MII_SR_100T2_HD_CAPS 0x0200 /* 100T2 Half Duplex Capable */ +#define MII_SR_100T2_FD_CAPS 0x0400 /* 100T2 Full Duplex Capable */ +#define MII_SR_10T_HD_CAPS 0x0800 /* 10T Half Duplex Capable */ +#define MII_SR_10T_FD_CAPS 0x1000 /* 10T Full Duplex Capable */ +#define MII_SR_100X_HD_CAPS 0x2000 /* 100X Half Duplex Capable */ +#define MII_SR_100X_FD_CAPS 0x4000 /* 100X Full Duplex Capable */ +#define MII_SR_100T4_CAPS 0x8000 /* 100T4 Capable */ + +extern void mii_reset(struct MiiTransceiver *s); +extern int mii_tick(struct MiiTransceiver *s, int clk, int io); +extern void lxt971_init(struct MiiTransceiver *s, int phy_addr); + +#endif /* HW_NET_TULIP_MDIO_H */ diff --git a/hw/net/tulip_uwire_eeprom.c b/hw/net/tulip_uwire_eeprom.c new file mode 100644 index 0000000..370c5e4 --- /dev/null +++ b/hw/net/tulip_uwire_eeprom.c @@ -0,0 +1,111 @@ +/* + * QEMU Microchip 93LC46B 1K Microwire Compatible Serial EEPROM emulation + * + * Copyright (C) 2011, 2013 Antony Pavlov + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#include "net/net.h" + +#include "tulip_uwire_eeprom.h" + +void microwire_reset(struct MicrowireEeprom *s) +{ + s->state = MW_EEPROM_STATE_IDLE; + s->bit_num = 0; + s->cmd = 0; + s->addr = 0; + s->data = 0; + s->last_clk = 0; +} + +void microwire_load_data(struct MicrowireEeprom *s, const uint16_t *data) +{ + memmove(s->eeprom_data, data, sizeof(s->eeprom_data)); +} + +int microwire_tick(struct MicrowireEeprom *s, int clk, int en, int din) +{ + int ret; + + ret = 0; + + /* Microwire eeprom disabled */ + if (en == 0) { + s->state = MW_EEPROM_STATE_IDLE; + return 0; + } + + if (s->last_clk == 0 && clk == 1) { + switch (s->state) { + case MW_EEPROM_STATE_IDLE: + /* + * The Start bit is detected by the device if CS and DI are + * both high with respect to the positive edge of CLK for + * the first time. + */ + if (!din) { + break; + } + + s->state = MW_EEPROM_STATE_WAIT_CMD; + s->cmd = din & 1; + s->bit_num = 0; + break; + + case MW_EEPROM_STATE_WAIT_CMD: + s->cmd = (s->cmd << 1) | (din & 1); + s->bit_num++; + if (s->bit_num == 2) { + if (s->cmd == 0) { + s->state = MW_EEPROM_STATE_IDLE; + } + if (s->cmd == MW_EEPROM_CMD_READ) { + s->state = MW_EEPROM_STATE_WAIT_ADDR; + s->addr = 0; + s->bit_num = 0; + } + } + break; + + case MW_EEPROM_STATE_WAIT_ADDR: + s->addr = (s->addr << 1) | (din & 1); + s->bit_num++; + if (s->bit_num == 6) { /* ROM width = 6 */ + if (s->cmd == MW_EEPROM_CMD_READ) { + s->state = MW_EEPROM_STATE_OUT_DATA; + s->bit_num = 0; + } + } + break; + + case MW_EEPROM_STATE_OUT_DATA: + s->bit_num++; + if (s->bit_num == 16) { + if (s->cmd == MW_EEPROM_CMD_READ) { + s->state = MW_EEPROM_STATE_IDLE; + } + } + + ret = ((s->eeprom_data[s->addr] >> (16 - s->bit_num)) & 1); + + break; + } + } + + s->last_clk = clk; + + return ret; +} diff --git a/hw/net/tulip_uwire_eeprom.h b/hw/net/tulip_uwire_eeprom.h new file mode 100644 index 0000000..4d310cb --- /dev/null +++ b/hw/net/tulip_uwire_eeprom.h @@ -0,0 +1,60 @@ +/* + * vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab + * + * QEMU 93LC46B uWire-connected EEPROM emulation + * Copyright (C) 2011, 2013 Antony Pavlov + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#ifndef HW_NET_TULIP_UWIRE_EEPROM_H +#define HW_NET_TULIP_UWIRE_EEPROM_H + +struct MicrowireEeprom { + /* 93LC46B */ + int state; +#define MW_EEPROM_STATE_IDLE 0 +#define MW_EEPROM_STATE_WAIT_CMD 1 +#define MW_EEPROM_STATE_WAIT_ADDR 2 +#define MW_EEPROM_STATE_WAIT_DATA 3 +#define MW_EEPROM_STATE_OUT_DATA 4 +#define MW_EEPROM_STATE_ERROR 5 + + int last_clk; + int bit_num; + int cmd; +#define MW_EEPROM_CMD_ERASE 7 +#define MW_EEPROM_CMD_READ 6 +#define MW_EEPROM_CMD_WRITE 5 +#define MW_EEPROM_CMD_ERASE_ALL 4 + + int addr; + int data; + + uint16_t eeprom_data[64]; +}; + +/* EEPROM Commands - Microwire */ +#define EEPROM_READ_OPCODE_MICROWIRE 0x6 /* EEPROM read opcode */ +#define EEPROM_WRITE_OPCODE_MICROWIRE 0x5 /* EEPROM write opcode */ +#define EEPROM_ERASE_OPCODE_MICROWIRE 0x7 /* EEPROM erase opcode */ +#define EEPROM_EWEN_OPCODE_MICROWIRE 0x13 /* EEPROM erase/write enable */ +#define EEPROM_EWDS_OPCODE_MICROWIRE 0x10 /* EEPROM erast/write disable */ + +extern int microwire_tick(struct MicrowireEeprom *s, int clk, int en, int din); +extern void microwire_reset(struct MicrowireEeprom *s); +extern void microwire_load_data(struct MicrowireEeprom *s, + const uint16_t *data); + +#endif /* HW_NET_TULIP_UWIRE_EEPROM_H */ diff --git a/hw/pci/pci.c b/hw/pci/pci.c index a98c8a0..82d0239 100644 --- a/hw/pci/pci.c +++ b/hw/pci/pci.c @@ -1600,6 +1600,7 @@ static const char * const pci_nic_models[] = { "e1000", "pcnet", "virtio", + "tulip", NULL }; @@ -1612,6 +1613,7 @@ static const char * const pci_nic_names[] = { "e1000", "pcnet", "virtio-net-pci", + "tulip", NULL }; diff --git a/include/hw/pci/pci_ids.h b/include/hw/pci/pci_ids.h index 4c0002b..d14618a 100644 --- a/include/hw/pci/pci_ids.h +++ b/include/hw/pci/pci_ids.h @@ -58,6 +58,7 @@ #define PCI_DEVICE_ID_LSI_SAS1078 0x0060 #define PCI_VENDOR_ID_DEC 0x1011 +#define PCI_DEVICE_ID_DEC_21142 0x0019 #define PCI_DEVICE_ID_DEC_21154 0x0026 #define PCI_VENDOR_ID_CIRRUS 0x1013 -- 1.8.4.rc3