qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Qemu-devel] [PATCH pic32 v3 15/16] pic32: add file pic32_ethernet.c


From: Serge Vakulenko
Subject: [Qemu-devel] [PATCH pic32 v3 15/16] pic32: add file pic32_ethernet.c
Date: Sun, 5 Jul 2015 23:15:03 -0700

Implement pic32 Ethernet interface.

Signed-off-by: Serge Vakulenko <address@hidden>
---
 hw/mips/pic32_ethernet.c | 557 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 557 insertions(+)
 create mode 100644 hw/mips/pic32_ethernet.c

diff --git a/hw/mips/pic32_ethernet.c b/hw/mips/pic32_ethernet.c
new file mode 100644
index 0000000..383effd
--- /dev/null
+++ b/hw/mips/pic32_ethernet.c
@@ -0,0 +1,557 @@
+/*
+ * Ethernet port.
+ *
+ * Copyright (C) 2014-2015 Serge Vakulenko
+ *
+ * Permission to use, copy, modify, and distribute this software
+ * and its documentation for any purpose and without fee is hereby
+ * granted, provided that the above copyright notice appear in all
+ * copies and that both that the copyright notice and this
+ * permission notice and warranty disclaimer appear in supporting
+ * documentation, and that the name of the author not be used in
+ * advertising or publicity pertaining to distribution of the
+ * software without specific, written prior permission.
+ *
+ * The author disclaim all warranties with regard to this
+ * software, including all implied warranties of merchantability
+ * and fitness.  In no event shall the author be liable for any
+ * special, indirect or consequential damages or any damages
+ * whatsoever resulting from loss of use, data or profits, whether
+ * in an action of contract, negligence or other tortious action,
+ * arising out of or in connection with the use or performance of
+ * this software.
+ */
+#include "hw/hw.h"
+#include "exec/address-spaces.h"
+#include "sysemu/dma.h"
+#include "pic32_peripherals.h"
+
+#include "pic32mz.h"
+
+/*#define DPRINT printf */
+#ifndef DPRINT
+#define DPRINT(...) /*empty*/
+#endif
+
+/*
+ * TODO: add option to enable tracing.
+ */
+#define TRACE   0
+
+#define NAME_PIC32_ETH "pic32-eth"
+
+#define MIN_RX_SIZE 60
+
+/*
+ * PIC32 Ethernet device.
+ */
+struct _eth_t {
+    SysBusDevice parent_obj;
+
+    pic32_t     *pic32;
+    NICState    *eth_nic;       /* virtual network interface */
+    NICConf     eth_conf;       /* network configuration */
+
+    uint16_t    phy_reg[32];    /* PHY registers */
+
+};
+
+/*
+ * DMA buffer descriptor.
+ */
+typedef struct {
+    uint32_t hdr;               /* Flags */
+    uint32_t paddr;             /* Phys address of data buffer */
+    uint32_t ctl;               /* TX options / RX filter status */
+    uint32_t status;            /* Status */
+} eth_desc_t;
+
+/* Start of packet */
+#define DESC_SOP(d)             ((d)->hdr & 0x80000000)
+#define DESC_SET_SOP(d)         ((d)->hdr |= 0x80000000)
+#define DESC_CLEAR_SOP(d)       ((d)->hdr &= ~0x80000000)
+
+/* End of packet */
+#define DESC_EOP(d)             ((d)->hdr & 0x40000000)
+#define DESC_SET_EOP(d)         ((d)->hdr |= 0x40000000)
+#define DESC_CLEAR_EOP(d)       ((d)->hdr &= ~0x40000000)
+
+/* Number of data bytes */
+#define DESC_BYTECNT(d)         ((d)->hdr >> 16 & 0x7ff)
+#define DESC_SET_BYTECNT(d, n)  ((d)->hdr = ((d)->hdr & ~0x7ff0000) | (n) << 
16)
+
+/* Next descriptor pointer valid */
+#define DESC_NPV(d)             ((d)->hdr & 0x00000100)
+#define DESC_SET_NPV(d)         ((d)->hdr |= 0x00000100)
+#define DESC_CLEAR_NPV(d)       ((d)->hdr &= ~0x00000100)
+
+/* Eth controller owns this desc */
+#define DESC_EOWN(d)            ((d)->hdr & 0x00000080)
+#define DESC_SET_EOWN(d)        ((d)->hdr |= 0x00000080)
+#define DESC_CLEAR_EOWN(d)      ((d)->hdr &= ~0x00000080)
+
+/* Size of received packet */
+#define DESC_FRAMESZ(d)         ((d)->status & 0xffff)
+#define DESC_SET_FRAMESZ(d, n)  ((d)->status = ((d)->status & ~0xffff) | (n))
+
+/* Receive filter status */
+#define DESC_RXF(d)             ((d)->ctl >> 24)
+#define DESC_SET_RXF(d, n)      ((d)->ctl = ((d)->ctl & 0xffffff) | (n) << 24)
+
+/*-------------------------------------------------------------
+ * PHY declarations for SMSC LAN8720A/8740A chip.
+ */
+#define PHY_CONTROL             0       /* Basic Control Register */
+#define PHY_STATUS              1       /* Basic Status Register */
+#define PHY_ID1                 2       /* PHY identifier 1 */
+#define PHY_ID2                 3       /* PHY identifier 2 */
+#define PHY_MODE                18      /* Special Modes */
+#define PHY_SPECIAL             31      /* Special Control/Status Register */
+
+#define PHY_CONTROL_RESET       0x8000  /* Soft reset, bit self cleared */
+
+#define PHY_STATUS_CAP_100_FDX  0x4000  /* Can do 100Base-TX full duplex */
+#define PHY_STATUS_CAP_100_HDX  0x2000  /* Can do 100Base-TX half duplex */
+#define PHY_STATUS_CAP_10_FDX   0x1000  /* Can do 10Base-TX full duplex */
+#define PHY_STATUS_CAP_10_HDX   0x0800  /* Can do 10Base-TX half duplex */
+#define PHY_STATUS_ANEG_ACK     0x0020  /* Auto-negotiation acknowledge */
+#define PHY_STATUS_CAP_ANEG     0x0008  /* Auto-negotiation available */
+#define PHY_STATUS_LINK         0x0004  /* Link valid */
+
+#define PHY_MODE_PHYAD          0x000f  /* PHY address mask */
+
+#define PHY_SPECIAL_AUTODONE    0x1000  /* Auto-negotiation is done */
+#define PHY_SPECIAL_FDX         0x0010  /* Full duplex */
+#define PHY_SPECIAL_100         0x0008  /* Speed 100 Mbps */
+
+/*
+ * Reset the PHY chip.
+ */
+static void phy_reset(eth_t *e)
+{
+    DPRINT("---     PHY reset\n");
+    e->phy_reg[PHY_ID1]     = 0x0007;               /* Vendor: SMSC */
+    e->phy_reg[PHY_ID2]     = 0xc111;               /* Device: LAN8740A */
+    e->phy_reg[PHY_CONTROL] = 0x1000;
+    e->phy_reg[PHY_MODE]    = 0x4000;               /* RMII interface */
+    e->phy_reg[PHY_STATUS]  = PHY_STATUS_ANEG_ACK |
+                              PHY_STATUS_CAP_ANEG |
+                              PHY_STATUS_LINK |
+                              PHY_STATUS_CAP_100_FDX |
+                              PHY_STATUS_CAP_100_HDX |
+                              PHY_STATUS_CAP_10_FDX |
+                              PHY_STATUS_CAP_10_HDX;
+    e->phy_reg[PHY_SPECIAL] = PHY_SPECIAL_AUTODONE |
+                              PHY_SPECIAL_FDX |
+                              PHY_SPECIAL_100;
+}
+
+/*
+ * Reset the Ethernet controller.
+ */
+static void eth_reset(eth_t *e)
+{
+    uint8_t *macaddr = e->eth_conf.macaddr.a;
+    pic32_t *s = e->pic32;
+
+    DPRINT("--- %s()\n", __func__);
+    if (TRACE) {
+        fprintf(qemu_logfile, "--- Ethernet reset\n");
+    }
+    s->irq_clear(s, PIC32_IRQ_ETH);
+    VALUE(ETHSTAT) = 0;
+    VALUE(EMAC1SA2) = macaddr[0] | (macaddr[1] << 8);
+    VALUE(EMAC1SA1) = macaddr[2] | (macaddr[3] << 8);
+    VALUE(EMAC1SA0) = macaddr[4] | (macaddr[5] << 8);
+}
+
+/*
+ * Write to ETHCON1 register.
+ */
+void pic32_eth_control(pic32_t *s)
+{
+    eth_t *e = s->eth;
+
+    if (!(VALUE(ETHCON1) & PIC32_ETHCON1_ON)) {
+        /* Ethernet controller is disabled. */
+        DPRINT("--- %s() ETH disabled\n", __func__);
+        eth_reset(e);
+        return;
+    }
+
+    if (VALUE(ETHCON1) & PIC32_ETHCON1_TXRTS) {
+        /* Transmit request. */
+        eth_desc_t d = { 0 };
+        unsigned paddr = VALUE(ETHTXST);
+        unsigned nbytes, i;
+        unsigned char buf[2048];
+
+        d.hdr   = ldl_le_phys(&address_space_memory, paddr);
+        d.paddr = ldl_le_phys(&address_space_memory, paddr + 4);
+
+        nbytes = DESC_BYTECNT(&d);
+        DPRINT("--- %s() trasmit request %u bytes\n", __func__, nbytes);
+        DPRINT("--- TX desc [%08x] = %08x %08x\n", paddr, d.hdr, d.paddr);
+        if (TRACE) {
+            fprintf(qemu_logfile, "--- Ethernet transmit request, %u bytes\n",
+                nbytes);
+        }
+        if (nbytes > 0 && nbytes <= sizeof(buf)) {
+            for (i = 0; i < nbytes; i += 4)
+                *(uint32_t *) (buf + i) = ldl_le_phys(&address_space_memory,
+                                                      d.paddr + i);
+
+            DPRINT("--- %u bytes of data: %02x", nbytes, buf[0]);
+            for (i = 1; i < nbytes; i++) {
+                DPRINT("-%02x", buf[i]);
+            }
+            DPRINT("\n");
+
+            qemu_send_packet(qemu_get_queue(e->eth_nic), buf, nbytes);
+        }
+
+        /* Activate TXDONE interrupt. */
+        VALUE(ETHCON1) &= ~PIC32_ETHCON1_TXRTS;
+        VALUE(ETHIRQ) |= PIC32_ETHIRQ_TXDONE;
+        s->irq_raise(s, PIC32_IRQ_ETH);
+    }
+}
+
+/*
+ * Write to EMAC1MCMD register.
+ */
+void pic32_mii_command(pic32_t *s)
+{
+    eth_t *e = s->eth;
+    unsigned cmd = VALUE(EMAC1MCMD);
+    unsigned addr = VALUE(EMAC1MADR);
+    unsigned data;
+
+    if (cmd & (PIC32_EMAC1MCMD_READ | PIC32_EMAC1MCMD_SCAN)) {
+        data = e->phy_reg[addr & 0x1f];
+        /*DPRINT("--- %s() cmd = %04x, addr = %04x\n", __func__, cmd, addr);*/
+        DPRINT("--- %s() read register [%u] = %04x\n", __func__, addr & 0x1f,
+            data);
+        if (TRACE) {
+            fprintf(qemu_logfile,
+                "--- Ethernet MII read register [%u] = %04x\n",
+                addr & 0x1f, data);
+        }
+        VALUE(EMAC1MRDD) = data;
+    }
+}
+
+/*
+ * Write to EMAC1MWTD register.
+ */
+void pic32_mii_write(pic32_t *s)
+{
+    eth_t *e = s->eth;
+    unsigned addr = VALUE(EMAC1MADR);
+    unsigned data = VALUE(EMAC1MWTD);
+
+    DPRINT("--- %s() write register [%u] = %04x\n", __func__, addr & 0x1f,
+        data);
+    if (TRACE) {
+        fprintf(qemu_logfile, "--- Ethernet MII write register [%u] = %04x\n",
+            addr & 0x1f, data);
+    }
+    switch (addr & 0x1f) {
+    case PHY_CONTROL:                   /* Basic Control Register */
+        e->phy_reg[PHY_CONTROL] = data;
+        if (data & PHY_CONTROL_RESET) {
+            phy_reset(e);
+        }
+        break;
+    }
+}
+
+/*
+ * Return true when no receive buffers available.
+ */
+static int eth_buffer_full(pic32_t *s)
+{
+    eth_desc_t d = { 0 };
+
+    d.hdr = ldl_le_phys(&address_space_memory, VALUE(ETHRXST));
+    return !DESC_EOWN(&d);
+}
+
+/*
+ * Return true when we are ready to receive a packet.
+ */
+static int nic_can_receive(NetClientState *nc)
+{
+    eth_t *e = qemu_get_nic_opaque(nc);
+    pic32_t *s = e->pic32;
+
+    /*DPRINT("--- %s()\n", __func__);*/
+    if (!(VALUE(ETHCON1) & PIC32_ETHCON1_ON)) {
+        /* While interface is down,
+         * we can receive anything (and discard). */
+        return 1;
+    }
+
+    /* Check whether we have at least one receive buffer available. */
+    return !eth_buffer_full(s);
+}
+
+/*
+ * Receive a packet.
+ */
+static ssize_t nic_receive(NetClientState *nc, const uint8_t *buf, size_t size)
+{
+    eth_t *e = qemu_get_nic_opaque(nc);
+    pic32_t *s = e->pic32;
+    unsigned rxfc, rxf, rxst, rxst0, rxbufsz, nbytes, len;
+    int start_of_packet;
+    uint8_t buf1[60];
+    eth_desc_t *d, desc0, desc1;
+
+    DPRINT("--- %s: %d bytes\n", __func__, (int) size);
+    if (TRACE) {
+        fprintf(qemu_logfile, "--- Ethernet receive, %u bytes\n",
+            (unsigned)size);
+    }
+    if (!(VALUE(ETHCON1) & PIC32_ETHCON1_ON)) {
+        /* Ethernet controller is down. */
+        return -1;
+    }
+
+    if (eth_buffer_full(s)) {
+        return -1;
+    }
+
+    /* Cast acceptance filter. */
+    rxfc = VALUE(ETHRXFC);
+    if (buf[0] == 0xff && buf[1] == 0xff && buf[2] == 0xff &&
+        buf[3] == 0xff && buf[4] == 0xff && buf[5] == 0xff) {
+        /* Broadcast address. */
+        if (!(rxfc & PIC32_ETHRXFC_BCEN)) {
+            return size;
+        }
+        rxf = 0x40;
+    } else if (buf[0] & 0x01) {
+        /* Multicast. */
+        if (!(rxfc & PIC32_ETHRXFC_MCEN)) {
+            return size;
+        }
+        rxf = 0x80;
+    } else if (VALUE(EMAC1SA2) == (buf[0] | (buf[1] << 8)) ||
+               VALUE(EMAC1SA2) == (buf[2] | (buf[3] << 8)) ||
+               VALUE(EMAC1SA2) == (buf[4] | (buf[5] << 8))) {
+        /* Unicast for our MAC address. */
+        if (!(rxfc & PIC32_ETHRXFC_UCEN)) {
+            return size;
+        }
+        rxf = 0x20;
+    } else {
+        /* Not-Me Unicast. */
+        if (!(rxfc & PIC32_ETHRXFC_NOTMEEN)) {
+            return size;
+        }
+        rxf = 0x00;
+    }
+
+    /* If packet too small, then expand it */
+    nbytes = size;
+    if (nbytes < MIN_RX_SIZE) {
+        memcpy(buf1, buf, nbytes);
+        memset(buf1 + nbytes, 0, MIN_RX_SIZE - nbytes);
+        buf = buf1;
+        nbytes = MIN_RX_SIZE;
+    }
+
+    /* Copy data to descriptor chain. */
+    rxst0 = VALUE(ETHRXST);
+    rxbufsz = VALUE(ETHCON2) & 0x7f0;
+    start_of_packet = 1;
+    d = &desc0;
+    rxst = rxst0;
+    for (;;) {
+        /* Get descriptor. */
+        dma_memory_read(&address_space_memory, rxst, d, 16);
+        if (!DESC_EOWN(d) || (!start_of_packet && rxst == rxst0)) {
+            DPRINT("--- No more descriptors available\n");
+            VALUE(ETHIRQ) |= PIC32_ETHIRQ_RXBUFNA;
+            break;
+        }
+
+        /* Copy data to buffer. */
+        len = nbytes;
+        if (len > rxbufsz) {
+            len = rxbufsz;
+        }
+        DPRINT("--- Copy %d bytes to %08x\n", len, d->paddr);
+        dma_memory_write(&address_space_memory, d->paddr, buf, len);
+        buf += len;
+
+        /* Update the descriptor. */
+        DESC_CLEAR_EOWN(d);
+        DESC_SET_BYTECNT(d, len);
+        if (start_of_packet) {
+            DESC_SET_SOP(d);
+            DESC_SET_FRAMESZ(d, nbytes);
+            DESC_SET_RXF(d, rxf);
+            start_of_packet = 0;
+        } else {
+            DESC_CLEAR_SOP(d);
+        }
+        if (nbytes == len) {
+            DESC_SET_EOP(d);
+        } else {
+            DESC_CLEAR_EOP(d);
+        }
+
+        /* Write the descriptor back memory (all but the first). */
+        if (d == &desc1) {
+            DPRINT("--- RX desc [%08x] = %08x ... ... %08x\n", rxst,
+                desc1.hdr, desc1.status);
+            dma_memory_write(&address_space_memory, rxst, &desc1, 16);
+        }
+
+        /* Switch to next descriptor. */
+        rxst += 16;
+        if (DESC_NPV(d)) {
+            rxst = ldl_le_phys(&address_space_memory, rxst);
+        }
+        VALUE(ETHRXST) = rxst;
+        d = &desc1;
+
+        nbytes -= len;
+        if (nbytes == 0) {
+            /* Update the first descriptor. */
+            DPRINT("--- RX desc [%08x] = %08x ... ... %08x\n",
+                rxst0, desc0.hdr, desc0.status);
+            dma_memory_write(&address_space_memory, rxst0, &desc0, 16);
+
+            /* Packet successfully received. */
+            VALUE(ETHIRQ) |= PIC32_ETHIRQ_RXDONE;
+            break;
+        }
+    }
+
+    /* Activate interrupt. */
+    s->irq_raise(s, PIC32_IRQ_ETH);
+    return size;
+}
+
+/*
+ * Deallocate the QEMU network interface.
+ */
+static void nic_cleanup(NetClientState *nc)
+{
+    eth_t *e = qemu_get_nic_opaque(nc);
+
+    /*DPRINT("--- %s()\n", __func__);*/
+    e->eth_nic = 0;
+}
+
+/*
+ * Initialize Ethernet device.
+ */
+void pic32_eth_init(pic32_t *s, NICInfo *nd)
+{
+    DPRINT("--- %s()\n", __func__);
+
+    /* Create Ethernet device. */
+    s->eth_dev = qdev_create(NULL, NAME_PIC32_ETH);
+    qdev_set_nic_properties(s->eth_dev, nd);
+    qdev_init_nofail(s->eth_dev);
+
+    /* Setup back pointer from Ethernet device to pic32 object. */
+    s->eth = OBJECT_CHECK(eth_t, s->eth_dev, NAME_PIC32_ETH);
+    s->eth->pic32 = s;
+}
+
+/*
+ * Initialize pic32-eth object.
+ */
+static int eth_object_init(SysBusDevice *sbd)
+{
+    /* Table of methods for QEMU network interface. */
+    static NetClientInfo nic_info = {
+        .type           = NET_CLIENT_OPTIONS_KIND_NIC,
+        .size           = sizeof(NICState),
+        .can_receive    = nic_can_receive,
+        .receive        = nic_receive,
+        .cleanup        = nic_cleanup,
+    };
+    DeviceState *dev = DEVICE(sbd);
+    eth_t *e = OBJECT_CHECK(eth_t, dev, NAME_PIC32_ETH);
+
+    /*DPRINT("--- %s()\n", __func__);*/
+
+    /* Initialize MAC address. */
+    qemu_macaddr_default_if_unset(&e->eth_conf.macaddr);
+
+    /* Create a QEMU network interface. */
+    e->eth_nic = qemu_new_nic(&nic_info, &e->eth_conf,
+        object_get_typename(OBJECT(dev)), dev->id, e);
+
+    qemu_format_nic_info_str(qemu_get_queue(e->eth_nic),
+        e->eth_conf.macaddr.a);
+
+    return 0;
+}
+
+/*
+ * Reset pic32-eth object.
+ */
+static void eth_object_reset(DeviceState *dev)
+{
+    eth_t *e = OBJECT_CHECK(eth_t, dev, NAME_PIC32_ETH);
+
+    /*DPRINT("--- %s()\n", __func__);*/
+    eth_reset(e);
+    phy_reset(e);
+}
+
+/*
+ * Descriptor of eth_t data structure.
+ */
+static const VMStateDescription vmstate_info = {
+    .name               = NAME_PIC32_ETH,
+    .version_id         = 0,
+    .minimum_version_id = 0,
+    .fields             = (VMStateField[]) {
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+/*
+ * Build pic32-eth object.
+ */
+static void eth_object_constructor(ObjectClass *klass, void *data)
+{
+    static Property eth_properties[] = {
+        DEFINE_NIC_PROPERTIES(eth_t, eth_conf),
+        DEFINE_PROP_END_OF_LIST(),
+    };
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+    k->init = eth_object_init;
+    set_bit(DEVICE_CATEGORY_NETWORK, dc->categories);
+    dc->desc = "PIC32 Ethernet device";
+    dc->reset = eth_object_reset;
+    dc->vmsd = &vmstate_info;
+    dc->props = eth_properties;
+}
+
+/*
+ * Instantiate pic32-eth class.
+ */
+static void eth_register_types(void)
+{
+    static const TypeInfo eth_class_info = {
+        .name          = NAME_PIC32_ETH,
+        .parent        = TYPE_SYS_BUS_DEVICE,
+        .instance_size = sizeof(eth_t),
+        .class_init    = eth_object_constructor,
+    };
+
+    type_register_static(&eth_class_info);
+}
+
+type_init(eth_register_types)
-- 
2.2.2




reply via email to

[Prev in Thread] Current Thread [Next in Thread]