From patchwork Mon Jan 9 16:59:04 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Mark Langsdorf X-Patchwork-Id: 135079 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [140.186.70.17]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (Client did not present a certificate) by ozlabs.org (Postfix) with ESMTPS id A5D89B6FBA for ; Tue, 10 Jan 2012 04:35:59 +1100 (EST) Received: from localhost ([::1]:52498 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1RkIZx-0003qy-Ss for incoming@patchwork.ozlabs.org; Mon, 09 Jan 2012 12:00:13 -0500 Received: from eggs.gnu.org ([140.186.70.92]:50854) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1RkIZ8-0001OO-Sd for qemu-devel@nongnu.org; Mon, 09 Jan 2012 11:59:29 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1RkIZ1-0004b3-Ph for qemu-devel@nongnu.org; Mon, 09 Jan 2012 11:59:22 -0500 Received: from smtp161.dfw.emailsrvr.com ([67.192.241.161]:47896) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1RkIZ1-0004Zq-DV for qemu-devel@nongnu.org; Mon, 09 Jan 2012 11:59:15 -0500 Received: from localhost (localhost.localdomain [127.0.0.1]) by smtp6.relay.dfw1a.emailsrvr.com (SMTP Server) with ESMTP id 955FA2705E1; Mon, 9 Jan 2012 11:59:09 -0500 (EST) X-Virus-Scanned: OK Received: by smtp6.relay.dfw1a.emailsrvr.com (Authenticated sender: mark.langsdorf-AT-calxeda.com) with ESMTPSA id 4DA61270385; Mon, 9 Jan 2012 11:59:09 -0500 (EST) From: Mark Langsdorf To: qemu-devel@nongnu.org Date: Mon, 9 Jan 2012 10:59:04 -0600 Message-Id: <1326128349-3465-2-git-send-email-mark.langsdorf@calxeda.com> X-Mailer: git-send-email 1.7.5.4 In-Reply-To: <1326128349-3465-1-git-send-email-mark.langsdorf@calxeda.com> References: <1325793761-10249-1-git-send-email-mark.langsdorf@calxeda.com> <1326128349-3465-1-git-send-email-mark.langsdorf@calxeda.com> X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.6 (newer, 3) X-Received-From: 67.192.241.161 Cc: i.mitsyanko@gmail.com, peter.maydell@linaro.org, Rob Herring , afaerber@suse.de, Mark Langsdorf Subject: [Qemu-devel] [PATCH v5 1/6] Add xgmac ethernet model X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org This adds very basic support for XG-mac ethernet core from Synopsis and others. Missing things include: - statistics counters - WoL support - rx checksum offload - chained descriptors (only linear descriptor ring) - broadcast and multicast handling Signed-off-by: Rob Herring Signed-off-by: Mark Langsdorf --- Changes from v4 None Changes from v3 Added debug macro and cleaned up some debug code Refitted all lines to fit within 80 columns Changes from v2 None Changes from v1 Reformated most lines to fit within 80 columns Removed a bunch of unused variables in the state structures Got rid of some camelcase structure names Makefile.target | 1 + hw/xgmac.c | 424 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 425 insertions(+), 0 deletions(-) create mode 100644 hw/xgmac.c diff --git a/Makefile.target b/Makefile.target index 18be624..5780a5f 100644 --- a/Makefile.target +++ b/Makefile.target @@ -340,6 +340,7 @@ obj-arm-y += arm_l2x0.o obj-arm-y += arm_mptimer.o obj-arm-y += armv7m.o armv7m_nvic.o stellaris.o pl022.o stellaris_enet.o obj-arm-y += pl061.o +obj-arm-y += xgmac.o obj-arm-y += arm-semi.o obj-arm-y += pxa2xx.o pxa2xx_pic.o pxa2xx_gpio.o pxa2xx_timer.o pxa2xx_dma.o obj-arm-y += pxa2xx_lcd.o pxa2xx_mmci.o pxa2xx_pcmcia.o pxa2xx_keypad.o diff --git a/hw/xgmac.c b/hw/xgmac.c new file mode 100644 index 0000000..c19fff5 --- /dev/null +++ b/hw/xgmac.c @@ -0,0 +1,424 @@ +/* + * QEMU model of XGMAC Ethernet. + * + * derived from the Xilinx AXI-Ethernet by Edgar E. Iglesias. + * + * Copyright (c) 2011 Calxeda, Inc. + * + * 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 "sysbus.h" +#include "qemu-char.h" +#include "qemu-log.h" +#include "net.h" +#include "net/checksum.h" + +#ifdef DEBUG_XGMAC +#define DEBUGF_BRK(message, args...) do { \ + fprintf(stderr, (message), ## args); \ + } while (0) +#else +#define DEBUGF_BRK(message, args...) do { } while (0) +#endif + +#define XGMAC_CONTROL 0x00000000 /* MAC Configuration */ +#define XGMAC_FRAME_FILTER 0x00000001 /* MAC Frame Filter */ +#define XGMAC_FLOW_CTRL 0x00000006 /* MAC Flow Control */ +#define XGMAC_VLAN_TAG 0x00000007 /* VLAN Tags */ +#define XGMAC_VERSION 0x00000008 /* Version */ +/* VLAN tag for insertion or replacement into tx frames */ +#define XGMAC_VLAN_INCL 0x00000009 +#define XGMAC_LPI_CTRL 0x0000000a /* LPI Control and Status */ +#define XGMAC_LPI_TIMER 0x0000000b /* LPI Timers Control */ +#define XGMAC_TX_PACE 0x0000000c /* Transmit Pace and Stretch */ +#define XGMAC_VLAN_HASH 0x0000000d /* VLAN Hash Table */ +#define XGMAC_DEBUG 0x0000000e /* Debug */ +#define XGMAC_INT_STATUS 0x0000000f /* Interrupt and Control */ +/* HASH table registers */ +#define XGMAC_HASH(n) ((0x00000300/4) + (n)) +#define XGMAC_NUM_HASH 16 +/* Operation Mode */ +#define XGMAC_OPMODE (0x00000400/4) +/* Remote Wake-Up Frame Filter */ +#define XGMAC_REMOTE_WAKE (0x00000700/4) +/* PMT Control and Status */ +#define XGMAC_PMT (0x00000704/4) + +#define XGMAC_ADDR_HIGH(reg) (0x00000010+((reg) * 2)) +#define XGMAC_ADDR_LOW(reg) (0x00000011+((reg) * 2)) + +#define DMA_BUS_MODE 0x000003c0 /* Bus Mode */ +#define DMA_XMT_POLL_DEMAND 0x000003c1 /* Transmit Poll Demand */ +#define DMA_RCV_POLL_DEMAND 0x000003c2 /* Received Poll Demand */ +#define DMA_RCV_BASE_ADDR 0x000003c3 /* Receive List Base */ +#define DMA_TX_BASE_ADDR 0x000003c4 /* Transmit List Base */ +#define DMA_STATUS 0x000003c5 /* Status Register */ +#define DMA_CONTROL 0x000003c6 /* Ctrl (Operational Mode) */ +#define DMA_INTR_ENA 0x000003c7 /* Interrupt Enable */ +#define DMA_MISSED_FRAME_CTR 0x000003c8 /* Missed Frame Counter */ +/* Receive Interrupt Watchdog Timer */ +#define DMA_RI_WATCHDOG_TIMER 0x000003c9 +#define DMA_AXI_BUS 0x000003ca /* AXI Bus Mode */ +#define DMA_AXI_STATUS 0x000003cb /* AXI Status */ +#define DMA_CUR_TX_DESC_ADDR 0x000003d2 /* Current Host Tx Descriptor */ +#define DMA_CUR_RX_DESC_ADDR 0x000003d3 /* Current Host Rx Descriptor */ +#define DMA_CUR_TX_BUF_ADDR 0x000003d4 /* Current Host Tx Buffer */ +#define DMA_CUR_RX_BUF_ADDR 0x000003d5 /* Current Host Rx Buffer */ +#define DMA_HW_FEATURE 0x000003d6 /* Enabled Hardware Features */ + +/* DMA Status register defines */ +#define DMA_STATUS_GMI 0x08000000 /* MMC interrupt */ +#define DMA_STATUS_GLI 0x04000000 /* GMAC Line interface int */ +#define DMA_STATUS_EB_MASK 0x00380000 /* Error Bits Mask */ +#define DMA_STATUS_EB_TX_ABORT 0x00080000 /* Error Bits - TX Abort */ +#define DMA_STATUS_EB_RX_ABORT 0x00100000 /* Error Bits - RX Abort */ +#define DMA_STATUS_TS_MASK 0x00700000 /* Transmit Process State */ +#define DMA_STATUS_TS_SHIFT 20 +#define DMA_STATUS_RS_MASK 0x000e0000 /* Receive Process State */ +#define DMA_STATUS_RS_SHIFT 17 +#define DMA_STATUS_NIS 0x00010000 /* Normal Interrupt Summary */ +#define DMA_STATUS_AIS 0x00008000 /* Abnormal Interrupt Summary */ +#define DMA_STATUS_ERI 0x00004000 /* Early Receive Interrupt */ +#define DMA_STATUS_FBI 0x00002000 /* Fatal Bus Error Interrupt */ +#define DMA_STATUS_ETI 0x00000400 /* Early Transmit Interrupt */ +#define DMA_STATUS_RWT 0x00000200 /* Receive Watchdog Timeout */ +#define DMA_STATUS_RPS 0x00000100 /* Receive Process Stopped */ +#define DMA_STATUS_RU 0x00000080 /* Receive Buffer Unavailable */ +#define DMA_STATUS_RI 0x00000040 /* Receive Interrupt */ +#define DMA_STATUS_UNF 0x00000020 /* Transmit Underflow */ +#define DMA_STATUS_OVF 0x00000010 /* Receive Overflow */ +#define DMA_STATUS_TJT 0x00000008 /* Transmit Jabber Timeout */ +#define DMA_STATUS_TU 0x00000004 /* Transmit Buffer Unavailable */ +#define DMA_STATUS_TPS 0x00000002 /* Transmit Process Stopped */ +#define DMA_STATUS_TI 0x00000001 /* Transmit Interrupt */ + +/* DMA Control register defines */ +#define DMA_CONTROL_ST 0x00002000 /* Start/Stop Transmission */ +#define DMA_CONTROL_SR 0x00000002 /* Start/Stop Receive */ +#define DMA_CONTROL_DFF 0x01000000 /* Disable flush of rx frames */ + +struct desc { + uint32_t ctl_stat; + uint16_t buffer1_size; + uint16_t buffer2_size; + uint32_t buffer1_addr; + uint32_t buffer2_addr; + uint32_t ext_stat; + uint32_t res[3]; +}; + +#define R_MAX 0x400 + +typedef struct rxtx_stats { + uint64_t rx_bytes; + uint64_t tx_bytes; + + uint64_t rx; + uint64_t rx_bcast; + uint64_t rx_mcast; +} rxtx_stats; + +typedef struct xgmac_state { + SysBusDevice busdev; + MemoryRegion iomem; + qemu_irq sbd_irq; + qemu_irq pmt_irq; + qemu_irq mci_irq; + NICState *nic; + NICConf conf; + + uint32_t c_phyaddr; + struct rxtx_stats stats; + uint32_t regs[R_MAX]; +} xgmac_state; + +const VMStateDescription vmstate_rxtx_stats = { + .name = "xgmac_stats", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT64(rx_bytes, rxtx_stats), + VMSTATE_UINT64(tx_bytes, rxtx_stats), + VMSTATE_UINT64(rx, rxtx_stats), + VMSTATE_UINT64(rx_bcast, rxtx_stats), + VMSTATE_UINT64(rx_mcast, rxtx_stats), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_xgmac = { + .name = "xgmac", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(c_phyaddr, xgmac_state), + VMSTATE_STRUCT(stats, xgmac_state, 0, vmstate_rxtx_stats, rxtx_stats), + VMSTATE_UINT32_ARRAY(regs, xgmac_state, R_MAX), + VMSTATE_END_OF_LIST() + } +}; + +static void xgmac_read_desc(struct xgmac_state *s, struct desc *d, int rx) +{ + uint32_t addr = rx ? s->regs[DMA_CUR_RX_DESC_ADDR] : + s->regs[DMA_CUR_TX_DESC_ADDR]; + cpu_physical_memory_read(addr, d, sizeof(*d)); +} + +static void xgmac_write_desc(struct xgmac_state *s, struct desc *d, int rx) +{ + int reg = rx ? DMA_CUR_RX_DESC_ADDR : DMA_CUR_TX_DESC_ADDR; + uint32_t addr = s->regs[reg]; + + if (!rx && (d->ctl_stat & 0x00200000)) { + s->regs[reg] = s->regs[DMA_TX_BASE_ADDR]; + } else if (rx && (d->buffer1_size & 0x8000)) { + s->regs[reg] = s->regs[DMA_RCV_BASE_ADDR]; + } else { + s->regs[reg] += sizeof(*d); + } + cpu_physical_memory_write(addr, d, sizeof(*d)); +} + +static void xgmac_enet_send(struct xgmac_state *s) +{ + struct desc bd; + int frame_size; + int len; + uint8_t frame[8192]; + uint8_t *ptr; + + ptr = frame; + frame_size = 0; + while (1) { + xgmac_read_desc(s, &bd, 0); + if ((bd.ctl_stat & 0x80000000) == 0) { + /* Run out of descriptors to transmit. */ + break; + } + len = (bd.buffer1_size & 0xfff) + (bd.buffer2_size & 0xfff); + + if ((bd.buffer1_size & 0xfff) > 2048) { + DEBUGF_BRK("qemu:%s:ERROR...ERROR...ERROR... -- " + "xgmac buffer 1 len on send > 2048 (0x%x)\n", + __func__, bd.buffer1_size & 0xfff); + } + if ((bd.buffer2_size & 0xfff) != 0) { + DEBUGF_BRK("qemu:%s:ERROR...ERROR...ERROR... -- " + "xgmac buffer 2 len on send != 0 (0x%x)\n", + __func__, bd.buffer2_size & 0xfff); + } + if (len >= sizeof(frame)) { + DEBUGF_BRK("qemu:%s: buffer overflow %d read into %zu " + "buffer\n" , __func__, len, sizeof(frame)); + DEBUGF_BRK("qemu:%s: buffer1.size=%d; buffer2.size=%d\n", + __func__, bd.buffer1_size, bd.buffer2_size); + } + + cpu_physical_memory_read(bd.buffer1_addr, ptr, len); + ptr += len; + frame_size += len; + if (bd.ctl_stat & 0x20000000) { + /* Last buffer in frame. */ + qemu_send_packet(&s->nic->nc, frame, len); + ptr = frame; + frame_size = 0; + s->regs[DMA_STATUS] |= DMA_STATUS_TI | DMA_STATUS_NIS; + } + bd.ctl_stat &= ~0x80000000; + /* Write back the modified descriptor. */ + xgmac_write_desc(s, &bd, 0); + } +} + +static void enet_update_irq(struct xgmac_state *s) +{ + int stat = s->regs[DMA_STATUS] & s->regs[DMA_INTR_ENA]; + qemu_set_irq(s->sbd_irq, !!stat); +} + +static uint64_t enet_read(void *opaque, target_phys_addr_t addr, unsigned size) +{ + struct xgmac_state *s = opaque; + uint64_t r = 0; + addr >>= 2; + + switch (addr) { + case XGMAC_VERSION: + r = 0x1012; + break; + default: + if (addr < ARRAY_SIZE(s->regs)) { + r = s->regs[addr]; + } + break; + } + return r; +} + +static void enet_write(void *opaque, target_phys_addr_t addr, + uint64_t value, unsigned size) +{ + struct xgmac_state *s = opaque; + + addr >>= 2; + switch (addr) { + case DMA_BUS_MODE: + s->regs[DMA_BUS_MODE] = value & ~0x1; + break; + case DMA_XMT_POLL_DEMAND: + xgmac_enet_send(s); + break; + case DMA_STATUS: + s->regs[DMA_STATUS] = s->regs[DMA_STATUS] & ~value; + break; + case DMA_RCV_BASE_ADDR: + s->regs[DMA_RCV_BASE_ADDR] = s->regs[DMA_CUR_RX_DESC_ADDR] = value; + break; + case DMA_TX_BASE_ADDR: + s->regs[DMA_TX_BASE_ADDR] = s->regs[DMA_CUR_TX_DESC_ADDR] = value; + break; + default: + if (addr < ARRAY_SIZE(s->regs)) { + s->regs[addr] = value; + } + break; + } + enet_update_irq(s); +} + +static const MemoryRegionOps enet_mem_ops = { + .read = enet_read, + .write = enet_write, + .endianness = DEVICE_LITTLE_ENDIAN, +}; + +static int eth_can_rx(VLANClientState *nc) +{ + struct xgmac_state *s = DO_UPCAST(NICState, nc, nc)->opaque; + + /* RX enabled? */ + return s->regs[DMA_CONTROL] & DMA_CONTROL_SR; +} + +static ssize_t eth_rx(VLANClientState *nc, const uint8_t *buf, size_t size) +{ + struct xgmac_state *s = DO_UPCAST(NICState, nc, nc)->opaque; + static const unsigned char sa_bcast[6] = {0xff, 0xff, 0xff, + 0xff, 0xff, 0xff}; + int unicast, broadcast, multicast; + struct desc bd; + ssize_t ret; + + unicast = ~buf[0] & 0x1; + broadcast = memcmp(buf, sa_bcast, 6) == 0; + multicast = !unicast && !broadcast; + if (size < 12) { + s->regs[DMA_STATUS] |= DMA_STATUS_RI | DMA_STATUS_NIS; + ret = -1; + goto out; + } + + xgmac_read_desc(s, &bd, 1); + if ((bd.ctl_stat & 0x80000000) == 0) { + s->regs[DMA_STATUS] |= DMA_STATUS_RU | DMA_STATUS_AIS; + ret = size; + goto out; + } + + cpu_physical_memory_write(bd.buffer1_addr, buf, size); + + /* Add in the 4 bytes for crc (the real hw returns length incl crc) */ + size += 4; + bd.ctl_stat = (size << 16) | 0x300; + xgmac_write_desc(s, &bd, 1); + + s->stats.rx_bytes += size; + s->stats.rx++; + if (multicast) { + s->stats.rx_mcast++; + } else if (broadcast) { + s->stats.rx_bcast++; + } + + s->regs[DMA_STATUS] |= DMA_STATUS_RI | DMA_STATUS_NIS; + ret = size; + +out: + enet_update_irq(s); + return ret; +} + +static void eth_cleanup(VLANClientState *nc) +{ + struct xgmac_state *s = DO_UPCAST(NICState, nc, nc)->opaque; + s->nic = NULL; +} + +static NetClientInfo net_xgmac_enet_info = { + .type = NET_CLIENT_TYPE_NIC, + .size = sizeof(NICState), + .can_receive = eth_can_rx, + .receive = eth_rx, + .cleanup = eth_cleanup, +}; + +static int xgmac_enet_init(SysBusDevice *dev) +{ + struct xgmac_state *s = FROM_SYSBUS(typeof(*s), dev); + + memory_region_init_io(&s->iomem, &enet_mem_ops, s, "xgmac", 0x1000); + sysbus_init_mmio(dev, &s->iomem); + sysbus_init_irq(dev, &s->sbd_irq); + sysbus_init_irq(dev, &s->pmt_irq); + sysbus_init_irq(dev, &s->mci_irq); + + qemu_macaddr_default_if_unset(&s->conf.macaddr); + s->nic = qemu_new_nic(&net_xgmac_enet_info, &s->conf, + dev->qdev.info->name, dev->qdev.id, s); + qemu_format_nic_info_str(&s->nic->nc, s->conf.macaddr.a); + + s->regs[XGMAC_ADDR_HIGH(0)] = (s->conf.macaddr.a[5] << 8) | + s->conf.macaddr.a[4]; + s->regs[XGMAC_ADDR_LOW(0)] = (s->conf.macaddr.a[3] << 24) | + (s->conf.macaddr.a[2] << 16) | + (s->conf.macaddr.a[1] << 8) | + s->conf.macaddr.a[0]; + + return 0; +} + +static SysBusDeviceInfo xgmac_enet_info = { + .init = xgmac_enet_init, + .qdev.name = "xgmac", + .qdev.size = sizeof(struct xgmac_state), + .qdev.vmsd = &vmstate_xgmac, + .qdev.props = (Property[]) { + DEFINE_PROP_UINT32("phyaddr", struct xgmac_state, c_phyaddr, 7), + DEFINE_NIC_PROPERTIES(struct xgmac_state, conf), + DEFINE_PROP_END_OF_LIST(), + } +}; +static void xgmac_enet_register(void) +{ + sysbus_register_withprop(&xgmac_enet_info); +} + +device_init(xgmac_enet_register)