diff mbox

[v3,15/20] arm: add Faraday FTMAC110 10/100Mbps ethernet support

Message ID 1360143925-10800-16-git-send-email-dantesu@gmail.com
State New
Headers show

Commit Message

Kuo-Jung Su Feb. 6, 2013, 9:45 a.m. UTC
From: Kuo-Jung Su <dantesu@faraday-tech.com>

The FTMAC110 is an Ethernet controller that provides AHB master capability
and is in full compliance with the IEEE 802.3 10/100 Mbps specifications.
Its DMA controller handles all data transfers between system memory
and on-chip memories.
It supports half-word data transfer for Linux. However it has a weird DMA
alignment issue:

(1) Tx DMA Buffer Address:
    1 bytes aligned: Invalid
    2 bytes aligned: O.K
    4 bytes aligned: O.K

(2) Rx DMA Buffer Address:
    1 bytes aligned: Invalid
    2 bytes aligned: O.K
    4 bytes aligned: Invalid (It means 0x0, 0x4, 0x8, 0xC are invalid)

Signed-off-by: Kuo-Jung Su <dantesu@faraday-tech.com>
---
 hw/arm/Makefile.objs  |    1 +
 hw/arm/faraday_a360.c |   10 +
 hw/arm/ftmac110.c     |  681 +++++++++++++++++++++++++++++++++++++++++++++++++
 hw/arm/ftmac110.h     |  131 ++++++++++
 4 files changed, 823 insertions(+)
 create mode 100644 hw/arm/ftmac110.c
 create mode 100644 hw/arm/ftmac110.h

Comments

Blue Swirl Feb. 9, 2013, 1:03 p.m. UTC | #1
On Wed, Feb 6, 2013 at 9:45 AM, Kuo-Jung Su <dantesu@gmail.com> wrote:
> From: Kuo-Jung Su <dantesu@faraday-tech.com>
>
> The FTMAC110 is an Ethernet controller that provides AHB master capability
> and is in full compliance with the IEEE 802.3 10/100 Mbps specifications.
> Its DMA controller handles all data transfers between system memory
> and on-chip memories.
> It supports half-word data transfer for Linux. However it has a weird DMA
> alignment issue:
>
> (1) Tx DMA Buffer Address:
>     1 bytes aligned: Invalid
>     2 bytes aligned: O.K
>     4 bytes aligned: O.K
>
> (2) Rx DMA Buffer Address:
>     1 bytes aligned: Invalid
>     2 bytes aligned: O.K
>     4 bytes aligned: Invalid (It means 0x0, 0x4, 0x8, 0xC are invalid)
>
> Signed-off-by: Kuo-Jung Su <dantesu@faraday-tech.com>
> ---
>  hw/arm/Makefile.objs  |    1 +
>  hw/arm/faraday_a360.c |   10 +
>  hw/arm/ftmac110.c     |  681 +++++++++++++++++++++++++++++++++++++++++++++++++
>  hw/arm/ftmac110.h     |  131 ++++++++++
>  4 files changed, 823 insertions(+)
>  create mode 100644 hw/arm/ftmac110.c
>  create mode 100644 hw/arm/ftmac110.h
>
> diff --git a/hw/arm/Makefile.objs b/hw/arm/Makefile.objs
> index 70d4f25..f5eeaeb 100644
> --- a/hw/arm/Makefile.objs
> +++ b/hw/arm/Makefile.objs
> @@ -47,3 +47,4 @@ obj-y += ftapbbrg020.o
>  obj-y += ftnandc021.o
>  obj-y += fti2c010.o
>  obj-y += ftssp010.o
> +obj-y += ftmac110.o
> diff --git a/hw/arm/faraday_a360.c b/hw/arm/faraday_a360.c
> index 52cfcec..51e8649 100644
> --- a/hw/arm/faraday_a360.c
> +++ b/hw/arm/faraday_a360.c
> @@ -31,6 +31,7 @@ a360_device_init(A360State *s)
>      qemu_irq *pic;
>      DeviceState *ds, *fl;
>      SSIBus *spi;
> +    int done_nic = 0;
>      int i, nr_flash;
>      qemu_irq cs_line;
>      qemu_irq ack, req;
> @@ -122,6 +123,15 @@ a360_device_init(A360State *s)
>      req = qdev_get_gpio_in(s->pdma[0], 2);
>      qdev_connect_gpio_out(s->pdma[0], 2, ack);
>      qdev_connect_gpio_out(ds, 1, req);
> +
> +    /* ftmac110 */
> +    for (i = 0; i < nb_nics; i++) {
> +        NICInfo *nd = &nd_table[i];
> +        if (!done_nic && (!nd->model || strcmp(nd->model, "ftmac110") == 0)) {
> +            ftmac110_init(nd, 0x90900000, pic[25]);
> +            done_nic = 1;
> +        }
> +    }
>  }
>
>  static void
> diff --git a/hw/arm/ftmac110.c b/hw/arm/ftmac110.c
> new file mode 100644
> index 0000000..d45f4ba
> --- /dev/null
> +++ b/hw/arm/ftmac110.c
> @@ -0,0 +1,681 @@
> +/*
> + * QEMU model of the FTMAC110 Controller
> + *
> + * Copyright (C) 2012 Faraday Technology
> + * Written by Dante Su <dantesu@faraday-tech.com>
> + *
> + * This file is licensed under GNU GPL v2+.
> + */
> +
> +/*******************************************************************/
> +/*               FTMAC110 DMA design issue                         */
> +/*                                             Dante Su 2010.02.03 */
> +/*                                                                 */
> +/* The DMA engine has a weird restriction that its Rx DMA engine   */
> +/* accepts only 16-bits aligned address, 32-bits aligned is still  */
> +/* invalid. However this restriction does not apply to Tx DMA.     */
> +/* Conclusion:                                                     */
> +/* (1) Tx DMA Buffer Address:                                      */
> +/*     1 bytes aligned: Invalid                                    */
> +/*     2 bytes aligned: O.K                                        */
> +/*     4 bytes aligned: O.K (-> u-boot ZeroCopy is possible)       */
> +/* (2) Rx DMA Buffer Address:                                      */
> +/*     1 bytes aligned: Invalid                                    */
> +/*     2 bytes aligned: O.K                                        */
> +/*     4 bytes aligned: Invalid                                    */
> +/*******************************************************************/

Does this also apply to descriptors?

> +
> +#include <hw/sysbus.h>
> +#include <sysemu/sysemu.h>
> +#include <net/net.h>
> +
> +#include "faraday.h"
> +#include "ftmac110.h"
> +
> +#define TYPE_FTMAC110    "ftmac110"
> +
> +typedef struct Ftmac110State {
> +    SysBusDevice busdev;
> +    MemoryRegion mmio;
> +
> +    QEMUBH *bh;
> +    qemu_irq irq;
> +    NICState *nic;
> +    NICConf conf;
> +
> +    uint32_t isr;
> +    uint32_t ier;
> +    uint32_t mhash[2];
> +    uint32_t tx_bar;
> +    uint32_t rx_bar;
> +    uint32_t tx_idx;
> +    uint32_t rx_idx;
> +    uint32_t maccr;
> +    uint32_t macsr;
> +    uint32_t phycr;
> +    uint32_t phycr_rd;
> +
> +    struct {
> +        uint8_t  buf[2048];
> +        uint32_t len;
> +    } txbuff;
> +
> +    uint32_t rx_pkt;
> +    uint32_t rx_bcst;
> +    uint32_t rx_mcst;
> +    uint16_t rx_runt;
> +    uint16_t rx_drop;
> +    uint16_t rx_crc;
> +    uint16_t rx_ftl;
> +    uint32_t tx_pkt;
> +
> +} Ftmac110State;
> +
> +#define FTMAC110(obj) \
> +    OBJECT_CHECK(Ftmac110State, obj, TYPE_FTMAC110)
> +
> +static uint8_t bitrev8(uint8_t v)
> +{
> +    int i;
> +    uint8_t r = 0;
> +    for (i = 0; i < 8; ++i) {
> +        if (v & (1 << i)) {
> +            r |= (1 << (7 - i));
> +        }
> +    }
> +    return r;
> +}
> +
> +static int ftmac110_mcast_hash(int len, const uint8_t *p)
> +{
> +#define CRCPOLY_LE 0xedb88320
> +    int i;
> +    uint32_t crc = 0xFFFFFFFF;
> +
> +    while (len--) {
> +        crc ^= *p++;
> +        for (i = 0; i < 8; i++) {
> +            crc = (crc >> 1) ^ ((crc & 1) ? CRCPOLY_LE : 0);
> +        }
> +    }
> +
> +    /* Reverse CRC32 and return MSB 6 bits only */
> +    return bitrev8(crc >> 24) >> 2;
> +}
> +
> +static void ftmac110_read_rxdesc(hwaddr addr, Ftmac110RXD *desc)
> +{
> +    int i;
> +    uint32_t *p = (uint32_t *)desc;
> +
> +    if (addr & 0x0f) {
> +        hw_error("ftmac110: Rx desc is not 16-byte aligned!\n"
> +                 "It's fine in QEMU but the real HW would panic.\n");
> +    }
> +
> +    cpu_physical_memory_read(addr, desc, sizeof(*desc));
> +
> +    for (i = 0; i < sizeof(*desc); i += 4) {
> +        *p = le32_to_cpu(*p);
> +    }
> +
> +    if ((desc->buf & 0x1) || !(desc->buf % 4)) {
> +        hw_error("ftmac110: rx buffer is not exactly 16-bit aligned.\n");
> +    }
> +}
> +
> +static void ftmac110_write_rxdesc(hwaddr addr, Ftmac110RXD *desc)
> +{
> +    int i;
> +    uint32_t *p = (uint32_t *)desc;
> +

Alignment check probably also here.

> +    for (i = 0; i < sizeof(*desc); i += 4) {
> +        *p = cpu_to_le32(*p);
> +    }
> +
> +    cpu_physical_memory_write(addr, desc, sizeof(*desc));
> +}
> +
> +static void ftmac110_read_txdesc(hwaddr addr, Ftmac110TXD *desc)
> +{
> +    int i;
> +    uint32_t *p = (uint32_t *)desc;
> +
> +    if (addr & 0x0f) {
> +        hw_error("ftmac110: Tx desc is not 16-byte aligned!\n"
> +                 "It's fine in QEMU but the real HW would panic.\n");
> +    }
> +
> +    cpu_physical_memory_read(addr, desc, sizeof(*desc));
> +
> +    for (i = 0; i < sizeof(*desc); i += 4) {
> +        *p = le32_to_cpu(*p);
> +    }
> +
> +    if (desc->buf & 0x1) {
> +        hw_error("ftmac110: tx buffer is not 16-bit aligned.\n");
> +    }
> +}
> +
> +static void ftmac110_write_txdesc(hwaddr addr, Ftmac110TXD *desc)
> +{
> +    int i;
> +    uint32_t *p = (uint32_t *)desc;
> +
> +    for (i = 0; i < sizeof(*desc); i += 4) {
> +        *p = cpu_to_le32(*p);
> +    }
> +

Ditto.

> +    cpu_physical_memory_write(addr, desc, sizeof(*desc));
> +}
> +
> +static void ftmac110_update_irq(Ftmac110State *s)
> +{
> +    if (s->isr & s->ier) {
> +        qemu_set_irq(s->irq, 1);
> +    } else {
> +        qemu_set_irq(s->irq, 0);
> +    }
> +}
> +
> +static int ftmac110_can_receive(NetClientState *nc)
> +{
> +    Ftmac110State *s = FTMAC110(DO_UPCAST(NICState, nc, nc)->opaque);
> +    Ftmac110RXD rxd;
> +    hwaddr off = s->rx_bar + s->rx_idx * sizeof(rxd);
> +
> +    if ((s->maccr & (MACCR_RCV_EN | MACCR_RDMA_EN))
> +            != (MACCR_RCV_EN | MACCR_RDMA_EN)) {
> +        return 0;
> +    }
> +
> +    ftmac110_read_rxdesc(off, &rxd);
> +
> +    return rxd.owner;
> +}
> +
> +static ssize_t ftmac110_receive(NetClientState *nc,
> +                                const uint8_t  *buf,
> +                                size_t          size)
> +{
> +    const uint8_t *ptr = buf;
> +    hwaddr off;
> +    size_t len;
> +    Ftmac110RXD rxd;
> +    Ftmac110State *s = FTMAC110(DO_UPCAST(NICState, nc, nc)->opaque);
> +    int bcst, mcst, ftl, proto;
> +
> +    if ((s->maccr & (MACCR_RCV_EN | MACCR_RDMA_EN))
> +            != (MACCR_RCV_EN | MACCR_RDMA_EN)) {
> +        return -1;
> +    }
> +
> +    /*
> +     * Check if it's a long frame. (CRC32 is excluded)
> +     */
> +    proto = (buf[12] << 8) | buf[13];
> +    if (proto == 0x8100) {  /* 802.1Q VLAN */
> +        ftl = (size > 1518) ? 1 : 0;
> +    } else {
> +        ftl = (size > 1514) ? 1 : 0;
> +    }
> +    if (ftl) {
> +        DPRINTF("ftmac110_receive: frame too long, drop it\n");
> +        return -1;
> +    }
> +
> +    /* if it's a broadcast */
> +    if ((buf[0] == 0xff) && (buf[1] == 0xff) && (buf[2] == 0xff)
> +            && (buf[3] == 0xff) && (buf[4] == 0xff) && (buf[5] == 0xff)) {
> +        bcst = 1;
> +        if (!(s->maccr & MACCR_RCV_ALL) && !(s->maccr & MACCR_RX_BROADPKT)) {
> +            DPRINTF("ftmac110_receive: bcst filtered\n");
> +            return -1;
> +        }
> +    } else {
> +        bcst = 0;
> +    }
> +
> +    /* if it's a multicast */
> +    if ((buf[0] == 0x01) && (buf[1] == 0x00) && (buf[2] == 0x5e)
> +        && (buf[3] <= 0x7f)) {
> +        mcst = 1;
> +        if (!(s->maccr & MACCR_RCV_ALL) && !(s->maccr & MACCR_RX_MULTIPKT)) {
> +            int hash;
> +            if (!(s->maccr & MACCR_HT_MULTI_EN)) {
> +                DPRINTF("ftmac110_receive: mcst filtered\n");
> +                return -1;
> +            }
> +            hash = ftmac110_mcast_hash(6, buf);
> +            if (!(s->mhash[hash / 32] & (1 << (hash % 32)))) {
> +                DPRINTF("ftmac110_receive: mcst filtered\n");
> +                return -1;
> +            }
> +        }
> +    } else {
> +        mcst = 0;
> +    }
> +
> +    /* check if the destination matches NIC mac address */
> +    if (!(s->maccr & MACCR_RCV_ALL) && !bcst && !mcst) {
> +        if (memcmp(s->conf.macaddr.a, buf, 6)) {
> +            return -1;
> +        }
> +    }
> +
> +    while (size > 0) {
> +        off = s->rx_bar + s->rx_idx * sizeof(rxd);
> +        ftmac110_read_rxdesc(off, &rxd);
> +        if (!rxd.owner) {
> +            s->isr |= ISR_NORXBUF;
> +            DPRINTF("ftmac110: out of rxd!?\n");
> +            return -1;
> +        }
> +
> +        if (ptr == buf) {
> +            rxd.frs = 1;
> +        } else {
> +            rxd.frs = 0;
> +        }
> +
> +        len = size > rxd.bufsz ? rxd.bufsz : size;

This write below is the DMA that the comment above talks about, but
there's no alignment check here either.

> +        cpu_physical_memory_write(rxd.buf, (uint8_t *)ptr, len);
> +        ptr  += len;
> +        size -= len;
> +
> +        if (size <= 0) {
> +            rxd.lrs = 1;
> +        } else {
> +            rxd.lrs = 0;
> +        }
> +
> +        rxd.len = len;
> +        rxd.bcast = bcst;
> +        rxd.mcast = mcst;
> +        rxd.owner = 0;
> +
> +        /* write-back the rx descriptor */
> +        ftmac110_write_rxdesc(off, &rxd);
> +
> +        if (rxd.end) {
> +            s->rx_idx = 0;
> +        } else {
> +            s->rx_idx += 1;
> +        }
> +    }
> +
> +    /* update interrupt signal */
> +    s->isr |= ISR_RPKT_OK | ISR_RPKT_FINISH;
> +    ftmac110_update_irq(s);
> +
> +    return (ssize_t)((uint32_t)ptr - (uint32_t)buf);
> +}
> +
> +static void ftmac110_transmit(Ftmac110State *s, uint32_t *bar, uint32_t *idx)
> +{
> +    hwaddr off;
> +    uint8_t *buf;
> +    int ftl, proto;
> +    Ftmac110TXD txd;
> +
> +    if ((s->maccr & (MACCR_XMT_EN | MACCR_XDMA_EN))
> +            != (MACCR_XMT_EN | MACCR_XDMA_EN)) {
> +        return;
> +    }
> +
> +    do {
> +        off = (*bar) + (*idx) * sizeof(txd);
> +        ftmac110_read_txdesc(off, &txd);
> +        if (!txd.owner) {
> +            s->isr |= ISR_NOTXBUF;
> +            break;
> +        }
> +        if (txd.fts) {
> +            s->txbuff.len = 0;
> +        }
> +        if (txd.len + s->txbuff.len > sizeof(s->txbuff.buf)) {
> +            hw_error("ftmac110: tx buffer overflow!\n");
> +            exit(1);
> +        }
> +        buf = s->txbuff.buf + s->txbuff.len;

Ditto.

> +        cpu_physical_memory_read(txd.buf, (uint8_t *)buf, txd.len);
> +        s->txbuff.len += txd.len;
> +        /* Check if it's a long frame. (CRC32 is excluded) */
> +        proto = (s->txbuff.buf[12] << 8) | s->txbuff.buf[13];
> +        if (proto == 0x8100) {
> +            ftl = (s->txbuff.len > 1518) ? 1 : 0;
> +        } else {
> +            ftl = (s->txbuff.len > 1514) ? 1 : 0;
> +        }
> +        if (ftl) {
> +            hw_error("ftmac110_transmit: frame too long\n");
> +            exit(1);
> +        }
> +        if (txd.lts) {
> +            if (s->maccr & MACCR_LOOP_EN) {
> +                ftmac110_receive(&s->nic->nc, s->txbuff.buf, s->txbuff.len);
> +            } else {
> +                qemu_send_packet(&s->nic->nc, s->txbuff.buf, s->txbuff.len);
> +            }
> +        }
> +        if (txd.end) {
> +            *idx = 0;
> +        } else {
> +            *idx += 1;
> +        }
> +        if (txd.tx2fic) {
> +            s->isr |= ISR_XPKT_OK;
> +        }
> +        if (txd.txic) {
> +            s->isr |= ISR_XPKT_FINISH;
> +        }
> +        txd.owner = 0;
> +        ftmac110_write_txdesc(off, &txd);
> +    } while (1);
> +}
> +
> +static void ftmac110_bh(void *opaque)
> +{
> +    Ftmac110State *s = FTMAC110(opaque);
> +
> +    if (s->tx_bar) {
> +        ftmac110_transmit(s, &s->tx_bar, &s->tx_idx);
> +    }
> +
> +    ftmac110_update_irq(s);
> +}
> +
> +static uint64_t ftmac110_mem_read(void    *opaque,
> +                                  hwaddr   addr,
> +                                  unsigned size)
> +{
> +    Ftmac110State *s = FTMAC110(opaque);
> +    uint32_t rc = 0;
> +
> +    switch (addr) {
> +    case REG_ISR:
> +        rc = s->isr;
> +        s->isr = 0;
> +        ftmac110_update_irq(s);
> +        break;
> +    case REG_IMR:
> +        return s->ier;
> +    case REG_HMAC:
> +        return s->conf.macaddr.a[1]
> +               | (s->conf.macaddr.a[0] << 8);
> +    case REG_LMAC:
> +        return s->conf.macaddr.a[5]
> +               | (s->conf.macaddr.a[4] << 8)
> +               | (s->conf.macaddr.a[3] << 16)
> +               | (s->conf.macaddr.a[2] << 24);
> +    case REG_MHASH0:
> +        return s->mhash[0];
> +    case REG_MHASH1:
> +        return s->mhash[1];
> +    case REG_TXBAR:
> +        return s->tx_bar;
> +    case REG_RXBAR:
> +        return s->rx_bar;
> +    case REG_MACCR:
> +        return s->maccr;
> +    case REG_MACSR:
> +        rc = s->macsr;
> +        s->macsr = 0;
> +        break;
> +    case REG_PHYCTRL:
> +        do {
> +            uint8_t dev = (s->phycr >> 16) & 0x1f;
> +            uint8_t reg = (s->phycr >> 21) & 0x1f;
> +            if (dev != 0) {
> +                break;
> +            }
> +            if (s->phycr_rd) {
> +                switch (reg) {
> +                case 0:    /* PHY control register */
> +                    return s->phycr | 0x1140;
> +                case 1:    /* PHY status register */
> +                    return s->phycr | 0x796d;
> +                case 2:    /* PHY ID 1 register */
> +                    return s->phycr | 0x0141;
> +                case 3:    /* PHY ID 2 register */
> +                    return s->phycr | 0x0cc2;
> +                case 4:    /* Autonegotiation advertisement register */
> +                    return s->phycr | 0x0de1;
> +                case 5:    /* Autonegotiation partner abilities register */
> +                    return s->phycr | 0x45e1;
> +                }
> +            }
> +        } while (0);
> +        break;
> +    case REG_FCR:
> +        return 0x0000a400;
> +    case REG_BPR:
> +        return 0x00000400;
> +    case REG_TXPKT:
> +        return s->tx_pkt;
> +    case REG_RXPKT:
> +        return s->rx_pkt;
> +    case REG_RXBCST:
> +        return s->rx_bcst;
> +    case REG_RXMCST:
> +        return s->rx_mcst;
> +    case REG_RXRUNT:
> +        return s->rx_runt << 16;
> +    case REG_RXCRCFTL:
> +        return (s->rx_crc << 16) | (s->rx_ftl);
> +    case REG_REV:
> +        return 0x00000700;
> +    case REG_FEA:
> +        return 0x00000007;
> +    default:
> +        break;
> +    }
> +
> +    return rc;
> +}
> +
> +static void ftmac110_chip_reset(Ftmac110State *s)
> +{
> +    s->isr = 0;
> +    s->ier = 0;
> +    s->mhash[0] = 0;
> +    s->mhash[1] = 0;
> +    s->tx_bar = 0;
> +    s->rx_bar = 0;
> +    s->tx_idx = 0;
> +    s->rx_idx = 0;
> +    s->maccr = 0;
> +    s->macsr = 0;
> +    s->phycr = 0;
> +    s->txbuff.len = 0;
> +    s->rx_pkt = 0;
> +    s->rx_bcst = 0;
> +    s->rx_mcst = 0;
> +    s->rx_runt = 0;
> +    s->rx_drop = 0;
> +    s->rx_crc = 0;
> +    s->rx_ftl = 0;
> +    s->tx_pkt = 0;
> +
> +    if (s->bh) {
> +        qemu_bh_cancel(s->bh);
> +    }
> +
> +    ftmac110_update_irq(s);
> +}
> +
> +static void ftmac110_mem_write(void    *opaque,
> +                               hwaddr   addr,
> +                               uint64_t val,
> +                               unsigned size)
> +{
> +    Ftmac110State *s = FTMAC110(opaque);
> +
> +    switch (addr) {
> +    case REG_IMR:
> +        s->ier = (uint32_t)val;
> +        ftmac110_update_irq(s);
> +        break;
> +    case REG_HMAC:
> +        s->conf.macaddr.a[1] = (val >> 0) & 0xff;
> +        s->conf.macaddr.a[0] = (val >> 8) & 0xff;
> +        break;
> +    case REG_LMAC:
> +        s->conf.macaddr.a[5] = (val >> 0) & 0xff;
> +        s->conf.macaddr.a[4] = (val >> 8) & 0xff;
> +        s->conf.macaddr.a[3] = (val >> 16) & 0xff;
> +        s->conf.macaddr.a[2] = (val >> 24) & 0xff;
> +        break;
> +    case REG_MHASH0:
> +        s->mhash[0] = (uint32_t)val;
> +        break;
> +    case REG_MHASH1:
> +        s->mhash[1] = (uint32_t)val;
> +        break;
> +    case REG_TXBAR:
> +        s->tx_bar = (uint32_t)val;
> +        break;
> +    case REG_RXBAR:
> +        s->rx_bar = (uint32_t)val;
> +        break;
> +    case REG_MACCR:
> +        s->maccr = (uint32_t)val;
> +        if (s->maccr & MACCR_SW_RST) {
> +            ftmac110_chip_reset(s);
> +            s->maccr &= ~MACCR_SW_RST;
> +        }
> +        break;
> +    case REG_PHYCTRL:
> +        s->phycr = (uint32_t)val;
> +        if (s->phycr & PHYCR_MDIORD) {
> +            s->phycr_rd = 1;
> +        } else {
> +            s->phycr_rd = 0;
> +        }
> +        s->phycr &= ~(PHYCR_MDIOWR | PHYCR_MDIORD);
> +        break;
> +    case REG_TXPD:
> +        qemu_bh_schedule(s->bh);
> +        break;
> +    default:
> +        break;
> +    }
> +}
> +
> +static const MemoryRegionOps bus_ops = {
> +    .read  = ftmac110_mem_read,
> +    .write = ftmac110_mem_write,
> +    .endianness = DEVICE_LITTLE_ENDIAN,
> +    .valid = {
> +        .min_access_size = 4,
> +        .max_access_size = 4
> +    }
> +};
> +
> +static void ftmac110_cleanup(NetClientState *nc)
> +{
> +    Ftmac110State *s = FTMAC110(DO_UPCAST(NICState, nc, nc)->opaque);
> +
> +    s->nic = NULL;
> +}
> +
> +static NetClientInfo net_ftmac110_info = {
> +    .type = NET_CLIENT_OPTIONS_KIND_NIC,
> +    .size = sizeof(NICState),
> +    .can_receive = ftmac110_can_receive,
> +    .receive = ftmac110_receive,
> +    .cleanup = ftmac110_cleanup,
> +};
> +
> +static void ftmac110_reset(DeviceState *ds)
> +{
> +    SysBusDevice *busdev = SYS_BUS_DEVICE(ds);
> +    Ftmac110State *s = FTMAC110(FROM_SYSBUS(Ftmac110State, busdev));
> +
> +    ftmac110_chip_reset(s);
> +}
> +
> +static int ftmac110_init1(SysBusDevice *dev)
> +{
> +    Ftmac110State *s = FTMAC110(FROM_SYSBUS(Ftmac110State, dev));
> +
> +    memory_region_init_io(&s->mmio, &bus_ops, s, TYPE_FTMAC110, 0x1000);
> +    sysbus_init_mmio(dev, &s->mmio);
> +    sysbus_init_irq(dev, &s->irq);
> +
> +    qemu_macaddr_default_if_unset(&s->conf.macaddr);
> +    s->nic = qemu_new_nic(&net_ftmac110_info, &s->conf,
> +                          object_get_typename(OBJECT(dev)), dev->qdev.id, s);
> +    qemu_format_nic_info_str(&s->nic->nc, s->conf.macaddr.a);
> +
> +    s->bh = qemu_bh_new(ftmac110_bh, s);
> +
> +    ftmac110_chip_reset(s);
> +
> +    return 0;
> +}
> +
> +static const VMStateDescription vmstate_ftmac110 = {
> +    .name = TYPE_FTMAC110,
> +    .version_id = 1,
> +    .minimum_version_id = 1,
> +    .minimum_version_id_old = 1,
> +    .fields = (VMStateField[]) {
> +        VMSTATE_UINT32(ier, Ftmac110State),
> +        VMSTATE_UINT32(tx_bar, Ftmac110State),
> +        VMSTATE_UINT32(rx_bar, Ftmac110State),
> +        VMSTATE_UINT32(tx_idx, Ftmac110State),
> +        VMSTATE_UINT32(rx_idx, Ftmac110State),
> +        VMSTATE_UINT32(maccr, Ftmac110State),
> +        VMSTATE_UINT32(macsr, Ftmac110State),
> +        VMSTATE_UINT32(phycr, Ftmac110State),
> +        VMSTATE_UINT32_ARRAY(mhash, Ftmac110State, 2),
> +        VMSTATE_END_OF_LIST()
> +    }
> +};
> +
> +static Property ftmac110_properties[] = {
> +    DEFINE_NIC_PROPERTIES(Ftmac110State, conf),
> +    DEFINE_PROP_END_OF_LIST(),
> +};
> +
> +static void ftmac110_class_init(ObjectClass *klass, void *data)
> +{
> +    DeviceClass *dc = DEVICE_CLASS(klass);
> +    SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
> +
> +    k->init   = ftmac110_init1;
> +    dc->reset = ftmac110_reset;
> +    dc->vmsd  = &vmstate_ftmac110;
> +    dc->props = ftmac110_properties;
> +}
> +
> +static const TypeInfo ftmac110_info = {
> +    .name           = TYPE_FTMAC110,
> +    .parent         = TYPE_SYS_BUS_DEVICE,
> +    .instance_size  = sizeof(Ftmac110State),
> +    .class_init     = ftmac110_class_init,
> +};
> +
> +static void ftmac110_register_types(void)
> +{
> +    type_register_static(&ftmac110_info);
> +}
> +
> +/* Legacy helper function.  Should go away when machine config files are
> +   implemented.  */
> +void ftmac110_init(NICInfo *nd, uint32_t base, qemu_irq irq)
> +{
> +    DeviceState *dev;
> +    SysBusDevice *s;
> +
> +    qemu_check_nic_model(nd, TYPE_FTMAC110);
> +    dev = qdev_create(NULL, TYPE_FTMAC110);
> +    qdev_set_nic_properties(dev, nd);
> +    qdev_init_nofail(dev);
> +    s = SYS_BUS_DEVICE(dev);
> +    sysbus_mmio_map(s, 0, base);
> +    sysbus_connect_irq(s, 0, irq);
> +}
> +
> +type_init(ftmac110_register_types)
> diff --git a/hw/arm/ftmac110.h b/hw/arm/ftmac110.h
> new file mode 100644
> index 0000000..61f44e7
> --- /dev/null
> +++ b/hw/arm/ftmac110.h
> @@ -0,0 +1,131 @@
> +/*
> + * QEMU model of the FTMAC110 Controller
> + *
> + * Copyright (C) 2012 Faraday Technology
> + * Written by Dante Su <dantesu@faraday-tech.com>
> + *
> + * This file is licensed under GNU GPL v2+.
> + */
> +
> +#ifndef HW_ARM_FTMAC110_H
> +#define HW_ARM_FTMAC110_H
> +
> +#define REG_ISR             0x00
> +#define REG_IMR             0x04
> +#define REG_HMAC            0x08
> +#define REG_LMAC            0x0c
> +#define REG_MHASH0          0x10
> +#define REG_MHASH1          0x14
> +#define REG_TXPD            0x18
> +#define REG_RXPD            0x1c
> +#define REG_TXBAR           0x20
> +#define REG_RXBAR           0x24
> +#define REG_ITC             0x28
> +#define REG_APTC            0x2C
> +#define REG_DBLAC           0x30
> +#define REG_REV             0x34
> +#define REG_FEA             0x38
> +
> +#define REG_MACCR           0x88
> +#define REG_MACSR           0x8C
> +#define REG_PHYCTRL         0x90
> +#define REG_PHYDATA         0x94
> +#define REG_FCR             0x98
> +#define REG_BPR             0x9c
> +
> +#define REG_TXPKT           0xf8
> +#define REG_RXPKT           0xf4
> +#define REG_RXBCST          0xec
> +#define REG_RXMCST          0xf0
> +#define REG_RXRUNT          0xe0
> +#define REG_RXCRCFTL        0xe4
> +
> +/* interrupt status register */
> +#define ISR_PHYSTS_CHG      (1UL<<9)
> +#define ISR_AHB_ERR         (1UL<<8)
> +#define ISR_RPKT_LOST       (1UL<<7)
> +#define ISR_RPKT_OK         (1UL<<6)
> +#define ISR_XPKT_LOST       (1UL<<5)
> +#define ISR_XPKT_OK         (1UL<<4)
> +#define ISR_NOTXBUF         (1UL<<3)
> +#define ISR_XPKT_FINISH     (1UL<<2)
> +#define ISR_NORXBUF         (1UL<<1)
> +#define ISR_RPKT_FINISH     (1UL<<0)
> +
> +/* MAC control register */
> +#define MACCR_100M              (1UL<<18)
> +#define MACCR_RX_BROADPKT       (1UL<<17)
> +#define MACCR_RX_MULTIPKT       (1UL<<16)
> +#define MACCR_FULLDUP           (1UL<<15)
> +#define MACCR_CRC_APD           (1UL<<14)
> +#define MACCR_RCV_ALL           (1UL<<12)
> +#define MACCR_RX_FTL            (1UL<<11)
> +#define MACCR_RX_RUNT           (1UL<<10)
> +#define MACCR_HT_MULTI_EN       (1UL<<9)
> +#define MACCR_RCV_EN            (1UL<<8)
> +#define MACCR_ENRX_IN_HALFTX    (1UL<<6)
> +#define MACCR_XMT_EN            (1UL<<5)
> +#define MACCR_CRC_DIS           (1UL<<4)
> +#define MACCR_LOOP_EN           (1UL<<3)
> +#define MACCR_SW_RST            (1UL<<2)
> +#define MACCR_RDMA_EN           (1UL<<1)
> +#define MACCR_XDMA_EN           (1UL<<0)
> +
> +/*
> + * MDIO
> + */
> +#define PHYCR_MDIOWR            (1 << 27)
> +#define PHYCR_MDIORD            (1 << 26)
> +
> +/*
> + * Tx/Rx descriptors
> + */
> +typedef struct Ftmac110RXD {
> +    /* RXDES0 */
> +    uint32_t len:11;
> +    uint32_t rsvd1:5;
> +    uint32_t mcast:1;
> +    uint32_t bcast:1;
> +    uint32_t error:5;
> +    uint32_t rsvd2:5;
> +    uint32_t lrs:1;
> +    uint32_t frs:1;
> +    uint32_t rsvd3:1;
> +    uint32_t owner:1;   /* BIT: 31 - 1:HW, 0: SW */
> +
> +    /* RXDES1 */
> +    uint32_t bufsz:11;
> +    uint32_t rsvd4:20;
> +    uint32_t end:1;     /* BIT: 31 */
> +
> +    /* RXDES2 */
> +    uint32_t buf;
> +
> +    /* RXDES3 */
> +    void     *skb;
> +} __attribute__ ((aligned (16))) Ftmac110RXD;

The alignment attributes are not useful since we are not accessing
memory directly.

> +
> +typedef struct Ftmac110TXD {
> +    /* TXDES0 */
> +    uint32_t error:2;
> +    uint32_t rsvd1:29;
> +    uint32_t owner:1;   /* BIT: 31 - 1:HW, 0: SW */
> +
> +    /* TXDES1 */
> +    uint32_t len:11;
> +    uint32_t rsvd2:16;
> +    uint32_t lts:1;
> +    uint32_t fts:1;
> +    uint32_t tx2fic:1;
> +    uint32_t txic:1;
> +    uint32_t end:1;     /* BIT: 31 */
> +
> +    /* TXDES2 */
> +    uint32_t buf;
> +
> +    /* TXDES3 */
> +    void     *skb;
> +
> +} __attribute__ ((aligned (16))) Ftmac110TXD;
> +
> +#endif  /* FTMAC_H */

HW_ARM_FTMAC110_H

> --
> 1.7.9.5
>
Peter Crosthwaite Feb. 17, 2013, 6:29 a.m. UTC | #2
On Wed, Feb 6, 2013 at 7:45 PM, Kuo-Jung Su <dantesu@gmail.com> wrote:
> From: Kuo-Jung Su <dantesu@faraday-tech.com>
>
> The FTMAC110 is an Ethernet controller that provides AHB master capability
> and is in full compliance with the IEEE 802.3 10/100 Mbps specifications.
> Its DMA controller handles all data transfers between system memory
> and on-chip memories.
> It supports half-word data transfer for Linux. However it has a weird DMA
> alignment issue:
>
> (1) Tx DMA Buffer Address:
>     1 bytes aligned: Invalid
>     2 bytes aligned: O.K
>     4 bytes aligned: O.K
>
> (2) Rx DMA Buffer Address:
>     1 bytes aligned: Invalid
>     2 bytes aligned: O.K
>     4 bytes aligned: Invalid (It means 0x0, 0x4, 0x8, 0xC are invalid)
>
> Signed-off-by: Kuo-Jung Su <dantesu@faraday-tech.com>
> ---
>  hw/arm/Makefile.objs  |    1 +
>  hw/arm/faraday_a360.c |   10 +
>  hw/arm/ftmac110.c     |  681 +++++++++++++++++++++++++++++++++++++++++++++++++
>  hw/arm/ftmac110.h     |  131 ++++++++++
>  4 files changed, 823 insertions(+)
>  create mode 100644 hw/arm/ftmac110.c
>  create mode 100644 hw/arm/ftmac110.h
>
> diff --git a/hw/arm/Makefile.objs b/hw/arm/Makefile.objs
> index 70d4f25..f5eeaeb 100644
> --- a/hw/arm/Makefile.objs
> +++ b/hw/arm/Makefile.objs
> @@ -47,3 +47,4 @@ obj-y += ftapbbrg020.o
>  obj-y += ftnandc021.o
>  obj-y += fti2c010.o
>  obj-y += ftssp010.o
> +obj-y += ftmac110.o
> diff --git a/hw/arm/faraday_a360.c b/hw/arm/faraday_a360.c
> index 52cfcec..51e8649 100644
> --- a/hw/arm/faraday_a360.c
> +++ b/hw/arm/faraday_a360.c
> @@ -31,6 +31,7 @@ a360_device_init(A360State *s)
>      qemu_irq *pic;
>      DeviceState *ds, *fl;
>      SSIBus *spi;
> +    int done_nic = 0;
>      int i, nr_flash;
>      qemu_irq cs_line;
>      qemu_irq ack, req;
> @@ -122,6 +123,15 @@ a360_device_init(A360State *s)
>      req = qdev_get_gpio_in(s->pdma[0], 2);
>      qdev_connect_gpio_out(s->pdma[0], 2, ack);
>      qdev_connect_gpio_out(ds, 1, req);
> +
> +    /* ftmac110 */
> +    for (i = 0; i < nb_nics; i++) {
> +        NICInfo *nd = &nd_table[i];
> +        if (!done_nic && (!nd->model || strcmp(nd->model, "ftmac110") == 0)) {
> +            ftmac110_init(nd, 0x90900000, pic[25]);
> +            done_nic = 1;
> +        }
> +    }
>  }
>
>  static void
> diff --git a/hw/arm/ftmac110.c b/hw/arm/ftmac110.c
> new file mode 100644
> index 0000000..d45f4ba
> --- /dev/null
> +++ b/hw/arm/ftmac110.c
> @@ -0,0 +1,681 @@
> +/*
> + * QEMU model of the FTMAC110 Controller
> + *
> + * Copyright (C) 2012 Faraday Technology
> + * Written by Dante Su <dantesu@faraday-tech.com>
> + *
> + * This file is licensed under GNU GPL v2+.
> + */
> +
> +/*******************************************************************/
> +/*               FTMAC110 DMA design issue                         */
> +/*                                             Dante Su 2010.02.03 */
> +/*                                                                 */
> +/* The DMA engine has a weird restriction that its Rx DMA engine   */
> +/* accepts only 16-bits aligned address, 32-bits aligned is still  */
> +/* invalid. However this restriction does not apply to Tx DMA.     */
> +/* Conclusion:                                                     */
> +/* (1) Tx DMA Buffer Address:                                      */
> +/*     1 bytes aligned: Invalid                                    */
> +/*     2 bytes aligned: O.K                                        */
> +/*     4 bytes aligned: O.K (-> u-boot ZeroCopy is possible)       */
> +/* (2) Rx DMA Buffer Address:                                      */
> +/*     1 bytes aligned: Invalid                                    */
> +/*     2 bytes aligned: O.K                                        */
> +/*     4 bytes aligned: Invalid                                    */
> +/*******************************************************************/
> +

> +#include <hw/sysbus.h>
> +#include <sysemu/sysemu.h>
> +#include <net/net.h>
> +
> +#include "faraday.h"
> +#include "ftmac110.h"
> +
> +#define TYPE_FTMAC110    "ftmac110"
> +
> +typedef struct Ftmac110State {
> +    SysBusDevice busdev;
> +    MemoryRegion mmio;
> +
> +    QEMUBH *bh;
> +    qemu_irq irq;
> +    NICState *nic;
> +    NICConf conf;
> +
> +    uint32_t isr;
> +    uint32_t ier;
> +    uint32_t mhash[2];
> +    uint32_t tx_bar;
> +    uint32_t rx_bar;
> +    uint32_t tx_idx;
> +    uint32_t rx_idx;
> +    uint32_t maccr;
> +    uint32_t macsr;
> +    uint32_t phycr;
> +    uint32_t phycr_rd;
> +
> +    struct {
> +        uint8_t  buf[2048];

Magic number

> +        uint32_t len;
> +    } txbuff;
> +
> +    uint32_t rx_pkt;
> +    uint32_t rx_bcst;
> +    uint32_t rx_mcst;
> +    uint16_t rx_runt;
> +    uint16_t rx_drop;
> +    uint16_t rx_crc;
> +    uint16_t rx_ftl;
> +    uint32_t tx_pkt;
> +
> +} Ftmac110State;
> +
> +#define FTMAC110(obj) \
> +    OBJECT_CHECK(Ftmac110State, obj, TYPE_FTMAC110)
> +
> +static uint8_t bitrev8(uint8_t v)
> +{
> +    int i;
> +    uint8_t r = 0;
> +    for (i = 0; i < 8; ++i) {
> +        if (v & (1 << i)) {
> +            r |= (1 << (7 - i));
> +        }
> +    }
> +    return r;
> +}

I would suggest adding this to bitops.[ch] for all the use. This would
be a separate patch however.

> +
> +static int ftmac110_mcast_hash(int len, const uint8_t *p)
> +{
> +#define CRCPOLY_LE 0xedb88320
> +    int i;
> +    uint32_t crc = 0xFFFFFFFF;
> +
> +    while (len--) {
> +        crc ^= *p++;
> +        for (i = 0; i < 8; i++) {
> +            crc = (crc >> 1) ^ ((crc & 1) ? CRCPOLY_LE : 0);
> +        }
> +    }
> +
> +    /* Reverse CRC32 and return MSB 6 bits only */
> +    return bitrev8(crc >> 24) >> 2;
> +}
> +
> +static void ftmac110_read_rxdesc(hwaddr addr, Ftmac110RXD *desc)
> +{
> +    int i;
> +    uint32_t *p = (uint32_t *)desc;
> +
> +    if (addr & 0x0f) {
> +        hw_error("ftmac110: Rx desc is not 16-byte aligned!\n"
> +                 "It's fine in QEMU but the real HW would panic.\n");
> +    }
> +
> +    cpu_physical_memory_read(addr, desc, sizeof(*desc));

I think its better to use the DMA API.

dma_memory_read(&dma_context_memory, addr, desc, sizeof(*desc))

cpu_physical_memory_read shouldnt really be used by devices.

> +
> +    for (i = 0; i < sizeof(*desc); i += 4) {
> +        *p = le32_to_cpu(*p);
> +    }
> +
> +    if ((desc->buf & 0x1) || !(desc->buf % 4)) {
> +        hw_error("ftmac110: rx buffer is not exactly 16-bit aligned.\n");

Looks like a programmer error. You could use qemu_log_mask(LOG_GUEST_ERROR

> +    }
> +}
> +
> +static void ftmac110_write_rxdesc(hwaddr addr, Ftmac110RXD *desc)
> +{
> +    int i;
> +    uint32_t *p = (uint32_t *)desc;
> +
> +    for (i = 0; i < sizeof(*desc); i += 4) {
> +        *p = cpu_to_le32(*p);
> +    }
> +
> +    cpu_physical_memory_write(addr, desc, sizeof(*desc));

Same thing with DMA API

> +}
> +
> +static void ftmac110_read_txdesc(hwaddr addr, Ftmac110TXD *desc)
> +{
> +    int i;
> +    uint32_t *p = (uint32_t *)desc;
> +
> +    if (addr & 0x0f) {
> +        hw_error("ftmac110: Tx desc is not 16-byte aligned!\n"
> +                 "It's fine in QEMU but the real HW would panic.\n");
> +    }
> +
> +    cpu_physical_memory_read(addr, desc, sizeof(*desc));
> +
> +    for (i = 0; i < sizeof(*desc); i += 4) {
> +        *p = le32_to_cpu(*p);
> +    }
> +
> +    if (desc->buf & 0x1) {
> +        hw_error("ftmac110: tx buffer is not 16-bit aligned.\n");
> +    }
> +}
> +
> +static void ftmac110_write_txdesc(hwaddr addr, Ftmac110TXD *desc)
> +{
> +    int i;
> +    uint32_t *p = (uint32_t *)desc;
> +
> +    for (i = 0; i < sizeof(*desc); i += 4) {
> +        *p = cpu_to_le32(*p);
> +    }
> +
> +    cpu_physical_memory_write(addr, desc, sizeof(*desc));
> +}
> +
> +static void ftmac110_update_irq(Ftmac110State *s)
> +{
> +    if (s->isr & s->ier) {
> +        qemu_set_irq(s->irq, 1);
> +    } else {
> +        qemu_set_irq(s->irq, 0);
> +    }

You could save a few lines with:

qemu_set_irq(s->irq, !!(s->isr & s->ier))

> +}
> +
> +static int ftmac110_can_receive(NetClientState *nc)
> +{
> +    Ftmac110State *s = FTMAC110(DO_UPCAST(NICState, nc, nc)->opaque);
> +    Ftmac110RXD rxd;
> +    hwaddr off = s->rx_bar + s->rx_idx * sizeof(rxd);
> +
> +    if ((s->maccr & (MACCR_RCV_EN | MACCR_RDMA_EN))
> +            != (MACCR_RCV_EN | MACCR_RDMA_EN)) {
> +        return 0;
> +    }
> +
> +    ftmac110_read_rxdesc(off, &rxd);
> +
> +    return rxd.owner;

cc: Anthony

You are going to have the same problem here that I have with cadence
GEM where if you block can_recieve based on a SGDMA descriptors state,
there is no easy way to watch the descriptor for a change (which
should then call qemu_flush_queued_packets()). This leads to very poor
performance. I would suggest just returning 1 here and dropping the
packets with assertion of NO_RXBUF. This also makes behaviour between
long and short packets, as currently short packet that fit in one
descriptor return false on can_recieve() which long packets that
require multiple descs are potentially dropped due to NO_RXBUF.

The alternative is to setup a poll of the rxdesc memory region to
flush queued packets on change of rxdesc state.

> +}
> +
> +static ssize_t ftmac110_receive(NetClientState *nc,
> +                                const uint8_t  *buf,
> +                                size_t          size)
> +{
> +    const uint8_t *ptr = buf;
> +    hwaddr off;
> +    size_t len;
> +    Ftmac110RXD rxd;
> +    Ftmac110State *s = FTMAC110(DO_UPCAST(NICState, nc, nc)->opaque);
> +    int bcst, mcst, ftl, proto;
> +
> +    if ((s->maccr & (MACCR_RCV_EN | MACCR_RDMA_EN))
> +            != (MACCR_RCV_EN | MACCR_RDMA_EN)) {
> +        return -1;
> +    }

Common subexpression with can_receive.

> +
> +    /*
> +     * Check if it's a long frame. (CRC32 is excluded)
> +     */
> +    proto = (buf[12] << 8) | buf[13];
> +    if (proto == 0x8100) {  /* 802.1Q VLAN */
> +        ftl = (size > 1518) ? 1 : 0;
> +    } else {
> +        ftl = (size > 1514) ? 1 : 0;
> +    }
> +    if (ftl) {
> +        DPRINTF("ftmac110_receive: frame too long, drop it\n");
> +        return -1;
> +    }
> +
> +    /* if it's a broadcast */
> +    if ((buf[0] == 0xff) && (buf[1] == 0xff) && (buf[2] == 0xff)
> +            && (buf[3] == 0xff) && (buf[4] == 0xff) && (buf[5] == 0xff)) {
> +        bcst = 1;
> +        if (!(s->maccr & MACCR_RCV_ALL) && !(s->maccr & MACCR_RX_BROADPKT)) {
> +            DPRINTF("ftmac110_receive: bcst filtered\n");
> +            return -1;
> +        }
> +    } else {
> +        bcst = 0;
> +    }
> +
> +    /* if it's a multicast */
> +    if ((buf[0] == 0x01) && (buf[1] == 0x00) && (buf[2] == 0x5e)
> +        && (buf[3] <= 0x7f)) {
> +        mcst = 1;
> +        if (!(s->maccr & MACCR_RCV_ALL) && !(s->maccr & MACCR_RX_MULTIPKT)) {
> +            int hash;
> +            if (!(s->maccr & MACCR_HT_MULTI_EN)) {
> +                DPRINTF("ftmac110_receive: mcst filtered\n");
> +                return -1;
> +            }
> +            hash = ftmac110_mcast_hash(6, buf);
> +            if (!(s->mhash[hash / 32] & (1 << (hash % 32)))) {
> +                DPRINTF("ftmac110_receive: mcst filtered\n");
> +                return -1;
> +            }
> +        }
> +    } else {
> +        mcst = 0;
> +    }
> +
> +    /* check if the destination matches NIC mac address */
> +    if (!(s->maccr & MACCR_RCV_ALL) && !bcst && !mcst) {
> +        if (memcmp(s->conf.macaddr.a, buf, 6)) {
> +            return -1;
> +        }
> +    }
> +
> +    while (size > 0) {
> +        off = s->rx_bar + s->rx_idx * sizeof(rxd);
> +        ftmac110_read_rxdesc(off, &rxd);
> +        if (!rxd.owner) {
> +            s->isr |= ISR_NORXBUF;
> +            DPRINTF("ftmac110: out of rxd!?\n");
> +            return -1;
> +        }
> +
> +        if (ptr == buf) {
> +            rxd.frs = 1;
> +        } else {
> +            rxd.frs = 0;
> +        }
> +
> +        len = size > rxd.bufsz ? rxd.bufsz : size;
> +        cpu_physical_memory_write(rxd.buf, (uint8_t *)ptr, len);
> +        ptr  += len;
> +        size -= len;
> +
> +        if (size <= 0) {
> +            rxd.lrs = 1;
> +        } else {
> +            rxd.lrs = 0;
> +        }
> +
> +        rxd.len = len;
> +        rxd.bcast = bcst;
> +        rxd.mcast = mcst;
> +        rxd.owner = 0;
> +
> +        /* write-back the rx descriptor */
> +        ftmac110_write_rxdesc(off, &rxd);
> +
> +        if (rxd.end) {
> +            s->rx_idx = 0;
> +        } else {
> +            s->rx_idx += 1;
> +        }
> +    }
> +
> +    /* update interrupt signal */
> +    s->isr |= ISR_RPKT_OK | ISR_RPKT_FINISH;
> +    ftmac110_update_irq(s);
> +
> +    return (ssize_t)((uint32_t)ptr - (uint32_t)buf);
> +}
> +
> +static void ftmac110_transmit(Ftmac110State *s, uint32_t *bar, uint32_t *idx)
> +{
> +    hwaddr off;
> +    uint8_t *buf;
> +    int ftl, proto;
> +    Ftmac110TXD txd;
> +
> +    if ((s->maccr & (MACCR_XMT_EN | MACCR_XDMA_EN))
> +            != (MACCR_XMT_EN | MACCR_XDMA_EN)) {
> +        return;
> +    }
> +
> +    do {
> +        off = (*bar) + (*idx) * sizeof(txd);
> +        ftmac110_read_txdesc(off, &txd);
> +        if (!txd.owner) {
> +            s->isr |= ISR_NOTXBUF;
> +            break;
> +        }
> +        if (txd.fts) {
> +            s->txbuff.len = 0;
> +        }
> +        if (txd.len + s->txbuff.len > sizeof(s->txbuff.buf)) {
> +            hw_error("ftmac110: tx buffer overflow!\n");
> +            exit(1);
> +        }
> +        buf = s->txbuff.buf + s->txbuff.len;
> +        cpu_physical_memory_read(txd.buf, (uint8_t *)buf, txd.len);
> +        s->txbuff.len += txd.len;
> +        /* Check if it's a long frame. (CRC32 is excluded) */
> +        proto = (s->txbuff.buf[12] << 8) | s->txbuff.buf[13];
> +        if (proto == 0x8100) {
> +            ftl = (s->txbuff.len > 1518) ? 1 : 0;
> +        } else {
> +            ftl = (s->txbuff.len > 1514) ? 1 : 0;
> +        }
> +        if (ftl) {
> +            hw_error("ftmac110_transmit: frame too long\n");
> +            exit(1);
> +        }
> +        if (txd.lts) {
> +            if (s->maccr & MACCR_LOOP_EN) {
> +                ftmac110_receive(&s->nic->nc, s->txbuff.buf, s->txbuff.len);
> +            } else {
> +                qemu_send_packet(&s->nic->nc, s->txbuff.buf, s->txbuff.len);
> +            }
> +        }
> +        if (txd.end) {
> +            *idx = 0;
> +        } else {
> +            *idx += 1;
> +        }
> +        if (txd.tx2fic) {
> +            s->isr |= ISR_XPKT_OK;
> +        }
> +        if (txd.txic) {
> +            s->isr |= ISR_XPKT_FINISH;
> +        }
> +        txd.owner = 0;
> +        ftmac110_write_txdesc(off, &txd);
> +    } while (1);
> +}
> +
> +static void ftmac110_bh(void *opaque)
> +{
> +    Ftmac110State *s = FTMAC110(opaque);
> +
> +    if (s->tx_bar) {
> +        ftmac110_transmit(s, &s->tx_bar, &s->tx_idx);
> +    }
> +
> +    ftmac110_update_irq(s);
> +}
> +
> +static uint64_t ftmac110_mem_read(void    *opaque,
> +                                  hwaddr   addr,
> +                                  unsigned size)
> +{
> +    Ftmac110State *s = FTMAC110(opaque);
> +    uint32_t rc = 0;
> +
> +    switch (addr) {
> +    case REG_ISR:
> +        rc = s->isr;
> +        s->isr = 0;
> +        ftmac110_update_irq(s);
> +        break;
> +    case REG_IMR:
> +        return s->ier;
> +    case REG_HMAC:
> +        return s->conf.macaddr.a[1]
> +               | (s->conf.macaddr.a[0] << 8);
> +    case REG_LMAC:
> +        return s->conf.macaddr.a[5]
> +               | (s->conf.macaddr.a[4] << 8)
> +               | (s->conf.macaddr.a[3] << 16)
> +               | (s->conf.macaddr.a[2] << 24);
> +    case REG_MHASH0:
> +        return s->mhash[0];
> +    case REG_MHASH1:
> +        return s->mhash[1];
> +    case REG_TXBAR:
> +        return s->tx_bar;
> +    case REG_RXBAR:
> +        return s->rx_bar;
> +    case REG_MACCR:
> +        return s->maccr;
> +    case REG_MACSR:
> +        rc = s->macsr;
> +        s->macsr = 0;
> +        break;
> +    case REG_PHYCTRL:
> +        do {
> +            uint8_t dev = (s->phycr >> 16) & 0x1f;
> +            uint8_t reg = (s->phycr >> 21) & 0x1f;
> +            if (dev != 0) {
> +                break;
> +            }
> +            if (s->phycr_rd) {
> +                switch (reg) {
> +                case 0:    /* PHY control register */
> +                    return s->phycr | 0x1140;
> +                case 1:    /* PHY status register */
> +                    return s->phycr | 0x796d;
> +                case 2:    /* PHY ID 1 register */
> +                    return s->phycr | 0x0141;
> +                case 3:    /* PHY ID 2 register */
> +                    return s->phycr | 0x0cc2;
> +                case 4:    /* Autonegotiation advertisement register */
> +                    return s->phycr | 0x0de1;
> +                case 5:    /* Autonegotiation partner abilities register */
> +                    return s->phycr | 0x45e1;
> +                }
> +            }
> +        } while (0);
> +        break;
> +    case REG_FCR:
> +        return 0x0000a400;
> +    case REG_BPR:
> +        return 0x00000400;
> +    case REG_TXPKT:
> +        return s->tx_pkt;
> +    case REG_RXPKT:
> +        return s->rx_pkt;
> +    case REG_RXBCST:
> +        return s->rx_bcst;
> +    case REG_RXMCST:
> +        return s->rx_mcst;
> +    case REG_RXRUNT:
> +        return s->rx_runt << 16;
> +    case REG_RXCRCFTL:
> +        return (s->rx_crc << 16) | (s->rx_ftl);
> +    case REG_REV:
> +        return 0x00000700;
> +    case REG_FEA:
> +        return 0x00000007;
> +    default:
> +        break;
> +    }
> +
> +    return rc;
> +}
> +
> +static void ftmac110_chip_reset(Ftmac110State *s)
> +{
> +    s->isr = 0;
> +    s->ier = 0;
> +    s->mhash[0] = 0;
> +    s->mhash[1] = 0;
> +    s->tx_bar = 0;
> +    s->rx_bar = 0;
> +    s->tx_idx = 0;
> +    s->rx_idx = 0;
> +    s->maccr = 0;
> +    s->macsr = 0;
> +    s->phycr = 0;
> +    s->txbuff.len = 0;
> +    s->rx_pkt = 0;
> +    s->rx_bcst = 0;
> +    s->rx_mcst = 0;
> +    s->rx_runt = 0;
> +    s->rx_drop = 0;
> +    s->rx_crc = 0;
> +    s->rx_ftl = 0;
> +    s->tx_pkt = 0;
> +

Theres a lot of code for managing your registers that would be a lot
shorter if implemented your reigster set as an array.

> +    if (s->bh) {
> +        qemu_bh_cancel(s->bh);
> +    }
> +
> +    ftmac110_update_irq(s);
> +}
> +
> +static void ftmac110_mem_write(void    *opaque,
> +                               hwaddr   addr,
> +                               uint64_t val,
> +                               unsigned size)
> +{
> +    Ftmac110State *s = FTMAC110(opaque);
> +
> +    switch (addr) {
> +    case REG_IMR:
> +        s->ier = (uint32_t)val;
> +        ftmac110_update_irq(s);
> +        break;
> +    case REG_HMAC:
> +        s->conf.macaddr.a[1] = (val >> 0) & 0xff;
> +        s->conf.macaddr.a[0] = (val >> 8) & 0xff;
> +        break;
> +    case REG_LMAC:
> +        s->conf.macaddr.a[5] = (val >> 0) & 0xff;
> +        s->conf.macaddr.a[4] = (val >> 8) & 0xff;
> +        s->conf.macaddr.a[3] = (val >> 16) & 0xff;
> +        s->conf.macaddr.a[2] = (val >> 24) & 0xff;
> +        break;
> +    case REG_MHASH0:
> +        s->mhash[0] = (uint32_t)val;
> +        break;
> +    case REG_MHASH1:
> +        s->mhash[1] = (uint32_t)val;
> +        break;
> +    case REG_TXBAR:
> +        s->tx_bar = (uint32_t)val;
> +        break;
> +    case REG_RXBAR:
> +        s->rx_bar = (uint32_t)val;
> +        break;
> +    case REG_MACCR:
> +        s->maccr = (uint32_t)val;
> +        if (s->maccr & MACCR_SW_RST) {
> +            ftmac110_chip_reset(s);
> +            s->maccr &= ~MACCR_SW_RST;
> +        }

Changes to s->maccr can affect the can_receive() state. You should
check for queued packets here using qemu_flush_queued_packets().

Regards,
Peter

> +        break;
> +    case REG_PHYCTRL:
> +        s->phycr = (uint32_t)val;
> +        if (s->phycr & PHYCR_MDIORD) {
> +            s->phycr_rd = 1;
> +        } else {
> +            s->phycr_rd = 0;
> +        }
> +        s->phycr &= ~(PHYCR_MDIOWR | PHYCR_MDIORD);
> +        break;
> +    case REG_TXPD:
> +        qemu_bh_schedule(s->bh);
> +        break;
> +    default:
> +        break;
> +    }
> +}
> +
> +static const MemoryRegionOps bus_ops = {
> +    .read  = ftmac110_mem_read,
> +    .write = ftmac110_mem_write,
> +    .endianness = DEVICE_LITTLE_ENDIAN,
> +    .valid = {
> +        .min_access_size = 4,
> +        .max_access_size = 4
> +    }
> +};
> +
> +static void ftmac110_cleanup(NetClientState *nc)
> +{
> +    Ftmac110State *s = FTMAC110(DO_UPCAST(NICState, nc, nc)->opaque);
> +
> +    s->nic = NULL;
> +}
> +
> +static NetClientInfo net_ftmac110_info = {
> +    .type = NET_CLIENT_OPTIONS_KIND_NIC,
> +    .size = sizeof(NICState),
> +    .can_receive = ftmac110_can_receive,
> +    .receive = ftmac110_receive,
> +    .cleanup = ftmac110_cleanup,
> +};
> +
> +static void ftmac110_reset(DeviceState *ds)
> +{
> +    SysBusDevice *busdev = SYS_BUS_DEVICE(ds);
> +    Ftmac110State *s = FTMAC110(FROM_SYSBUS(Ftmac110State, busdev));
> +
> +    ftmac110_chip_reset(s);
> +}
> +
> +static int ftmac110_init1(SysBusDevice *dev)
> +{
> +    Ftmac110State *s = FTMAC110(FROM_SYSBUS(Ftmac110State, dev));
> +
> +    memory_region_init_io(&s->mmio, &bus_ops, s, TYPE_FTMAC110, 0x1000);
> +    sysbus_init_mmio(dev, &s->mmio);
> +    sysbus_init_irq(dev, &s->irq);
> +
> +    qemu_macaddr_default_if_unset(&s->conf.macaddr);
> +    s->nic = qemu_new_nic(&net_ftmac110_info, &s->conf,
> +                          object_get_typename(OBJECT(dev)), dev->qdev.id, s);
> +    qemu_format_nic_info_str(&s->nic->nc, s->conf.macaddr.a);
> +
> +    s->bh = qemu_bh_new(ftmac110_bh, s);
> +
> +    ftmac110_chip_reset(s);
> +
> +    return 0;
> +}
> +
> +static const VMStateDescription vmstate_ftmac110 = {
> +    .name = TYPE_FTMAC110,
> +    .version_id = 1,
> +    .minimum_version_id = 1,
> +    .minimum_version_id_old = 1,
> +    .fields = (VMStateField[]) {
> +        VMSTATE_UINT32(ier, Ftmac110State),
> +        VMSTATE_UINT32(tx_bar, Ftmac110State),
> +        VMSTATE_UINT32(rx_bar, Ftmac110State),
> +        VMSTATE_UINT32(tx_idx, Ftmac110State),
> +        VMSTATE_UINT32(rx_idx, Ftmac110State),
> +        VMSTATE_UINT32(maccr, Ftmac110State),
> +        VMSTATE_UINT32(macsr, Ftmac110State),
> +        VMSTATE_UINT32(phycr, Ftmac110State),
> +        VMSTATE_UINT32_ARRAY(mhash, Ftmac110State, 2),
> +        VMSTATE_END_OF_LIST()
> +    }
> +};
> +
> +static Property ftmac110_properties[] = {
> +    DEFINE_NIC_PROPERTIES(Ftmac110State, conf),
> +    DEFINE_PROP_END_OF_LIST(),
> +};
> +
> +static void ftmac110_class_init(ObjectClass *klass, void *data)
> +{
> +    DeviceClass *dc = DEVICE_CLASS(klass);
> +    SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
> +
> +    k->init   = ftmac110_init1;
> +    dc->reset = ftmac110_reset;
> +    dc->vmsd  = &vmstate_ftmac110;
> +    dc->props = ftmac110_properties;
> +}
> +
> +static const TypeInfo ftmac110_info = {
> +    .name           = TYPE_FTMAC110,
> +    .parent         = TYPE_SYS_BUS_DEVICE,
> +    .instance_size  = sizeof(Ftmac110State),
> +    .class_init     = ftmac110_class_init,
> +};
> +
> +static void ftmac110_register_types(void)
> +{
> +    type_register_static(&ftmac110_info);
> +}
> +
> +/* Legacy helper function.  Should go away when machine config files are
> +   implemented.  */
> +void ftmac110_init(NICInfo *nd, uint32_t base, qemu_irq irq)
> +{
> +    DeviceState *dev;
> +    SysBusDevice *s;
> +
> +    qemu_check_nic_model(nd, TYPE_FTMAC110);
> +    dev = qdev_create(NULL, TYPE_FTMAC110);
> +    qdev_set_nic_properties(dev, nd);
> +    qdev_init_nofail(dev);
> +    s = SYS_BUS_DEVICE(dev);
> +    sysbus_mmio_map(s, 0, base);
> +    sysbus_connect_irq(s, 0, irq);
> +}
> +
> +type_init(ftmac110_register_types)
> diff --git a/hw/arm/ftmac110.h b/hw/arm/ftmac110.h
> new file mode 100644
> index 0000000..61f44e7
> --- /dev/null
> +++ b/hw/arm/ftmac110.h
> @@ -0,0 +1,131 @@
> +/*
> + * QEMU model of the FTMAC110 Controller
> + *
> + * Copyright (C) 2012 Faraday Technology
> + * Written by Dante Su <dantesu@faraday-tech.com>
> + *
> + * This file is licensed under GNU GPL v2+.
> + */
> +
> +#ifndef HW_ARM_FTMAC110_H
> +#define HW_ARM_FTMAC110_H
> +
> +#define REG_ISR             0x00
> +#define REG_IMR             0x04
> +#define REG_HMAC            0x08
> +#define REG_LMAC            0x0c
> +#define REG_MHASH0          0x10
> +#define REG_MHASH1          0x14
> +#define REG_TXPD            0x18
> +#define REG_RXPD            0x1c
> +#define REG_TXBAR           0x20
> +#define REG_RXBAR           0x24
> +#define REG_ITC             0x28
> +#define REG_APTC            0x2C
> +#define REG_DBLAC           0x30
> +#define REG_REV             0x34
> +#define REG_FEA             0x38
> +
> +#define REG_MACCR           0x88
> +#define REG_MACSR           0x8C
> +#define REG_PHYCTRL         0x90
> +#define REG_PHYDATA         0x94
> +#define REG_FCR             0x98
> +#define REG_BPR             0x9c
> +
> +#define REG_TXPKT           0xf8
> +#define REG_RXPKT           0xf4
> +#define REG_RXBCST          0xec
> +#define REG_RXMCST          0xf0
> +#define REG_RXRUNT          0xe0
> +#define REG_RXCRCFTL        0xe4
> +
> +/* interrupt status register */
> +#define ISR_PHYSTS_CHG      (1UL<<9)
> +#define ISR_AHB_ERR         (1UL<<8)
> +#define ISR_RPKT_LOST       (1UL<<7)
> +#define ISR_RPKT_OK         (1UL<<6)
> +#define ISR_XPKT_LOST       (1UL<<5)
> +#define ISR_XPKT_OK         (1UL<<4)
> +#define ISR_NOTXBUF         (1UL<<3)
> +#define ISR_XPKT_FINISH     (1UL<<2)
> +#define ISR_NORXBUF         (1UL<<1)
> +#define ISR_RPKT_FINISH     (1UL<<0)
> +
> +/* MAC control register */
> +#define MACCR_100M              (1UL<<18)
> +#define MACCR_RX_BROADPKT       (1UL<<17)
> +#define MACCR_RX_MULTIPKT       (1UL<<16)
> +#define MACCR_FULLDUP           (1UL<<15)
> +#define MACCR_CRC_APD           (1UL<<14)
> +#define MACCR_RCV_ALL           (1UL<<12)
> +#define MACCR_RX_FTL            (1UL<<11)
> +#define MACCR_RX_RUNT           (1UL<<10)
> +#define MACCR_HT_MULTI_EN       (1UL<<9)
> +#define MACCR_RCV_EN            (1UL<<8)
> +#define MACCR_ENRX_IN_HALFTX    (1UL<<6)
> +#define MACCR_XMT_EN            (1UL<<5)
> +#define MACCR_CRC_DIS           (1UL<<4)
> +#define MACCR_LOOP_EN           (1UL<<3)
> +#define MACCR_SW_RST            (1UL<<2)
> +#define MACCR_RDMA_EN           (1UL<<1)
> +#define MACCR_XDMA_EN           (1UL<<0)
> +
> +/*
> + * MDIO
> + */
> +#define PHYCR_MDIOWR            (1 << 27)
> +#define PHYCR_MDIORD            (1 << 26)
> +
> +/*
> + * Tx/Rx descriptors
> + */
> +typedef struct Ftmac110RXD {
> +    /* RXDES0 */
> +    uint32_t len:11;
> +    uint32_t rsvd1:5;
> +    uint32_t mcast:1;
> +    uint32_t bcast:1;
> +    uint32_t error:5;
> +    uint32_t rsvd2:5;
> +    uint32_t lrs:1;
> +    uint32_t frs:1;
> +    uint32_t rsvd3:1;
> +    uint32_t owner:1;   /* BIT: 31 - 1:HW, 0: SW */
> +
> +    /* RXDES1 */
> +    uint32_t bufsz:11;
> +    uint32_t rsvd4:20;
> +    uint32_t end:1;     /* BIT: 31 */
> +
> +    /* RXDES2 */
> +    uint32_t buf;
> +
> +    /* RXDES3 */
> +    void     *skb;
> +} __attribute__ ((aligned (16))) Ftmac110RXD;
> +
> +typedef struct Ftmac110TXD {
> +    /* TXDES0 */
> +    uint32_t error:2;
> +    uint32_t rsvd1:29;
> +    uint32_t owner:1;   /* BIT: 31 - 1:HW, 0: SW */
> +
> +    /* TXDES1 */
> +    uint32_t len:11;
> +    uint32_t rsvd2:16;
> +    uint32_t lts:1;
> +    uint32_t fts:1;
> +    uint32_t tx2fic:1;
> +    uint32_t txic:1;
> +    uint32_t end:1;     /* BIT: 31 */
> +
> +    /* TXDES2 */
> +    uint32_t buf;
> +
> +    /* TXDES3 */
> +    void     *skb;
> +
> +} __attribute__ ((aligned (16))) Ftmac110TXD;
> +
> +#endif  /* FTMAC_H */
> --
> 1.7.9.5
>
>
Kuo-Jung Su Feb. 18, 2013, 2:45 a.m. UTC | #3
2013/2/9 Blue Swirl <blauwirbel@gmail.com>:
> On Wed, Feb 6, 2013 at 9:45 AM, Kuo-Jung Su <dantesu@gmail.com> wrote:
>> From: Kuo-Jung Su <dantesu@faraday-tech.com>
>>
>> The FTMAC110 is an Ethernet controller that provides AHB master capability
>> and is in full compliance with the IEEE 802.3 10/100 Mbps specifications.
>> Its DMA controller handles all data transfers between system memory
>> and on-chip memories.
>> It supports half-word data transfer for Linux. However it has a weird DMA
>> alignment issue:
>>
>> (1) Tx DMA Buffer Address:
>>     1 bytes aligned: Invalid
>>     2 bytes aligned: O.K
>>     4 bytes aligned: O.K
>>
>> (2) Rx DMA Buffer Address:
>>     1 bytes aligned: Invalid
>>     2 bytes aligned: O.K
>>     4 bytes aligned: Invalid (It means 0x0, 0x4, 0x8, 0xC are invalid)
>>
>> Signed-off-by: Kuo-Jung Su <dantesu@faraday-tech.com>
>> ---
>>  hw/arm/Makefile.objs  |    1 +
>>  hw/arm/faraday_a360.c |   10 +
>>  hw/arm/ftmac110.c     |  681 +++++++++++++++++++++++++++++++++++++++++++++++++
>>  hw/arm/ftmac110.h     |  131 ++++++++++
>>  4 files changed, 823 insertions(+)
>>  create mode 100644 hw/arm/ftmac110.c
>>  create mode 100644 hw/arm/ftmac110.h
>>
>> diff --git a/hw/arm/Makefile.objs b/hw/arm/Makefile.objs
>> index 70d4f25..f5eeaeb 100644
>> --- a/hw/arm/Makefile.objs
>> +++ b/hw/arm/Makefile.objs
>> @@ -47,3 +47,4 @@ obj-y += ftapbbrg020.o
>>  obj-y += ftnandc021.o
>>  obj-y += fti2c010.o
>>  obj-y += ftssp010.o
>> +obj-y += ftmac110.o
>> diff --git a/hw/arm/faraday_a360.c b/hw/arm/faraday_a360.c
>> index 52cfcec..51e8649 100644
>> --- a/hw/arm/faraday_a360.c
>> +++ b/hw/arm/faraday_a360.c
>> @@ -31,6 +31,7 @@ a360_device_init(A360State *s)
>>      qemu_irq *pic;
>>      DeviceState *ds, *fl;
>>      SSIBus *spi;
>> +    int done_nic = 0;
>>      int i, nr_flash;
>>      qemu_irq cs_line;
>>      qemu_irq ack, req;
>> @@ -122,6 +123,15 @@ a360_device_init(A360State *s)
>>      req = qdev_get_gpio_in(s->pdma[0], 2);
>>      qdev_connect_gpio_out(s->pdma[0], 2, ack);
>>      qdev_connect_gpio_out(ds, 1, req);
>> +
>> +    /* ftmac110 */
>> +    for (i = 0; i < nb_nics; i++) {
>> +        NICInfo *nd = &nd_table[i];
>> +        if (!done_nic && (!nd->model || strcmp(nd->model, "ftmac110") == 0)) {
>> +            ftmac110_init(nd, 0x90900000, pic[25]);
>> +            done_nic = 1;
>> +        }
>> +    }
>>  }
>>
>>  static void
>> diff --git a/hw/arm/ftmac110.c b/hw/arm/ftmac110.c
>> new file mode 100644
>> index 0000000..d45f4ba
>> --- /dev/null
>> +++ b/hw/arm/ftmac110.c
>> @@ -0,0 +1,681 @@
>> +/*
>> + * QEMU model of the FTMAC110 Controller
>> + *
>> + * Copyright (C) 2012 Faraday Technology
>> + * Written by Dante Su <dantesu@faraday-tech.com>
>> + *
>> + * This file is licensed under GNU GPL v2+.
>> + */
>> +
>> +/*******************************************************************/
>> +/*               FTMAC110 DMA design issue                         */
>> +/*                                             Dante Su 2010.02.03 */
>> +/*                                                                 */
>> +/* The DMA engine has a weird restriction that its Rx DMA engine   */
>> +/* accepts only 16-bits aligned address, 32-bits aligned is still  */
>> +/* invalid. However this restriction does not apply to Tx DMA.     */
>> +/* Conclusion:                                                     */
>> +/* (1) Tx DMA Buffer Address:                                      */
>> +/*     1 bytes aligned: Invalid                                    */
>> +/*     2 bytes aligned: O.K                                        */
>> +/*     4 bytes aligned: O.K (-> u-boot ZeroCopy is possible)       */
>> +/* (2) Rx DMA Buffer Address:                                      */
>> +/*     1 bytes aligned: Invalid                                    */
>> +/*     2 bytes aligned: O.K                                        */
>> +/*     4 bytes aligned: Invalid                                    */
>> +/*******************************************************************/
>
> Does this also apply to descriptors?

No, it only apply to the packet/frame buffer.

>
>> +
>> +#include <hw/sysbus.h>
>> +#include <sysemu/sysemu.h>
>> +#include <net/net.h>
>> +
>> +#include "faraday.h"
>> +#include "ftmac110.h"
>> +
>> +#define TYPE_FTMAC110    "ftmac110"
>> +
>> +typedef struct Ftmac110State {
>> +    SysBusDevice busdev;
>> +    MemoryRegion mmio;
>> +
>> +    QEMUBH *bh;
>> +    qemu_irq irq;
>> +    NICState *nic;
>> +    NICConf conf;
>> +
>> +    uint32_t isr;
>> +    uint32_t ier;
>> +    uint32_t mhash[2];
>> +    uint32_t tx_bar;
>> +    uint32_t rx_bar;
>> +    uint32_t tx_idx;
>> +    uint32_t rx_idx;
>> +    uint32_t maccr;
>> +    uint32_t macsr;
>> +    uint32_t phycr;
>> +    uint32_t phycr_rd;
>> +
>> +    struct {
>> +        uint8_t  buf[2048];
>> +        uint32_t len;
>> +    } txbuff;
>> +
>> +    uint32_t rx_pkt;
>> +    uint32_t rx_bcst;
>> +    uint32_t rx_mcst;
>> +    uint16_t rx_runt;
>> +    uint16_t rx_drop;
>> +    uint16_t rx_crc;
>> +    uint16_t rx_ftl;
>> +    uint32_t tx_pkt;
>> +
>> +} Ftmac110State;
>> +
>> +#define FTMAC110(obj) \
>> +    OBJECT_CHECK(Ftmac110State, obj, TYPE_FTMAC110)
>> +
>> +static uint8_t bitrev8(uint8_t v)
>> +{
>> +    int i;
>> +    uint8_t r = 0;
>> +    for (i = 0; i < 8; ++i) {
>> +        if (v & (1 << i)) {
>> +            r |= (1 << (7 - i));
>> +        }
>> +    }
>> +    return r;
>> +}
>> +
>> +static int ftmac110_mcast_hash(int len, const uint8_t *p)
>> +{
>> +#define CRCPOLY_LE 0xedb88320
>> +    int i;
>> +    uint32_t crc = 0xFFFFFFFF;
>> +
>> +    while (len--) {
>> +        crc ^= *p++;
>> +        for (i = 0; i < 8; i++) {
>> +            crc = (crc >> 1) ^ ((crc & 1) ? CRCPOLY_LE : 0);
>> +        }
>> +    }
>> +
>> +    /* Reverse CRC32 and return MSB 6 bits only */
>> +    return bitrev8(crc >> 24) >> 2;
>> +}
>> +
>> +static void ftmac110_read_rxdesc(hwaddr addr, Ftmac110RXD *desc)
>> +{
>> +    int i;
>> +    uint32_t *p = (uint32_t *)desc;
>> +
>> +    if (addr & 0x0f) {
>> +        hw_error("ftmac110: Rx desc is not 16-byte aligned!\n"
>> +                 "It's fine in QEMU but the real HW would panic.\n");
>> +    }
>> +
>> +    cpu_physical_memory_read(addr, desc, sizeof(*desc));
>> +
>> +    for (i = 0; i < sizeof(*desc); i += 4) {
>> +        *p = le32_to_cpu(*p);
>> +    }
>> +
>> +    if ((desc->buf & 0x1) || !(desc->buf % 4)) {
>> +        hw_error("ftmac110: rx buffer is not exactly 16-bit aligned.\n");
>> +    }
>> +}
>> +
>> +static void ftmac110_write_rxdesc(hwaddr addr, Ftmac110RXD *desc)
>> +{
>> +    int i;
>> +    uint32_t *p = (uint32_t *)desc;
>> +
>
> Alignment check probably also here.

Since the decriptor is supplied from the software drivers, and the
FTMAC110 always checks the OWNER bit before taking any actions,
so a descriptor read is always called prior to any descriptor write,
and the alignment check is invoked inside ftmac110_read_rxdesc(...)
and ftmac110_read_rxdesc(...).

BTW, the descriptor alignment is 16 bytes.

>
>> +    for (i = 0; i < sizeof(*desc); i += 4) {
>> +        *p = cpu_to_le32(*p);
>> +    }
>> +
>> +    cpu_physical_memory_write(addr, desc, sizeof(*desc));
>> +}
>> +
>> +static void ftmac110_read_txdesc(hwaddr addr, Ftmac110TXD *desc)
>> +{
>> +    int i;
>> +    uint32_t *p = (uint32_t *)desc;
>> +
>> +    if (addr & 0x0f) {
>> +        hw_error("ftmac110: Tx desc is not 16-byte aligned!\n"
>> +                 "It's fine in QEMU but the real HW would panic.\n");
>> +    }
>> +
>> +    cpu_physical_memory_read(addr, desc, sizeof(*desc));
>> +
>> +    for (i = 0; i < sizeof(*desc); i += 4) {
>> +        *p = le32_to_cpu(*p);
>> +    }
>> +
>> +    if (desc->buf & 0x1) {
>> +        hw_error("ftmac110: tx buffer is not 16-bit aligned.\n");
>> +    }
>> +}
>> +
>> +static void ftmac110_write_txdesc(hwaddr addr, Ftmac110TXD *desc)
>> +{
>> +    int i;
>> +    uint32_t *p = (uint32_t *)desc;
>> +
>> +    for (i = 0; i < sizeof(*desc); i += 4) {
>> +        *p = cpu_to_le32(*p);
>> +    }
>> +
>
> Ditto.
>
>> +    cpu_physical_memory_write(addr, desc, sizeof(*desc));
>> +}
>> +
>> +static void ftmac110_update_irq(Ftmac110State *s)
>> +{
>> +    if (s->isr & s->ier) {
>> +        qemu_set_irq(s->irq, 1);
>> +    } else {
>> +        qemu_set_irq(s->irq, 0);
>> +    }
>> +}
>> +
>> +static int ftmac110_can_receive(NetClientState *nc)
>> +{
>> +    Ftmac110State *s = FTMAC110(DO_UPCAST(NICState, nc, nc)->opaque);
>> +    Ftmac110RXD rxd;
>> +    hwaddr off = s->rx_bar + s->rx_idx * sizeof(rxd);
>> +
>> +    if ((s->maccr & (MACCR_RCV_EN | MACCR_RDMA_EN))
>> +            != (MACCR_RCV_EN | MACCR_RDMA_EN)) {
>> +        return 0;
>> +    }
>> +
>> +    ftmac110_read_rxdesc(off, &rxd);
>> +
>> +    return rxd.owner;
>> +}
>> +
>> +static ssize_t ftmac110_receive(NetClientState *nc,
>> +                                const uint8_t  *buf,
>> +                                size_t          size)
>> +{
>> +    const uint8_t *ptr = buf;
>> +    hwaddr off;
>> +    size_t len;
>> +    Ftmac110RXD rxd;
>> +    Ftmac110State *s = FTMAC110(DO_UPCAST(NICState, nc, nc)->opaque);
>> +    int bcst, mcst, ftl, proto;
>> +
>> +    if ((s->maccr & (MACCR_RCV_EN | MACCR_RDMA_EN))
>> +            != (MACCR_RCV_EN | MACCR_RDMA_EN)) {
>> +        return -1;
>> +    }
>> +
>> +    /*
>> +     * Check if it's a long frame. (CRC32 is excluded)
>> +     */
>> +    proto = (buf[12] << 8) | buf[13];
>> +    if (proto == 0x8100) {  /* 802.1Q VLAN */
>> +        ftl = (size > 1518) ? 1 : 0;
>> +    } else {
>> +        ftl = (size > 1514) ? 1 : 0;
>> +    }
>> +    if (ftl) {
>> +        DPRINTF("ftmac110_receive: frame too long, drop it\n");
>> +        return -1;
>> +    }
>> +
>> +    /* if it's a broadcast */
>> +    if ((buf[0] == 0xff) && (buf[1] == 0xff) && (buf[2] == 0xff)
>> +            && (buf[3] == 0xff) && (buf[4] == 0xff) && (buf[5] == 0xff)) {
>> +        bcst = 1;
>> +        if (!(s->maccr & MACCR_RCV_ALL) && !(s->maccr & MACCR_RX_BROADPKT)) {
>> +            DPRINTF("ftmac110_receive: bcst filtered\n");
>> +            return -1;
>> +        }
>> +    } else {
>> +        bcst = 0;
>> +    }
>> +
>> +    /* if it's a multicast */
>> +    if ((buf[0] == 0x01) && (buf[1] == 0x00) && (buf[2] == 0x5e)
>> +        && (buf[3] <= 0x7f)) {
>> +        mcst = 1;
>> +        if (!(s->maccr & MACCR_RCV_ALL) && !(s->maccr & MACCR_RX_MULTIPKT)) {
>> +            int hash;
>> +            if (!(s->maccr & MACCR_HT_MULTI_EN)) {
>> +                DPRINTF("ftmac110_receive: mcst filtered\n");
>> +                return -1;
>> +            }
>> +            hash = ftmac110_mcast_hash(6, buf);
>> +            if (!(s->mhash[hash / 32] & (1 << (hash % 32)))) {
>> +                DPRINTF("ftmac110_receive: mcst filtered\n");
>> +                return -1;
>> +            }
>> +        }
>> +    } else {
>> +        mcst = 0;
>> +    }
>> +
>> +    /* check if the destination matches NIC mac address */
>> +    if (!(s->maccr & MACCR_RCV_ALL) && !bcst && !mcst) {
>> +        if (memcmp(s->conf.macaddr.a, buf, 6)) {
>> +            return -1;
>> +        }
>> +    }
>> +
>> +    while (size > 0) {
>> +        off = s->rx_bar + s->rx_idx * sizeof(rxd);
>> +        ftmac110_read_rxdesc(off, &rxd);
>> +        if (!rxd.owner) {
>> +            s->isr |= ISR_NORXBUF;
>> +            DPRINTF("ftmac110: out of rxd!?\n");
>> +            return -1;
>> +        }
>> +
>> +        if (ptr == buf) {
>> +            rxd.frs = 1;
>> +        } else {
>> +            rxd.frs = 0;
>> +        }
>> +
>> +        len = size > rxd.bufsz ? rxd.bufsz : size;
>
> This write below is the DMA that the comment above talks about, but
> there's no alignment check here either.
>

The alignment check has been invoked inside
ftmac110_read_txdesc() and ftmac110_read_rxdesc()

>> +        cpu_physical_memory_write(rxd.buf, (uint8_t *)ptr, len);
>> +        ptr  += len;
>> +        size -= len;
>> +
>> +        if (size <= 0) {
>> +            rxd.lrs = 1;
>> +        } else {
>> +            rxd.lrs = 0;
>> +        }
>> +
>> +        rxd.len = len;
>> +        rxd.bcast = bcst;
>> +        rxd.mcast = mcst;
>> +        rxd.owner = 0;
>> +
>> +        /* write-back the rx descriptor */
>> +        ftmac110_write_rxdesc(off, &rxd);
>> +
>> +        if (rxd.end) {
>> +            s->rx_idx = 0;
>> +        } else {
>> +            s->rx_idx += 1;
>> +        }
>> +    }
>> +
>> +    /* update interrupt signal */
>> +    s->isr |= ISR_RPKT_OK | ISR_RPKT_FINISH;
>> +    ftmac110_update_irq(s);
>> +
>> +    return (ssize_t)((uint32_t)ptr - (uint32_t)buf);
>> +}
>> +
>> +static void ftmac110_transmit(Ftmac110State *s, uint32_t *bar, uint32_t *idx)
>> +{
>> +    hwaddr off;
>> +    uint8_t *buf;
>> +    int ftl, proto;
>> +    Ftmac110TXD txd;
>> +
>> +    if ((s->maccr & (MACCR_XMT_EN | MACCR_XDMA_EN))
>> +            != (MACCR_XMT_EN | MACCR_XDMA_EN)) {
>> +        return;
>> +    }
>> +
>> +    do {
>> +        off = (*bar) + (*idx) * sizeof(txd);
>> +        ftmac110_read_txdesc(off, &txd);
>> +        if (!txd.owner) {
>> +            s->isr |= ISR_NOTXBUF;
>> +            break;
>> +        }
>> +        if (txd.fts) {
>> +            s->txbuff.len = 0;
>> +        }
>> +        if (txd.len + s->txbuff.len > sizeof(s->txbuff.buf)) {
>> +            hw_error("ftmac110: tx buffer overflow!\n");
>> +            exit(1);
>> +        }
>> +        buf = s->txbuff.buf + s->txbuff.len;
>
> Ditto.
>
>> +        cpu_physical_memory_read(txd.buf, (uint8_t *)buf, txd.len);
>> +        s->txbuff.len += txd.len;
>> +        /* Check if it's a long frame. (CRC32 is excluded) */
>> +        proto = (s->txbuff.buf[12] << 8) | s->txbuff.buf[13];
>> +        if (proto == 0x8100) {
>> +            ftl = (s->txbuff.len > 1518) ? 1 : 0;
>> +        } else {
>> +            ftl = (s->txbuff.len > 1514) ? 1 : 0;
>> +        }
>> +        if (ftl) {
>> +            hw_error("ftmac110_transmit: frame too long\n");
>> +            exit(1);
>> +        }
>> +        if (txd.lts) {
>> +            if (s->maccr & MACCR_LOOP_EN) {
>> +                ftmac110_receive(&s->nic->nc, s->txbuff.buf, s->txbuff.len);
>> +            } else {
>> +                qemu_send_packet(&s->nic->nc, s->txbuff.buf, s->txbuff.len);
>> +            }
>> +        }
>> +        if (txd.end) {
>> +            *idx = 0;
>> +        } else {
>> +            *idx += 1;
>> +        }
>> +        if (txd.tx2fic) {
>> +            s->isr |= ISR_XPKT_OK;
>> +        }
>> +        if (txd.txic) {
>> +            s->isr |= ISR_XPKT_FINISH;
>> +        }
>> +        txd.owner = 0;
>> +        ftmac110_write_txdesc(off, &txd);
>> +    } while (1);
>> +}
>> +
>> +static void ftmac110_bh(void *opaque)
>> +{
>> +    Ftmac110State *s = FTMAC110(opaque);
>> +
>> +    if (s->tx_bar) {
>> +        ftmac110_transmit(s, &s->tx_bar, &s->tx_idx);
>> +    }
>> +
>> +    ftmac110_update_irq(s);
>> +}
>> +
>> +static uint64_t ftmac110_mem_read(void    *opaque,
>> +                                  hwaddr   addr,
>> +                                  unsigned size)
>> +{
>> +    Ftmac110State *s = FTMAC110(opaque);
>> +    uint32_t rc = 0;
>> +
>> +    switch (addr) {
>> +    case REG_ISR:
>> +        rc = s->isr;
>> +        s->isr = 0;
>> +        ftmac110_update_irq(s);
>> +        break;
>> +    case REG_IMR:
>> +        return s->ier;
>> +    case REG_HMAC:
>> +        return s->conf.macaddr.a[1]
>> +               | (s->conf.macaddr.a[0] << 8);
>> +    case REG_LMAC:
>> +        return s->conf.macaddr.a[5]
>> +               | (s->conf.macaddr.a[4] << 8)
>> +               | (s->conf.macaddr.a[3] << 16)
>> +               | (s->conf.macaddr.a[2] << 24);
>> +    case REG_MHASH0:
>> +        return s->mhash[0];
>> +    case REG_MHASH1:
>> +        return s->mhash[1];
>> +    case REG_TXBAR:
>> +        return s->tx_bar;
>> +    case REG_RXBAR:
>> +        return s->rx_bar;
>> +    case REG_MACCR:
>> +        return s->maccr;
>> +    case REG_MACSR:
>> +        rc = s->macsr;
>> +        s->macsr = 0;
>> +        break;
>> +    case REG_PHYCTRL:
>> +        do {
>> +            uint8_t dev = (s->phycr >> 16) & 0x1f;
>> +            uint8_t reg = (s->phycr >> 21) & 0x1f;
>> +            if (dev != 0) {
>> +                break;
>> +            }
>> +            if (s->phycr_rd) {
>> +                switch (reg) {
>> +                case 0:    /* PHY control register */
>> +                    return s->phycr | 0x1140;
>> +                case 1:    /* PHY status register */
>> +                    return s->phycr | 0x796d;
>> +                case 2:    /* PHY ID 1 register */
>> +                    return s->phycr | 0x0141;
>> +                case 3:    /* PHY ID 2 register */
>> +                    return s->phycr | 0x0cc2;
>> +                case 4:    /* Autonegotiation advertisement register */
>> +                    return s->phycr | 0x0de1;
>> +                case 5:    /* Autonegotiation partner abilities register */
>> +                    return s->phycr | 0x45e1;
>> +                }
>> +            }
>> +        } while (0);
>> +        break;
>> +    case REG_FCR:
>> +        return 0x0000a400;
>> +    case REG_BPR:
>> +        return 0x00000400;
>> +    case REG_TXPKT:
>> +        return s->tx_pkt;
>> +    case REG_RXPKT:
>> +        return s->rx_pkt;
>> +    case REG_RXBCST:
>> +        return s->rx_bcst;
>> +    case REG_RXMCST:
>> +        return s->rx_mcst;
>> +    case REG_RXRUNT:
>> +        return s->rx_runt << 16;
>> +    case REG_RXCRCFTL:
>> +        return (s->rx_crc << 16) | (s->rx_ftl);
>> +    case REG_REV:
>> +        return 0x00000700;
>> +    case REG_FEA:
>> +        return 0x00000007;
>> +    default:
>> +        break;
>> +    }
>> +
>> +    return rc;
>> +}
>> +
>> +static void ftmac110_chip_reset(Ftmac110State *s)
>> +{
>> +    s->isr = 0;
>> +    s->ier = 0;
>> +    s->mhash[0] = 0;
>> +    s->mhash[1] = 0;
>> +    s->tx_bar = 0;
>> +    s->rx_bar = 0;
>> +    s->tx_idx = 0;
>> +    s->rx_idx = 0;
>> +    s->maccr = 0;
>> +    s->macsr = 0;
>> +    s->phycr = 0;
>> +    s->txbuff.len = 0;
>> +    s->rx_pkt = 0;
>> +    s->rx_bcst = 0;
>> +    s->rx_mcst = 0;
>> +    s->rx_runt = 0;
>> +    s->rx_drop = 0;
>> +    s->rx_crc = 0;
>> +    s->rx_ftl = 0;
>> +    s->tx_pkt = 0;
>> +
>> +    if (s->bh) {
>> +        qemu_bh_cancel(s->bh);
>> +    }
>> +
>> +    ftmac110_update_irq(s);
>> +}
>> +
>> +static void ftmac110_mem_write(void    *opaque,
>> +                               hwaddr   addr,
>> +                               uint64_t val,
>> +                               unsigned size)
>> +{
>> +    Ftmac110State *s = FTMAC110(opaque);
>> +
>> +    switch (addr) {
>> +    case REG_IMR:
>> +        s->ier = (uint32_t)val;
>> +        ftmac110_update_irq(s);
>> +        break;
>> +    case REG_HMAC:
>> +        s->conf.macaddr.a[1] = (val >> 0) & 0xff;
>> +        s->conf.macaddr.a[0] = (val >> 8) & 0xff;
>> +        break;
>> +    case REG_LMAC:
>> +        s->conf.macaddr.a[5] = (val >> 0) & 0xff;
>> +        s->conf.macaddr.a[4] = (val >> 8) & 0xff;
>> +        s->conf.macaddr.a[3] = (val >> 16) & 0xff;
>> +        s->conf.macaddr.a[2] = (val >> 24) & 0xff;
>> +        break;
>> +    case REG_MHASH0:
>> +        s->mhash[0] = (uint32_t)val;
>> +        break;
>> +    case REG_MHASH1:
>> +        s->mhash[1] = (uint32_t)val;
>> +        break;
>> +    case REG_TXBAR:
>> +        s->tx_bar = (uint32_t)val;
>> +        break;
>> +    case REG_RXBAR:
>> +        s->rx_bar = (uint32_t)val;
>> +        break;
>> +    case REG_MACCR:
>> +        s->maccr = (uint32_t)val;
>> +        if (s->maccr & MACCR_SW_RST) {
>> +            ftmac110_chip_reset(s);
>> +            s->maccr &= ~MACCR_SW_RST;
>> +        }
>> +        break;
>> +    case REG_PHYCTRL:
>> +        s->phycr = (uint32_t)val;
>> +        if (s->phycr & PHYCR_MDIORD) {
>> +            s->phycr_rd = 1;
>> +        } else {
>> +            s->phycr_rd = 0;
>> +        }
>> +        s->phycr &= ~(PHYCR_MDIOWR | PHYCR_MDIORD);
>> +        break;
>> +    case REG_TXPD:
>> +        qemu_bh_schedule(s->bh);
>> +        break;
>> +    default:
>> +        break;
>> +    }
>> +}
>> +
>> +static const MemoryRegionOps bus_ops = {
>> +    .read  = ftmac110_mem_read,
>> +    .write = ftmac110_mem_write,
>> +    .endianness = DEVICE_LITTLE_ENDIAN,
>> +    .valid = {
>> +        .min_access_size = 4,
>> +        .max_access_size = 4
>> +    }
>> +};
>> +
>> +static void ftmac110_cleanup(NetClientState *nc)
>> +{
>> +    Ftmac110State *s = FTMAC110(DO_UPCAST(NICState, nc, nc)->opaque);
>> +
>> +    s->nic = NULL;
>> +}
>> +
>> +static NetClientInfo net_ftmac110_info = {
>> +    .type = NET_CLIENT_OPTIONS_KIND_NIC,
>> +    .size = sizeof(NICState),
>> +    .can_receive = ftmac110_can_receive,
>> +    .receive = ftmac110_receive,
>> +    .cleanup = ftmac110_cleanup,
>> +};
>> +
>> +static void ftmac110_reset(DeviceState *ds)
>> +{
>> +    SysBusDevice *busdev = SYS_BUS_DEVICE(ds);
>> +    Ftmac110State *s = FTMAC110(FROM_SYSBUS(Ftmac110State, busdev));
>> +
>> +    ftmac110_chip_reset(s);
>> +}
>> +
>> +static int ftmac110_init1(SysBusDevice *dev)
>> +{
>> +    Ftmac110State *s = FTMAC110(FROM_SYSBUS(Ftmac110State, dev));
>> +
>> +    memory_region_init_io(&s->mmio, &bus_ops, s, TYPE_FTMAC110, 0x1000);
>> +    sysbus_init_mmio(dev, &s->mmio);
>> +    sysbus_init_irq(dev, &s->irq);
>> +
>> +    qemu_macaddr_default_if_unset(&s->conf.macaddr);
>> +    s->nic = qemu_new_nic(&net_ftmac110_info, &s->conf,
>> +                          object_get_typename(OBJECT(dev)), dev->qdev.id, s);
>> +    qemu_format_nic_info_str(&s->nic->nc, s->conf.macaddr.a);
>> +
>> +    s->bh = qemu_bh_new(ftmac110_bh, s);
>> +
>> +    ftmac110_chip_reset(s);
>> +
>> +    return 0;
>> +}
>> +
>> +static const VMStateDescription vmstate_ftmac110 = {
>> +    .name = TYPE_FTMAC110,
>> +    .version_id = 1,
>> +    .minimum_version_id = 1,
>> +    .minimum_version_id_old = 1,
>> +    .fields = (VMStateField[]) {
>> +        VMSTATE_UINT32(ier, Ftmac110State),
>> +        VMSTATE_UINT32(tx_bar, Ftmac110State),
>> +        VMSTATE_UINT32(rx_bar, Ftmac110State),
>> +        VMSTATE_UINT32(tx_idx, Ftmac110State),
>> +        VMSTATE_UINT32(rx_idx, Ftmac110State),
>> +        VMSTATE_UINT32(maccr, Ftmac110State),
>> +        VMSTATE_UINT32(macsr, Ftmac110State),
>> +        VMSTATE_UINT32(phycr, Ftmac110State),
>> +        VMSTATE_UINT32_ARRAY(mhash, Ftmac110State, 2),
>> +        VMSTATE_END_OF_LIST()
>> +    }
>> +};
>> +
>> +static Property ftmac110_properties[] = {
>> +    DEFINE_NIC_PROPERTIES(Ftmac110State, conf),
>> +    DEFINE_PROP_END_OF_LIST(),
>> +};
>> +
>> +static void ftmac110_class_init(ObjectClass *klass, void *data)
>> +{
>> +    DeviceClass *dc = DEVICE_CLASS(klass);
>> +    SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
>> +
>> +    k->init   = ftmac110_init1;
>> +    dc->reset = ftmac110_reset;
>> +    dc->vmsd  = &vmstate_ftmac110;
>> +    dc->props = ftmac110_properties;
>> +}
>> +
>> +static const TypeInfo ftmac110_info = {
>> +    .name           = TYPE_FTMAC110,
>> +    .parent         = TYPE_SYS_BUS_DEVICE,
>> +    .instance_size  = sizeof(Ftmac110State),
>> +    .class_init     = ftmac110_class_init,
>> +};
>> +
>> +static void ftmac110_register_types(void)
>> +{
>> +    type_register_static(&ftmac110_info);
>> +}
>> +
>> +/* Legacy helper function.  Should go away when machine config files are
>> +   implemented.  */
>> +void ftmac110_init(NICInfo *nd, uint32_t base, qemu_irq irq)
>> +{
>> +    DeviceState *dev;
>> +    SysBusDevice *s;
>> +
>> +    qemu_check_nic_model(nd, TYPE_FTMAC110);
>> +    dev = qdev_create(NULL, TYPE_FTMAC110);
>> +    qdev_set_nic_properties(dev, nd);
>> +    qdev_init_nofail(dev);
>> +    s = SYS_BUS_DEVICE(dev);
>> +    sysbus_mmio_map(s, 0, base);
>> +    sysbus_connect_irq(s, 0, irq);
>> +}
>> +
>> +type_init(ftmac110_register_types)
>> diff --git a/hw/arm/ftmac110.h b/hw/arm/ftmac110.h
>> new file mode 100644
>> index 0000000..61f44e7
>> --- /dev/null
>> +++ b/hw/arm/ftmac110.h
>> @@ -0,0 +1,131 @@
>> +/*
>> + * QEMU model of the FTMAC110 Controller
>> + *
>> + * Copyright (C) 2012 Faraday Technology
>> + * Written by Dante Su <dantesu@faraday-tech.com>
>> + *
>> + * This file is licensed under GNU GPL v2+.
>> + */
>> +
>> +#ifndef HW_ARM_FTMAC110_H
>> +#define HW_ARM_FTMAC110_H
>> +
>> +#define REG_ISR             0x00
>> +#define REG_IMR             0x04
>> +#define REG_HMAC            0x08
>> +#define REG_LMAC            0x0c
>> +#define REG_MHASH0          0x10
>> +#define REG_MHASH1          0x14
>> +#define REG_TXPD            0x18
>> +#define REG_RXPD            0x1c
>> +#define REG_TXBAR           0x20
>> +#define REG_RXBAR           0x24
>> +#define REG_ITC             0x28
>> +#define REG_APTC            0x2C
>> +#define REG_DBLAC           0x30
>> +#define REG_REV             0x34
>> +#define REG_FEA             0x38
>> +
>> +#define REG_MACCR           0x88
>> +#define REG_MACSR           0x8C
>> +#define REG_PHYCTRL         0x90
>> +#define REG_PHYDATA         0x94
>> +#define REG_FCR             0x98
>> +#define REG_BPR             0x9c
>> +
>> +#define REG_TXPKT           0xf8
>> +#define REG_RXPKT           0xf4
>> +#define REG_RXBCST          0xec
>> +#define REG_RXMCST          0xf0
>> +#define REG_RXRUNT          0xe0
>> +#define REG_RXCRCFTL        0xe4
>> +
>> +/* interrupt status register */
>> +#define ISR_PHYSTS_CHG      (1UL<<9)
>> +#define ISR_AHB_ERR         (1UL<<8)
>> +#define ISR_RPKT_LOST       (1UL<<7)
>> +#define ISR_RPKT_OK         (1UL<<6)
>> +#define ISR_XPKT_LOST       (1UL<<5)
>> +#define ISR_XPKT_OK         (1UL<<4)
>> +#define ISR_NOTXBUF         (1UL<<3)
>> +#define ISR_XPKT_FINISH     (1UL<<2)
>> +#define ISR_NORXBUF         (1UL<<1)
>> +#define ISR_RPKT_FINISH     (1UL<<0)
>> +
>> +/* MAC control register */
>> +#define MACCR_100M              (1UL<<18)
>> +#define MACCR_RX_BROADPKT       (1UL<<17)
>> +#define MACCR_RX_MULTIPKT       (1UL<<16)
>> +#define MACCR_FULLDUP           (1UL<<15)
>> +#define MACCR_CRC_APD           (1UL<<14)
>> +#define MACCR_RCV_ALL           (1UL<<12)
>> +#define MACCR_RX_FTL            (1UL<<11)
>> +#define MACCR_RX_RUNT           (1UL<<10)
>> +#define MACCR_HT_MULTI_EN       (1UL<<9)
>> +#define MACCR_RCV_EN            (1UL<<8)
>> +#define MACCR_ENRX_IN_HALFTX    (1UL<<6)
>> +#define MACCR_XMT_EN            (1UL<<5)
>> +#define MACCR_CRC_DIS           (1UL<<4)
>> +#define MACCR_LOOP_EN           (1UL<<3)
>> +#define MACCR_SW_RST            (1UL<<2)
>> +#define MACCR_RDMA_EN           (1UL<<1)
>> +#define MACCR_XDMA_EN           (1UL<<0)
>> +
>> +/*
>> + * MDIO
>> + */
>> +#define PHYCR_MDIOWR            (1 << 27)
>> +#define PHYCR_MDIORD            (1 << 26)
>> +
>> +/*
>> + * Tx/Rx descriptors
>> + */
>> +typedef struct Ftmac110RXD {
>> +    /* RXDES0 */
>> +    uint32_t len:11;
>> +    uint32_t rsvd1:5;
>> +    uint32_t mcast:1;
>> +    uint32_t bcast:1;
>> +    uint32_t error:5;
>> +    uint32_t rsvd2:5;
>> +    uint32_t lrs:1;
>> +    uint32_t frs:1;
>> +    uint32_t rsvd3:1;
>> +    uint32_t owner:1;   /* BIT: 31 - 1:HW, 0: SW */
>> +
>> +    /* RXDES1 */
>> +    uint32_t bufsz:11;
>> +    uint32_t rsvd4:20;
>> +    uint32_t end:1;     /* BIT: 31 */
>> +
>> +    /* RXDES2 */
>> +    uint32_t buf;
>> +
>> +    /* RXDES3 */
>> +    void     *skb;
>> +} __attribute__ ((aligned (16))) Ftmac110RXD;
>
> The alignment attributes are not useful since we are not accessing
> memory directly.
>

It's here because of the following codes:

static void ftmac110_read_txdesc(hwaddr addr, Ftmac110TXD *desc)
{
......
    cpu_physical_memory_read(addr, desc, sizeof(*desc));

    for (i = 0; i < sizeof(*desc); i += 4) {
        *p = le32_to_cpu(*p);
    }
......
}

In other words, it would be better to make sure these descriptors are
always aligned to 4 bytes boundary. And to avoid ambigous, I use the
real hardware
algnement (16  bytes)  instead of the emulator alignment (4 bytes).

P.S
In x86 platforms, the unaligned access is always resolved by the hardware
cores, so it's no harm if we have some unaligned issues.

>> +
>> +typedef struct Ftmac110TXD {
>> +    /* TXDES0 */
>> +    uint32_t error:2;
>> +    uint32_t rsvd1:29;
>> +    uint32_t owner:1;   /* BIT: 31 - 1:HW, 0: SW */
>> +
>> +    /* TXDES1 */
>> +    uint32_t len:11;
>> +    uint32_t rsvd2:16;
>> +    uint32_t lts:1;
>> +    uint32_t fts:1;
>> +    uint32_t tx2fic:1;
>> +    uint32_t txic:1;
>> +    uint32_t end:1;     /* BIT: 31 */
>> +
>> +    /* TXDES2 */
>> +    uint32_t buf;
>> +
>> +    /* TXDES3 */
>> +    void     *skb;
>> +
>> +} __attribute__ ((aligned (16))) Ftmac110TXD;
>> +
>> +#endif  /* FTMAC_H */
>
> HW_ARM_FTMAC110_H
>
>> --
>> 1.7.9.5
>>
Kuo-Jung Su Feb. 18, 2013, 7:30 a.m. UTC | #4
2013/2/17 Peter Crosthwaite <peter.crosthwaite@xilinx.com>:
> On Wed, Feb 6, 2013 at 7:45 PM, Kuo-Jung Su <dantesu@gmail.com> wrote:
>> From: Kuo-Jung Su <dantesu@faraday-tech.com>
>>
>> The FTMAC110 is an Ethernet controller that provides AHB master capability
>> and is in full compliance with the IEEE 802.3 10/100 Mbps specifications.
>> Its DMA controller handles all data transfers between system memory
>> and on-chip memories.
>> It supports half-word data transfer for Linux. However it has a weird DMA
>> alignment issue:
>>
>> (1) Tx DMA Buffer Address:
>>     1 bytes aligned: Invalid
>>     2 bytes aligned: O.K
>>     4 bytes aligned: O.K
>>
>> (2) Rx DMA Buffer Address:
>>     1 bytes aligned: Invalid
>>     2 bytes aligned: O.K
>>     4 bytes aligned: Invalid (It means 0x0, 0x4, 0x8, 0xC are invalid)
>>
>> Signed-off-by: Kuo-Jung Su <dantesu@faraday-tech.com>
>> ---
>>  hw/arm/Makefile.objs  |    1 +
>>  hw/arm/faraday_a360.c |   10 +
>>  hw/arm/ftmac110.c     |  681 +++++++++++++++++++++++++++++++++++++++++++++++++
>>  hw/arm/ftmac110.h     |  131 ++++++++++
>>  4 files changed, 823 insertions(+)
>>  create mode 100644 hw/arm/ftmac110.c
>>  create mode 100644 hw/arm/ftmac110.h
>>
>> diff --git a/hw/arm/Makefile.objs b/hw/arm/Makefile.objs
>> index 70d4f25..f5eeaeb 100644
>> --- a/hw/arm/Makefile.objs
>> +++ b/hw/arm/Makefile.objs
>> @@ -47,3 +47,4 @@ obj-y += ftapbbrg020.o
>>  obj-y += ftnandc021.o
>>  obj-y += fti2c010.o
>>  obj-y += ftssp010.o
>> +obj-y += ftmac110.o
>> diff --git a/hw/arm/faraday_a360.c b/hw/arm/faraday_a360.c
>> index 52cfcec..51e8649 100644
>> --- a/hw/arm/faraday_a360.c
>> +++ b/hw/arm/faraday_a360.c
>> @@ -31,6 +31,7 @@ a360_device_init(A360State *s)
>>      qemu_irq *pic;
>>      DeviceState *ds, *fl;
>>      SSIBus *spi;
>> +    int done_nic = 0;
>>      int i, nr_flash;
>>      qemu_irq cs_line;
>>      qemu_irq ack, req;
>> @@ -122,6 +123,15 @@ a360_device_init(A360State *s)
>>      req = qdev_get_gpio_in(s->pdma[0], 2);
>>      qdev_connect_gpio_out(s->pdma[0], 2, ack);
>>      qdev_connect_gpio_out(ds, 1, req);
>> +
>> +    /* ftmac110 */
>> +    for (i = 0; i < nb_nics; i++) {
>> +        NICInfo *nd = &nd_table[i];
>> +        if (!done_nic && (!nd->model || strcmp(nd->model, "ftmac110") == 0)) {
>> +            ftmac110_init(nd, 0x90900000, pic[25]);
>> +            done_nic = 1;
>> +        }
>> +    }
>>  }
>>
>>  static void
>> diff --git a/hw/arm/ftmac110.c b/hw/arm/ftmac110.c
>> new file mode 100644
>> index 0000000..d45f4ba
>> --- /dev/null
>> +++ b/hw/arm/ftmac110.c
>> @@ -0,0 +1,681 @@
>> +/*
>> + * QEMU model of the FTMAC110 Controller
>> + *
>> + * Copyright (C) 2012 Faraday Technology
>> + * Written by Dante Su <dantesu@faraday-tech.com>
>> + *
>> + * This file is licensed under GNU GPL v2+.
>> + */
>> +
>> +/*******************************************************************/
>> +/*               FTMAC110 DMA design issue                         */
>> +/*                                             Dante Su 2010.02.03 */
>> +/*                                                                 */
>> +/* The DMA engine has a weird restriction that its Rx DMA engine   */
>> +/* accepts only 16-bits aligned address, 32-bits aligned is still  */
>> +/* invalid. However this restriction does not apply to Tx DMA.     */
>> +/* Conclusion:                                                     */
>> +/* (1) Tx DMA Buffer Address:                                      */
>> +/*     1 bytes aligned: Invalid                                    */
>> +/*     2 bytes aligned: O.K                                        */
>> +/*     4 bytes aligned: O.K (-> u-boot ZeroCopy is possible)       */
>> +/* (2) Rx DMA Buffer Address:                                      */
>> +/*     1 bytes aligned: Invalid                                    */
>> +/*     2 bytes aligned: O.K                                        */
>> +/*     4 bytes aligned: Invalid                                    */
>> +/*******************************************************************/
>> +
>
>> +#include <hw/sysbus.h>
>> +#include <sysemu/sysemu.h>
>> +#include <net/net.h>
>> +
>> +#include "faraday.h"
>> +#include "ftmac110.h"
>> +
>> +#define TYPE_FTMAC110    "ftmac110"
>> +
>> +typedef struct Ftmac110State {
>> +    SysBusDevice busdev;
>> +    MemoryRegion mmio;
>> +
>> +    QEMUBH *bh;
>> +    qemu_irq irq;
>> +    NICState *nic;
>> +    NICConf conf;
>> +
>> +    uint32_t isr;
>> +    uint32_t ier;
>> +    uint32_t mhash[2];
>> +    uint32_t tx_bar;
>> +    uint32_t rx_bar;
>> +    uint32_t tx_idx;
>> +    uint32_t rx_idx;
>> +    uint32_t maccr;
>> +    uint32_t macsr;
>> +    uint32_t phycr;
>> +    uint32_t phycr_rd;
>> +
>> +    struct {
>> +        uint8_t  buf[2048];
>
> Magic number
>

Now a new macro for Max. Frame Length has been added.

>> +        uint32_t len;
>> +    } txbuff;
>> +
>> +    uint32_t rx_pkt;
>> +    uint32_t rx_bcst;
>> +    uint32_t rx_mcst;
>> +    uint16_t rx_runt;
>> +    uint16_t rx_drop;
>> +    uint16_t rx_crc;
>> +    uint16_t rx_ftl;
>> +    uint32_t tx_pkt;
>> +
>> +} Ftmac110State;
>> +
>> +#define FTMAC110(obj) \
>> +    OBJECT_CHECK(Ftmac110State, obj, TYPE_FTMAC110)
>> +
>> +static uint8_t bitrev8(uint8_t v)
>> +{
>> +    int i;
>> +    uint8_t r = 0;
>> +    for (i = 0; i < 8; ++i) {
>> +        if (v & (1 << i)) {
>> +            r |= (1 << (7 - i));
>> +        }
>> +    }
>> +    return r;
>> +}
>
> I would suggest adding this to bitops.[ch] for all the use. This would
> be a separate patch however.
>

I'll port the bitrev.[ch] from linux kernel to QEMU.

>> +
>> +static int ftmac110_mcast_hash(int len, const uint8_t *p)
>> +{
>> +#define CRCPOLY_LE 0xedb88320
>> +    int i;
>> +    uint32_t crc = 0xFFFFFFFF;
>> +
>> +    while (len--) {
>> +        crc ^= *p++;
>> +        for (i = 0; i < 8; i++) {
>> +            crc = (crc >> 1) ^ ((crc & 1) ? CRCPOLY_LE : 0);
>> +        }
>> +    }
>> +
>> +    /* Reverse CRC32 and return MSB 6 bits only */
>> +    return bitrev8(crc >> 24) >> 2;
>> +}
>> +
>> +static void ftmac110_read_rxdesc(hwaddr addr, Ftmac110RXD *desc)
>> +{
>> +    int i;
>> +    uint32_t *p = (uint32_t *)desc;
>> +
>> +    if (addr & 0x0f) {
>> +        hw_error("ftmac110: Rx desc is not 16-byte aligned!\n"
>> +                 "It's fine in QEMU but the real HW would panic.\n");
>> +    }
>> +
>> +    cpu_physical_memory_read(addr, desc, sizeof(*desc));
>
> I think its better to use the DMA API.
>
> dma_memory_read(&dma_context_memory, addr, desc, sizeof(*desc))
>
> cpu_physical_memory_read shouldnt really be used by devices.
>

Got it, thanks

>> +
>> +    for (i = 0; i < sizeof(*desc); i += 4) {
>> +        *p = le32_to_cpu(*p);
>> +    }
>> +
>> +    if ((desc->buf & 0x1) || !(desc->buf % 4)) {
>> +        hw_error("ftmac110: rx buffer is not exactly 16-bit aligned.\n");
>
> Looks like a programmer error. You could use qemu_log_mask(LOG_GUEST_ERROR
>

Got it, thanks

>> +    }
>> +}
>> +
>> +static void ftmac110_write_rxdesc(hwaddr addr, Ftmac110RXD *desc)
>> +{
>> +    int i;
>> +    uint32_t *p = (uint32_t *)desc;
>> +
>> +    for (i = 0; i < sizeof(*desc); i += 4) {
>> +        *p = cpu_to_le32(*p);
>> +    }
>> +
>> +    cpu_physical_memory_write(addr, desc, sizeof(*desc));
>
> Same thing with DMA API
>
>> +}
>> +
>> +static void ftmac110_read_txdesc(hwaddr addr, Ftmac110TXD *desc)
>> +{
>> +    int i;
>> +    uint32_t *p = (uint32_t *)desc;
>> +
>> +    if (addr & 0x0f) {
>> +        hw_error("ftmac110: Tx desc is not 16-byte aligned!\n"
>> +                 "It's fine in QEMU but the real HW would panic.\n");
>> +    }
>> +
>> +    cpu_physical_memory_read(addr, desc, sizeof(*desc));
>> +
>> +    for (i = 0; i < sizeof(*desc); i += 4) {
>> +        *p = le32_to_cpu(*p);
>> +    }
>> +
>> +    if (desc->buf & 0x1) {
>> +        hw_error("ftmac110: tx buffer is not 16-bit aligned.\n");
>> +    }
>> +}
>> +
>> +static void ftmac110_write_txdesc(hwaddr addr, Ftmac110TXD *desc)
>> +{
>> +    int i;
>> +    uint32_t *p = (uint32_t *)desc;
>> +
>> +    for (i = 0; i < sizeof(*desc); i += 4) {
>> +        *p = cpu_to_le32(*p);
>> +    }
>> +
>> +    cpu_physical_memory_write(addr, desc, sizeof(*desc));
>> +}
>> +
>> +static void ftmac110_update_irq(Ftmac110State *s)
>> +{
>> +    if (s->isr & s->ier) {
>> +        qemu_set_irq(s->irq, 1);
>> +    } else {
>> +        qemu_set_irq(s->irq, 0);
>> +    }
>
> You could save a few lines with:
>
> qemu_set_irq(s->irq, !!(s->isr & s->ier))
>

Got it, thanks

>> +}
>> +
>> +static int ftmac110_can_receive(NetClientState *nc)
>> +{
>> +    Ftmac110State *s = FTMAC110(DO_UPCAST(NICState, nc, nc)->opaque);
>> +    Ftmac110RXD rxd;
>> +    hwaddr off = s->rx_bar + s->rx_idx * sizeof(rxd);
>> +
>> +    if ((s->maccr & (MACCR_RCV_EN | MACCR_RDMA_EN))
>> +            != (MACCR_RCV_EN | MACCR_RDMA_EN)) {
>> +        return 0;
>> +    }
>> +
>> +    ftmac110_read_rxdesc(off, &rxd);
>> +
>> +    return rxd.owner;
>
> cc: Anthony
>
> You are going to have the same problem here that I have with cadence
> GEM where if you block can_recieve based on a SGDMA descriptors state,
> there is no easy way to watch the descriptor for a change (which
> should then call qemu_flush_queued_packets()). This leads to very poor
> performance. I would suggest just returning 1 here and dropping the
> packets with assertion of NO_RXBUF. This also makes behaviour between
> long and short packets, as currently short packet that fit in one
> descriptor return false on can_recieve() which long packets that
> require multiple descs are potentially dropped due to NO_RXBUF.
>
> The alternative is to setup a poll of the rxdesc memory region to
> flush queued packets on change of rxdesc state.
>

Got it, thanks.
It looks to me that it's the answer to the reason why the u-boot would
usually have a long long timeout before receiving the 1st valid packet.

>> +}
>> +
>> +static ssize_t ftmac110_receive(NetClientState *nc,
>> +                                const uint8_t  *buf,
>> +                                size_t          size)
>> +{
>> +    const uint8_t *ptr = buf;
>> +    hwaddr off;
>> +    size_t len;
>> +    Ftmac110RXD rxd;
>> +    Ftmac110State *s = FTMAC110(DO_UPCAST(NICState, nc, nc)->opaque);
>> +    int bcst, mcst, ftl, proto;
>> +
>> +    if ((s->maccr & (MACCR_RCV_EN | MACCR_RDMA_EN))
>> +            != (MACCR_RCV_EN | MACCR_RDMA_EN)) {
>> +        return -1;
>> +    }
>
> Common subexpression with can_receive.
>

Got it, thanks

>> +
>> +    /*
>> +     * Check if it's a long frame. (CRC32 is excluded)
>> +     */
>> +    proto = (buf[12] << 8) | buf[13];
>> +    if (proto == 0x8100) {  /* 802.1Q VLAN */
>> +        ftl = (size > 1518) ? 1 : 0;
>> +    } else {
>> +        ftl = (size > 1514) ? 1 : 0;
>> +    }
>> +    if (ftl) {
>> +        DPRINTF("ftmac110_receive: frame too long, drop it\n");
>> +        return -1;
>> +    }
>> +
>> +    /* if it's a broadcast */
>> +    if ((buf[0] == 0xff) && (buf[1] == 0xff) && (buf[2] == 0xff)
>> +            && (buf[3] == 0xff) && (buf[4] == 0xff) && (buf[5] == 0xff)) {
>> +        bcst = 1;
>> +        if (!(s->maccr & MACCR_RCV_ALL) && !(s->maccr & MACCR_RX_BROADPKT)) {
>> +            DPRINTF("ftmac110_receive: bcst filtered\n");
>> +            return -1;
>> +        }
>> +    } else {
>> +        bcst = 0;
>> +    }
>> +
>> +    /* if it's a multicast */
>> +    if ((buf[0] == 0x01) && (buf[1] == 0x00) && (buf[2] == 0x5e)
>> +        && (buf[3] <= 0x7f)) {
>> +        mcst = 1;
>> +        if (!(s->maccr & MACCR_RCV_ALL) && !(s->maccr & MACCR_RX_MULTIPKT)) {
>> +            int hash;
>> +            if (!(s->maccr & MACCR_HT_MULTI_EN)) {
>> +                DPRINTF("ftmac110_receive: mcst filtered\n");
>> +                return -1;
>> +            }
>> +            hash = ftmac110_mcast_hash(6, buf);
>> +            if (!(s->mhash[hash / 32] & (1 << (hash % 32)))) {
>> +                DPRINTF("ftmac110_receive: mcst filtered\n");
>> +                return -1;
>> +            }
>> +        }
>> +    } else {
>> +        mcst = 0;
>> +    }
>> +
>> +    /* check if the destination matches NIC mac address */
>> +    if (!(s->maccr & MACCR_RCV_ALL) && !bcst && !mcst) {
>> +        if (memcmp(s->conf.macaddr.a, buf, 6)) {
>> +            return -1;
>> +        }
>> +    }
>> +
>> +    while (size > 0) {
>> +        off = s->rx_bar + s->rx_idx * sizeof(rxd);
>> +        ftmac110_read_rxdesc(off, &rxd);
>> +        if (!rxd.owner) {
>> +            s->isr |= ISR_NORXBUF;
>> +            DPRINTF("ftmac110: out of rxd!?\n");
>> +            return -1;
>> +        }
>> +
>> +        if (ptr == buf) {
>> +            rxd.frs = 1;
>> +        } else {
>> +            rxd.frs = 0;
>> +        }
>> +
>> +        len = size > rxd.bufsz ? rxd.bufsz : size;
>> +        cpu_physical_memory_write(rxd.buf, (uint8_t *)ptr, len);
>> +        ptr  += len;
>> +        size -= len;
>> +
>> +        if (size <= 0) {
>> +            rxd.lrs = 1;
>> +        } else {
>> +            rxd.lrs = 0;
>> +        }
>> +
>> +        rxd.len = len;
>> +        rxd.bcast = bcst;
>> +        rxd.mcast = mcst;
>> +        rxd.owner = 0;
>> +
>> +        /* write-back the rx descriptor */
>> +        ftmac110_write_rxdesc(off, &rxd);
>> +
>> +        if (rxd.end) {
>> +            s->rx_idx = 0;
>> +        } else {
>> +            s->rx_idx += 1;
>> +        }
>> +    }
>> +
>> +    /* update interrupt signal */
>> +    s->isr |= ISR_RPKT_OK | ISR_RPKT_FINISH;
>> +    ftmac110_update_irq(s);
>> +
>> +    return (ssize_t)((uint32_t)ptr - (uint32_t)buf);
>> +}
>> +
>> +static void ftmac110_transmit(Ftmac110State *s, uint32_t *bar, uint32_t *idx)
>> +{
>> +    hwaddr off;
>> +    uint8_t *buf;
>> +    int ftl, proto;
>> +    Ftmac110TXD txd;
>> +
>> +    if ((s->maccr & (MACCR_XMT_EN | MACCR_XDMA_EN))
>> +            != (MACCR_XMT_EN | MACCR_XDMA_EN)) {
>> +        return;
>> +    }
>> +
>> +    do {
>> +        off = (*bar) + (*idx) * sizeof(txd);
>> +        ftmac110_read_txdesc(off, &txd);
>> +        if (!txd.owner) {
>> +            s->isr |= ISR_NOTXBUF;
>> +            break;
>> +        }
>> +        if (txd.fts) {
>> +            s->txbuff.len = 0;
>> +        }
>> +        if (txd.len + s->txbuff.len > sizeof(s->txbuff.buf)) {
>> +            hw_error("ftmac110: tx buffer overflow!\n");
>> +            exit(1);
>> +        }
>> +        buf = s->txbuff.buf + s->txbuff.len;
>> +        cpu_physical_memory_read(txd.buf, (uint8_t *)buf, txd.len);
>> +        s->txbuff.len += txd.len;
>> +        /* Check if it's a long frame. (CRC32 is excluded) */
>> +        proto = (s->txbuff.buf[12] << 8) | s->txbuff.buf[13];
>> +        if (proto == 0x8100) {
>> +            ftl = (s->txbuff.len > 1518) ? 1 : 0;
>> +        } else {
>> +            ftl = (s->txbuff.len > 1514) ? 1 : 0;
>> +        }
>> +        if (ftl) {
>> +            hw_error("ftmac110_transmit: frame too long\n");
>> +            exit(1);
>> +        }
>> +        if (txd.lts) {
>> +            if (s->maccr & MACCR_LOOP_EN) {
>> +                ftmac110_receive(&s->nic->nc, s->txbuff.buf, s->txbuff.len);
>> +            } else {
>> +                qemu_send_packet(&s->nic->nc, s->txbuff.buf, s->txbuff.len);
>> +            }
>> +        }
>> +        if (txd.end) {
>> +            *idx = 0;
>> +        } else {
>> +            *idx += 1;
>> +        }
>> +        if (txd.tx2fic) {
>> +            s->isr |= ISR_XPKT_OK;
>> +        }
>> +        if (txd.txic) {
>> +            s->isr |= ISR_XPKT_FINISH;
>> +        }
>> +        txd.owner = 0;
>> +        ftmac110_write_txdesc(off, &txd);
>> +    } while (1);
>> +}
>> +
>> +static void ftmac110_bh(void *opaque)
>> +{
>> +    Ftmac110State *s = FTMAC110(opaque);
>> +
>> +    if (s->tx_bar) {
>> +        ftmac110_transmit(s, &s->tx_bar, &s->tx_idx);
>> +    }
>> +
>> +    ftmac110_update_irq(s);
>> +}
>> +
>> +static uint64_t ftmac110_mem_read(void    *opaque,
>> +                                  hwaddr   addr,
>> +                                  unsigned size)
>> +{
>> +    Ftmac110State *s = FTMAC110(opaque);
>> +    uint32_t rc = 0;
>> +
>> +    switch (addr) {
>> +    case REG_ISR:
>> +        rc = s->isr;
>> +        s->isr = 0;
>> +        ftmac110_update_irq(s);
>> +        break;
>> +    case REG_IMR:
>> +        return s->ier;
>> +    case REG_HMAC:
>> +        return s->conf.macaddr.a[1]
>> +               | (s->conf.macaddr.a[0] << 8);
>> +    case REG_LMAC:
>> +        return s->conf.macaddr.a[5]
>> +               | (s->conf.macaddr.a[4] << 8)
>> +               | (s->conf.macaddr.a[3] << 16)
>> +               | (s->conf.macaddr.a[2] << 24);
>> +    case REG_MHASH0:
>> +        return s->mhash[0];
>> +    case REG_MHASH1:
>> +        return s->mhash[1];
>> +    case REG_TXBAR:
>> +        return s->tx_bar;
>> +    case REG_RXBAR:
>> +        return s->rx_bar;
>> +    case REG_MACCR:
>> +        return s->maccr;
>> +    case REG_MACSR:
>> +        rc = s->macsr;
>> +        s->macsr = 0;
>> +        break;
>> +    case REG_PHYCTRL:
>> +        do {
>> +            uint8_t dev = (s->phycr >> 16) & 0x1f;
>> +            uint8_t reg = (s->phycr >> 21) & 0x1f;
>> +            if (dev != 0) {
>> +                break;
>> +            }
>> +            if (s->phycr_rd) {
>> +                switch (reg) {
>> +                case 0:    /* PHY control register */
>> +                    return s->phycr | 0x1140;
>> +                case 1:    /* PHY status register */
>> +                    return s->phycr | 0x796d;
>> +                case 2:    /* PHY ID 1 register */
>> +                    return s->phycr | 0x0141;
>> +                case 3:    /* PHY ID 2 register */
>> +                    return s->phycr | 0x0cc2;
>> +                case 4:    /* Autonegotiation advertisement register */
>> +                    return s->phycr | 0x0de1;
>> +                case 5:    /* Autonegotiation partner abilities register */
>> +                    return s->phycr | 0x45e1;
>> +                }
>> +            }
>> +        } while (0);
>> +        break;
>> +    case REG_FCR:
>> +        return 0x0000a400;
>> +    case REG_BPR:
>> +        return 0x00000400;
>> +    case REG_TXPKT:
>> +        return s->tx_pkt;
>> +    case REG_RXPKT:
>> +        return s->rx_pkt;
>> +    case REG_RXBCST:
>> +        return s->rx_bcst;
>> +    case REG_RXMCST:
>> +        return s->rx_mcst;
>> +    case REG_RXRUNT:
>> +        return s->rx_runt << 16;
>> +    case REG_RXCRCFTL:
>> +        return (s->rx_crc << 16) | (s->rx_ftl);
>> +    case REG_REV:
>> +        return 0x00000700;
>> +    case REG_FEA:
>> +        return 0x00000007;
>> +    default:
>> +        break;
>> +    }
>> +
>> +    return rc;
>> +}
>> +
>> +static void ftmac110_chip_reset(Ftmac110State *s)
>> +{
>> +    s->isr = 0;
>> +    s->ier = 0;
>> +    s->mhash[0] = 0;
>> +    s->mhash[1] = 0;
>> +    s->tx_bar = 0;
>> +    s->rx_bar = 0;
>> +    s->tx_idx = 0;
>> +    s->rx_idx = 0;
>> +    s->maccr = 0;
>> +    s->macsr = 0;
>> +    s->phycr = 0;
>> +    s->txbuff.len = 0;
>> +    s->rx_pkt = 0;
>> +    s->rx_bcst = 0;
>> +    s->rx_mcst = 0;
>> +    s->rx_runt = 0;
>> +    s->rx_drop = 0;
>> +    s->rx_crc = 0;
>> +    s->rx_ftl = 0;
>> +    s->tx_pkt = 0;
>> +
>
> Theres a lot of code for managing your registers that would be a lot
> shorter if implemented your reigster set as an array.
>

Got it, thanks

>> +    if (s->bh) {
>> +        qemu_bh_cancel(s->bh);
>> +    }
>> +
>> +    ftmac110_update_irq(s);
>> +}
>> +
>> +static void ftmac110_mem_write(void    *opaque,
>> +                               hwaddr   addr,
>> +                               uint64_t val,
>> +                               unsigned size)
>> +{
>> +    Ftmac110State *s = FTMAC110(opaque);
>> +
>> +    switch (addr) {
>> +    case REG_IMR:
>> +        s->ier = (uint32_t)val;
>> +        ftmac110_update_irq(s);
>> +        break;
>> +    case REG_HMAC:
>> +        s->conf.macaddr.a[1] = (val >> 0) & 0xff;
>> +        s->conf.macaddr.a[0] = (val >> 8) & 0xff;
>> +        break;
>> +    case REG_LMAC:
>> +        s->conf.macaddr.a[5] = (val >> 0) & 0xff;
>> +        s->conf.macaddr.a[4] = (val >> 8) & 0xff;
>> +        s->conf.macaddr.a[3] = (val >> 16) & 0xff;
>> +        s->conf.macaddr.a[2] = (val >> 24) & 0xff;
>> +        break;
>> +    case REG_MHASH0:
>> +        s->mhash[0] = (uint32_t)val;
>> +        break;
>> +    case REG_MHASH1:
>> +        s->mhash[1] = (uint32_t)val;
>> +        break;
>> +    case REG_TXBAR:
>> +        s->tx_bar = (uint32_t)val;
>> +        break;
>> +    case REG_RXBAR:
>> +        s->rx_bar = (uint32_t)val;
>> +        break;
>> +    case REG_MACCR:
>> +        s->maccr = (uint32_t)val;
>> +        if (s->maccr & MACCR_SW_RST) {
>> +            ftmac110_chip_reset(s);
>> +            s->maccr &= ~MACCR_SW_RST;
>> +        }
>
> Changes to s->maccr can affect the can_receive() state. You should
> check for queued packets here using qemu_flush_queued_packets().
>

Got it, thanks

> Regards,
> Peter
>
>> +        break;
>> +    case REG_PHYCTRL:
>> +        s->phycr = (uint32_t)val;
>> +        if (s->phycr & PHYCR_MDIORD) {
>> +            s->phycr_rd = 1;
>> +        } else {
>> +            s->phycr_rd = 0;
>> +        }
>> +        s->phycr &= ~(PHYCR_MDIOWR | PHYCR_MDIORD);
>> +        break;
>> +    case REG_TXPD:
>> +        qemu_bh_schedule(s->bh);
>> +        break;
>> +    default:
>> +        break;
>> +    }
>> +}
>> +
>> +static const MemoryRegionOps bus_ops = {
>> +    .read  = ftmac110_mem_read,
>> +    .write = ftmac110_mem_write,
>> +    .endianness = DEVICE_LITTLE_ENDIAN,
>> +    .valid = {
>> +        .min_access_size = 4,
>> +        .max_access_size = 4
>> +    }
>> +};
>> +
>> +static void ftmac110_cleanup(NetClientState *nc)
>> +{
>> +    Ftmac110State *s = FTMAC110(DO_UPCAST(NICState, nc, nc)->opaque);
>> +
>> +    s->nic = NULL;
>> +}
>> +
>> +static NetClientInfo net_ftmac110_info = {
>> +    .type = NET_CLIENT_OPTIONS_KIND_NIC,
>> +    .size = sizeof(NICState),
>> +    .can_receive = ftmac110_can_receive,
>> +    .receive = ftmac110_receive,
>> +    .cleanup = ftmac110_cleanup,
>> +};
>> +
>> +static void ftmac110_reset(DeviceState *ds)
>> +{
>> +    SysBusDevice *busdev = SYS_BUS_DEVICE(ds);
>> +    Ftmac110State *s = FTMAC110(FROM_SYSBUS(Ftmac110State, busdev));
>> +
>> +    ftmac110_chip_reset(s);
>> +}
>> +
>> +static int ftmac110_init1(SysBusDevice *dev)
>> +{
>> +    Ftmac110State *s = FTMAC110(FROM_SYSBUS(Ftmac110State, dev));
>> +
>> +    memory_region_init_io(&s->mmio, &bus_ops, s, TYPE_FTMAC110, 0x1000);
>> +    sysbus_init_mmio(dev, &s->mmio);
>> +    sysbus_init_irq(dev, &s->irq);
>> +
>> +    qemu_macaddr_default_if_unset(&s->conf.macaddr);
>> +    s->nic = qemu_new_nic(&net_ftmac110_info, &s->conf,
>> +                          object_get_typename(OBJECT(dev)), dev->qdev.id, s);
>> +    qemu_format_nic_info_str(&s->nic->nc, s->conf.macaddr.a);
>> +
>> +    s->bh = qemu_bh_new(ftmac110_bh, s);
>> +
>> +    ftmac110_chip_reset(s);
>> +
>> +    return 0;
>> +}
>> +
>> +static const VMStateDescription vmstate_ftmac110 = {
>> +    .name = TYPE_FTMAC110,
>> +    .version_id = 1,
>> +    .minimum_version_id = 1,
>> +    .minimum_version_id_old = 1,
>> +    .fields = (VMStateField[]) {
>> +        VMSTATE_UINT32(ier, Ftmac110State),
>> +        VMSTATE_UINT32(tx_bar, Ftmac110State),
>> +        VMSTATE_UINT32(rx_bar, Ftmac110State),
>> +        VMSTATE_UINT32(tx_idx, Ftmac110State),
>> +        VMSTATE_UINT32(rx_idx, Ftmac110State),
>> +        VMSTATE_UINT32(maccr, Ftmac110State),
>> +        VMSTATE_UINT32(macsr, Ftmac110State),
>> +        VMSTATE_UINT32(phycr, Ftmac110State),
>> +        VMSTATE_UINT32_ARRAY(mhash, Ftmac110State, 2),
>> +        VMSTATE_END_OF_LIST()
>> +    }
>> +};
>> +
>> +static Property ftmac110_properties[] = {
>> +    DEFINE_NIC_PROPERTIES(Ftmac110State, conf),
>> +    DEFINE_PROP_END_OF_LIST(),
>> +};
>> +
>> +static void ftmac110_class_init(ObjectClass *klass, void *data)
>> +{
>> +    DeviceClass *dc = DEVICE_CLASS(klass);
>> +    SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
>> +
>> +    k->init   = ftmac110_init1;
>> +    dc->reset = ftmac110_reset;
>> +    dc->vmsd  = &vmstate_ftmac110;
>> +    dc->props = ftmac110_properties;
>> +}
>> +
>> +static const TypeInfo ftmac110_info = {
>> +    .name           = TYPE_FTMAC110,
>> +    .parent         = TYPE_SYS_BUS_DEVICE,
>> +    .instance_size  = sizeof(Ftmac110State),
>> +    .class_init     = ftmac110_class_init,
>> +};
>> +
>> +static void ftmac110_register_types(void)
>> +{
>> +    type_register_static(&ftmac110_info);
>> +}
>> +
>> +/* Legacy helper function.  Should go away when machine config files are
>> +   implemented.  */
>> +void ftmac110_init(NICInfo *nd, uint32_t base, qemu_irq irq)
>> +{
>> +    DeviceState *dev;
>> +    SysBusDevice *s;
>> +
>> +    qemu_check_nic_model(nd, TYPE_FTMAC110);
>> +    dev = qdev_create(NULL, TYPE_FTMAC110);
>> +    qdev_set_nic_properties(dev, nd);
>> +    qdev_init_nofail(dev);
>> +    s = SYS_BUS_DEVICE(dev);
>> +    sysbus_mmio_map(s, 0, base);
>> +    sysbus_connect_irq(s, 0, irq);
>> +}
>> +
>> +type_init(ftmac110_register_types)
>> diff --git a/hw/arm/ftmac110.h b/hw/arm/ftmac110.h
>> new file mode 100644
>> index 0000000..61f44e7
>> --- /dev/null
>> +++ b/hw/arm/ftmac110.h
>> @@ -0,0 +1,131 @@
>> +/*
>> + * QEMU model of the FTMAC110 Controller
>> + *
>> + * Copyright (C) 2012 Faraday Technology
>> + * Written by Dante Su <dantesu@faraday-tech.com>
>> + *
>> + * This file is licensed under GNU GPL v2+.
>> + */
>> +
>> +#ifndef HW_ARM_FTMAC110_H
>> +#define HW_ARM_FTMAC110_H
>> +
>> +#define REG_ISR             0x00
>> +#define REG_IMR             0x04
>> +#define REG_HMAC            0x08
>> +#define REG_LMAC            0x0c
>> +#define REG_MHASH0          0x10
>> +#define REG_MHASH1          0x14
>> +#define REG_TXPD            0x18
>> +#define REG_RXPD            0x1c
>> +#define REG_TXBAR           0x20
>> +#define REG_RXBAR           0x24
>> +#define REG_ITC             0x28
>> +#define REG_APTC            0x2C
>> +#define REG_DBLAC           0x30
>> +#define REG_REV             0x34
>> +#define REG_FEA             0x38
>> +
>> +#define REG_MACCR           0x88
>> +#define REG_MACSR           0x8C
>> +#define REG_PHYCTRL         0x90
>> +#define REG_PHYDATA         0x94
>> +#define REG_FCR             0x98
>> +#define REG_BPR             0x9c
>> +
>> +#define REG_TXPKT           0xf8
>> +#define REG_RXPKT           0xf4
>> +#define REG_RXBCST          0xec
>> +#define REG_RXMCST          0xf0
>> +#define REG_RXRUNT          0xe0
>> +#define REG_RXCRCFTL        0xe4
>> +
>> +/* interrupt status register */
>> +#define ISR_PHYSTS_CHG      (1UL<<9)
>> +#define ISR_AHB_ERR         (1UL<<8)
>> +#define ISR_RPKT_LOST       (1UL<<7)
>> +#define ISR_RPKT_OK         (1UL<<6)
>> +#define ISR_XPKT_LOST       (1UL<<5)
>> +#define ISR_XPKT_OK         (1UL<<4)
>> +#define ISR_NOTXBUF         (1UL<<3)
>> +#define ISR_XPKT_FINISH     (1UL<<2)
>> +#define ISR_NORXBUF         (1UL<<1)
>> +#define ISR_RPKT_FINISH     (1UL<<0)
>> +
>> +/* MAC control register */
>> +#define MACCR_100M              (1UL<<18)
>> +#define MACCR_RX_BROADPKT       (1UL<<17)
>> +#define MACCR_RX_MULTIPKT       (1UL<<16)
>> +#define MACCR_FULLDUP           (1UL<<15)
>> +#define MACCR_CRC_APD           (1UL<<14)
>> +#define MACCR_RCV_ALL           (1UL<<12)
>> +#define MACCR_RX_FTL            (1UL<<11)
>> +#define MACCR_RX_RUNT           (1UL<<10)
>> +#define MACCR_HT_MULTI_EN       (1UL<<9)
>> +#define MACCR_RCV_EN            (1UL<<8)
>> +#define MACCR_ENRX_IN_HALFTX    (1UL<<6)
>> +#define MACCR_XMT_EN            (1UL<<5)
>> +#define MACCR_CRC_DIS           (1UL<<4)
>> +#define MACCR_LOOP_EN           (1UL<<3)
>> +#define MACCR_SW_RST            (1UL<<2)
>> +#define MACCR_RDMA_EN           (1UL<<1)
>> +#define MACCR_XDMA_EN           (1UL<<0)
>> +
>> +/*
>> + * MDIO
>> + */
>> +#define PHYCR_MDIOWR            (1 << 27)
>> +#define PHYCR_MDIORD            (1 << 26)
>> +
>> +/*
>> + * Tx/Rx descriptors
>> + */
>> +typedef struct Ftmac110RXD {
>> +    /* RXDES0 */
>> +    uint32_t len:11;
>> +    uint32_t rsvd1:5;
>> +    uint32_t mcast:1;
>> +    uint32_t bcast:1;
>> +    uint32_t error:5;
>> +    uint32_t rsvd2:5;
>> +    uint32_t lrs:1;
>> +    uint32_t frs:1;
>> +    uint32_t rsvd3:1;
>> +    uint32_t owner:1;   /* BIT: 31 - 1:HW, 0: SW */
>> +
>> +    /* RXDES1 */
>> +    uint32_t bufsz:11;
>> +    uint32_t rsvd4:20;
>> +    uint32_t end:1;     /* BIT: 31 */
>> +
>> +    /* RXDES2 */
>> +    uint32_t buf;
>> +
>> +    /* RXDES3 */
>> +    void     *skb;
>> +} __attribute__ ((aligned (16))) Ftmac110RXD;
>> +
>> +typedef struct Ftmac110TXD {
>> +    /* TXDES0 */
>> +    uint32_t error:2;
>> +    uint32_t rsvd1:29;
>> +    uint32_t owner:1;   /* BIT: 31 - 1:HW, 0: SW */
>> +
>> +    /* TXDES1 */
>> +    uint32_t len:11;
>> +    uint32_t rsvd2:16;
>> +    uint32_t lts:1;
>> +    uint32_t fts:1;
>> +    uint32_t tx2fic:1;
>> +    uint32_t txic:1;
>> +    uint32_t end:1;     /* BIT: 31 */
>> +
>> +    /* TXDES2 */
>> +    uint32_t buf;
>> +
>> +    /* TXDES3 */
>> +    void     *skb;
>> +
>> +} __attribute__ ((aligned (16))) Ftmac110TXD;
>> +
>> +#endif  /* FTMAC_H */
>> --
>> 1.7.9.5
>>
>>
Peter Crosthwaite Feb. 18, 2013, 7:40 a.m. UTC | #5
On Mon, Feb 18, 2013 at 5:30 PM, Kuo-Jung Su <dantesu@gmail.com> wrote:
> 2013/2/17 Peter Crosthwaite <peter.crosthwaite@xilinx.com>:
>> On Wed, Feb 6, 2013 at 7:45 PM, Kuo-Jung Su <dantesu@gmail.com> wrote:
>>> From: Kuo-Jung Su <dantesu@faraday-tech.com>
>>>
>>> The FTMAC110 is an Ethernet controller that provides AHB master capability
>>> and is in full compliance with the IEEE 802.3 10/100 Mbps specifications.
>>> Its DMA controller handles all data transfers between system memory
>>> and on-chip memories.
>>> It supports half-word data transfer for Linux. However it has a weird DMA
>>> alignment issue:
>>>
>>> (1) Tx DMA Buffer Address:
>>>     1 bytes aligned: Invalid
>>>     2 bytes aligned: O.K
>>>     4 bytes aligned: O.K
>>>
>>> (2) Rx DMA Buffer Address:
>>>     1 bytes aligned: Invalid
>>>     2 bytes aligned: O.K
>>>     4 bytes aligned: Invalid (It means 0x0, 0x4, 0x8, 0xC are invalid)
>>>
>>> Signed-off-by: Kuo-Jung Su <dantesu@faraday-tech.com>
>>> ---
>>>  hw/arm/Makefile.objs  |    1 +
>>>  hw/arm/faraday_a360.c |   10 +
>>>  hw/arm/ftmac110.c     |  681 +++++++++++++++++++++++++++++++++++++++++++++++++
>>>  hw/arm/ftmac110.h     |  131 ++++++++++
>>>  4 files changed, 823 insertions(+)
>>>  create mode 100644 hw/arm/ftmac110.c
>>>  create mode 100644 hw/arm/ftmac110.h
>>>
>>> diff --git a/hw/arm/Makefile.objs b/hw/arm/Makefile.objs
>>> index 70d4f25..f5eeaeb 100644
>>> --- a/hw/arm/Makefile.objs
>>> +++ b/hw/arm/Makefile.objs
>>> @@ -47,3 +47,4 @@ obj-y += ftapbbrg020.o
>>>  obj-y += ftnandc021.o
>>>  obj-y += fti2c010.o
>>>  obj-y += ftssp010.o
>>> +obj-y += ftmac110.o
>>> diff --git a/hw/arm/faraday_a360.c b/hw/arm/faraday_a360.c
>>> index 52cfcec..51e8649 100644
>>> --- a/hw/arm/faraday_a360.c
>>> +++ b/hw/arm/faraday_a360.c
>>> @@ -31,6 +31,7 @@ a360_device_init(A360State *s)
>>>      qemu_irq *pic;
>>>      DeviceState *ds, *fl;
>>>      SSIBus *spi;
>>> +    int done_nic = 0;
>>>      int i, nr_flash;
>>>      qemu_irq cs_line;
>>>      qemu_irq ack, req;
>>> @@ -122,6 +123,15 @@ a360_device_init(A360State *s)
>>>      req = qdev_get_gpio_in(s->pdma[0], 2);
>>>      qdev_connect_gpio_out(s->pdma[0], 2, ack);
>>>      qdev_connect_gpio_out(ds, 1, req);
>>> +
>>> +    /* ftmac110 */
>>> +    for (i = 0; i < nb_nics; i++) {
>>> +        NICInfo *nd = &nd_table[i];
>>> +        if (!done_nic && (!nd->model || strcmp(nd->model, "ftmac110") == 0)) {
>>> +            ftmac110_init(nd, 0x90900000, pic[25]);
>>> +            done_nic = 1;
>>> +        }
>>> +    }
>>>  }
>>>
>>>  static void
>>> diff --git a/hw/arm/ftmac110.c b/hw/arm/ftmac110.c
>>> new file mode 100644
>>> index 0000000..d45f4ba
>>> --- /dev/null
>>> +++ b/hw/arm/ftmac110.c
>>> @@ -0,0 +1,681 @@
>>> +/*
>>> + * QEMU model of the FTMAC110 Controller
>>> + *
>>> + * Copyright (C) 2012 Faraday Technology
>>> + * Written by Dante Su <dantesu@faraday-tech.com>
>>> + *
>>> + * This file is licensed under GNU GPL v2+.
>>> + */
>>> +
>>> +/*******************************************************************/
>>> +/*               FTMAC110 DMA design issue                         */
>>> +/*                                             Dante Su 2010.02.03 */
>>> +/*                                                                 */
>>> +/* The DMA engine has a weird restriction that its Rx DMA engine   */
>>> +/* accepts only 16-bits aligned address, 32-bits aligned is still  */
>>> +/* invalid. However this restriction does not apply to Tx DMA.     */
>>> +/* Conclusion:                                                     */
>>> +/* (1) Tx DMA Buffer Address:                                      */
>>> +/*     1 bytes aligned: Invalid                                    */
>>> +/*     2 bytes aligned: O.K                                        */
>>> +/*     4 bytes aligned: O.K (-> u-boot ZeroCopy is possible)       */
>>> +/* (2) Rx DMA Buffer Address:                                      */
>>> +/*     1 bytes aligned: Invalid                                    */
>>> +/*     2 bytes aligned: O.K                                        */
>>> +/*     4 bytes aligned: Invalid                                    */
>>> +/*******************************************************************/
>>> +
>>
>>> +#include <hw/sysbus.h>
>>> +#include <sysemu/sysemu.h>
>>> +#include <net/net.h>
>>> +
>>> +#include "faraday.h"
>>> +#include "ftmac110.h"
>>> +
>>> +#define TYPE_FTMAC110    "ftmac110"
>>> +
>>> +typedef struct Ftmac110State {
>>> +    SysBusDevice busdev;
>>> +    MemoryRegion mmio;
>>> +
>>> +    QEMUBH *bh;
>>> +    qemu_irq irq;
>>> +    NICState *nic;
>>> +    NICConf conf;
>>> +
>>> +    uint32_t isr;
>>> +    uint32_t ier;
>>> +    uint32_t mhash[2];
>>> +    uint32_t tx_bar;
>>> +    uint32_t rx_bar;
>>> +    uint32_t tx_idx;
>>> +    uint32_t rx_idx;
>>> +    uint32_t maccr;
>>> +    uint32_t macsr;
>>> +    uint32_t phycr;
>>> +    uint32_t phycr_rd;
>>> +
>>> +    struct {
>>> +        uint8_t  buf[2048];
>>
>> Magic number
>>
>
> Now a new macro for Max. Frame Length has been added.
>
>>> +        uint32_t len;
>>> +    } txbuff;
>>> +
>>> +    uint32_t rx_pkt;
>>> +    uint32_t rx_bcst;
>>> +    uint32_t rx_mcst;
>>> +    uint16_t rx_runt;
>>> +    uint16_t rx_drop;
>>> +    uint16_t rx_crc;
>>> +    uint16_t rx_ftl;
>>> +    uint32_t tx_pkt;
>>> +
>>> +} Ftmac110State;
>>> +
>>> +#define FTMAC110(obj) \
>>> +    OBJECT_CHECK(Ftmac110State, obj, TYPE_FTMAC110)
>>> +
>>> +static uint8_t bitrev8(uint8_t v)
>>> +{
>>> +    int i;
>>> +    uint8_t r = 0;
>>> +    for (i = 0; i < 8; ++i) {
>>> +        if (v & (1 << i)) {
>>> +            r |= (1 << (7 - i));
>>> +        }
>>> +    }
>>> +    return r;
>>> +}
>>
>> I would suggest adding this to bitops.[ch] for all the use. This would
>> be a separate patch however.
>>
>
> I'll port the bitrev.[ch] from linux kernel to QEMU.
>
>>> +
>>> +static int ftmac110_mcast_hash(int len, const uint8_t *p)
>>> +{
>>> +#define CRCPOLY_LE 0xedb88320
>>> +    int i;
>>> +    uint32_t crc = 0xFFFFFFFF;
>>> +
>>> +    while (len--) {
>>> +        crc ^= *p++;
>>> +        for (i = 0; i < 8; i++) {
>>> +            crc = (crc >> 1) ^ ((crc & 1) ? CRCPOLY_LE : 0);
>>> +        }
>>> +    }
>>> +
>>> +    /* Reverse CRC32 and return MSB 6 bits only */
>>> +    return bitrev8(crc >> 24) >> 2;
>>> +}
>>> +
>>> +static void ftmac110_read_rxdesc(hwaddr addr, Ftmac110RXD *desc)
>>> +{
>>> +    int i;
>>> +    uint32_t *p = (uint32_t *)desc;
>>> +
>>> +    if (addr & 0x0f) {
>>> +        hw_error("ftmac110: Rx desc is not 16-byte aligned!\n"
>>> +                 "It's fine in QEMU but the real HW would panic.\n");
>>> +    }
>>> +
>>> +    cpu_physical_memory_read(addr, desc, sizeof(*desc));
>>
>> I think its better to use the DMA API.
>>
>> dma_memory_read(&dma_context_memory, addr, desc, sizeof(*desc))
>>
>> cpu_physical_memory_read shouldnt really be used by devices.
>>
>
> Got it, thanks
>
>>> +
>>> +    for (i = 0; i < sizeof(*desc); i += 4) {
>>> +        *p = le32_to_cpu(*p);
>>> +    }
>>> +
>>> +    if ((desc->buf & 0x1) || !(desc->buf % 4)) {
>>> +        hw_error("ftmac110: rx buffer is not exactly 16-bit aligned.\n");
>>
>> Looks like a programmer error. You could use qemu_log_mask(LOG_GUEST_ERROR
>>
>
> Got it, thanks
>
>>> +    }
>>> +}
>>> +
>>> +static void ftmac110_write_rxdesc(hwaddr addr, Ftmac110RXD *desc)
>>> +{
>>> +    int i;
>>> +    uint32_t *p = (uint32_t *)desc;
>>> +
>>> +    for (i = 0; i < sizeof(*desc); i += 4) {
>>> +        *p = cpu_to_le32(*p);
>>> +    }
>>> +
>>> +    cpu_physical_memory_write(addr, desc, sizeof(*desc));
>>
>> Same thing with DMA API
>>
>>> +}
>>> +
>>> +static void ftmac110_read_txdesc(hwaddr addr, Ftmac110TXD *desc)
>>> +{
>>> +    int i;
>>> +    uint32_t *p = (uint32_t *)desc;
>>> +
>>> +    if (addr & 0x0f) {
>>> +        hw_error("ftmac110: Tx desc is not 16-byte aligned!\n"
>>> +                 "It's fine in QEMU but the real HW would panic.\n");
>>> +    }
>>> +
>>> +    cpu_physical_memory_read(addr, desc, sizeof(*desc));
>>> +
>>> +    for (i = 0; i < sizeof(*desc); i += 4) {
>>> +        *p = le32_to_cpu(*p);
>>> +    }
>>> +
>>> +    if (desc->buf & 0x1) {
>>> +        hw_error("ftmac110: tx buffer is not 16-bit aligned.\n");
>>> +    }
>>> +}
>>> +
>>> +static void ftmac110_write_txdesc(hwaddr addr, Ftmac110TXD *desc)
>>> +{
>>> +    int i;
>>> +    uint32_t *p = (uint32_t *)desc;
>>> +
>>> +    for (i = 0; i < sizeof(*desc); i += 4) {
>>> +        *p = cpu_to_le32(*p);
>>> +    }
>>> +
>>> +    cpu_physical_memory_write(addr, desc, sizeof(*desc));
>>> +}
>>> +
>>> +static void ftmac110_update_irq(Ftmac110State *s)
>>> +{
>>> +    if (s->isr & s->ier) {
>>> +        qemu_set_irq(s->irq, 1);
>>> +    } else {
>>> +        qemu_set_irq(s->irq, 0);
>>> +    }
>>
>> You could save a few lines with:
>>
>> qemu_set_irq(s->irq, !!(s->isr & s->ier))
>>
>
> Got it, thanks
>
>>> +}
>>> +
>>> +static int ftmac110_can_receive(NetClientState *nc)
>>> +{
>>> +    Ftmac110State *s = FTMAC110(DO_UPCAST(NICState, nc, nc)->opaque);
>>> +    Ftmac110RXD rxd;
>>> +    hwaddr off = s->rx_bar + s->rx_idx * sizeof(rxd);
>>> +
>>> +    if ((s->maccr & (MACCR_RCV_EN | MACCR_RDMA_EN))
>>> +            != (MACCR_RCV_EN | MACCR_RDMA_EN)) {
>>> +        return 0;
>>> +    }
>>> +
>>> +    ftmac110_read_rxdesc(off, &rxd);
>>> +
>>> +    return rxd.owner;
>>
>> cc: Anthony
>>
>> You are going to have the same problem here that I have with cadence
>> GEM where if you block can_recieve based on a SGDMA descriptors state,
>> there is no easy way to watch the descriptor for a change (which
>> should then call qemu_flush_queued_packets()). This leads to very poor
>> performance. I would suggest just returning 1 here and dropping the
>> packets with assertion of NO_RXBUF. This also makes behaviour between
>> long and short packets, as currently short packet that fit in one
>> descriptor return false on can_recieve() which long packets that
>> require multiple descs are potentially dropped due to NO_RXBUF.
>>
>> The alternative is to setup a poll of the rxdesc memory region to
>> flush queued packets on change of rxdesc state.
>>
>
> Got it, thanks.
> It looks to me that it's the answer to the reason why the u-boot would
> usually have a long long timeout before receiving the 1st valid packet.
>

Yes,

Similar symptoms my end with the current mainline cadence_gem model.
U-boot tftpboot suffers timeouts as it never rxs a packet until it
retries unblocking the 0 return fro can_recieve. I have workarounds in
place at the moment.

Regards,
Peter

>>> +}
>>> +
>>> +static ssize_t ftmac110_receive(NetClientState *nc,
>>> +                                const uint8_t  *buf,
>>> +                                size_t          size)
>>> +{
>>> +    const uint8_t *ptr = buf;
>>> +    hwaddr off;
>>> +    size_t len;
>>> +    Ftmac110RXD rxd;
>>> +    Ftmac110State *s = FTMAC110(DO_UPCAST(NICState, nc, nc)->opaque);
>>> +    int bcst, mcst, ftl, proto;
>>> +
>>> +    if ((s->maccr & (MACCR_RCV_EN | MACCR_RDMA_EN))
>>> +            != (MACCR_RCV_EN | MACCR_RDMA_EN)) {
>>> +        return -1;
>>> +    }
>>
>> Common subexpression with can_receive.
>>
>
> Got it, thanks
>
>>> +
>>> +    /*
>>> +     * Check if it's a long frame. (CRC32 is excluded)
>>> +     */
>>> +    proto = (buf[12] << 8) | buf[13];
>>> +    if (proto == 0x8100) {  /* 802.1Q VLAN */
>>> +        ftl = (size > 1518) ? 1 : 0;
>>> +    } else {
>>> +        ftl = (size > 1514) ? 1 : 0;
>>> +    }
>>> +    if (ftl) {
>>> +        DPRINTF("ftmac110_receive: frame too long, drop it\n");
>>> +        return -1;
>>> +    }
>>> +
>>> +    /* if it's a broadcast */
>>> +    if ((buf[0] == 0xff) && (buf[1] == 0xff) && (buf[2] == 0xff)
>>> +            && (buf[3] == 0xff) && (buf[4] == 0xff) && (buf[5] == 0xff)) {
>>> +        bcst = 1;
>>> +        if (!(s->maccr & MACCR_RCV_ALL) && !(s->maccr & MACCR_RX_BROADPKT)) {
>>> +            DPRINTF("ftmac110_receive: bcst filtered\n");
>>> +            return -1;
>>> +        }
>>> +    } else {
>>> +        bcst = 0;
>>> +    }
>>> +
>>> +    /* if it's a multicast */
>>> +    if ((buf[0] == 0x01) && (buf[1] == 0x00) && (buf[2] == 0x5e)
>>> +        && (buf[3] <= 0x7f)) {
>>> +        mcst = 1;
>>> +        if (!(s->maccr & MACCR_RCV_ALL) && !(s->maccr & MACCR_RX_MULTIPKT)) {
>>> +            int hash;
>>> +            if (!(s->maccr & MACCR_HT_MULTI_EN)) {
>>> +                DPRINTF("ftmac110_receive: mcst filtered\n");
>>> +                return -1;
>>> +            }
>>> +            hash = ftmac110_mcast_hash(6, buf);
>>> +            if (!(s->mhash[hash / 32] & (1 << (hash % 32)))) {
>>> +                DPRINTF("ftmac110_receive: mcst filtered\n");
>>> +                return -1;
>>> +            }
>>> +        }
>>> +    } else {
>>> +        mcst = 0;
>>> +    }
>>> +
>>> +    /* check if the destination matches NIC mac address */
>>> +    if (!(s->maccr & MACCR_RCV_ALL) && !bcst && !mcst) {
>>> +        if (memcmp(s->conf.macaddr.a, buf, 6)) {
>>> +            return -1;
>>> +        }
>>> +    }
>>> +
>>> +    while (size > 0) {
>>> +        off = s->rx_bar + s->rx_idx * sizeof(rxd);
>>> +        ftmac110_read_rxdesc(off, &rxd);
>>> +        if (!rxd.owner) {
>>> +            s->isr |= ISR_NORXBUF;
>>> +            DPRINTF("ftmac110: out of rxd!?\n");
>>> +            return -1;
>>> +        }
>>> +
>>> +        if (ptr == buf) {
>>> +            rxd.frs = 1;
>>> +        } else {
>>> +            rxd.frs = 0;
>>> +        }
>>> +
>>> +        len = size > rxd.bufsz ? rxd.bufsz : size;
>>> +        cpu_physical_memory_write(rxd.buf, (uint8_t *)ptr, len);
>>> +        ptr  += len;
>>> +        size -= len;
>>> +
>>> +        if (size <= 0) {
>>> +            rxd.lrs = 1;
>>> +        } else {
>>> +            rxd.lrs = 0;
>>> +        }
>>> +
>>> +        rxd.len = len;
>>> +        rxd.bcast = bcst;
>>> +        rxd.mcast = mcst;
>>> +        rxd.owner = 0;
>>> +
>>> +        /* write-back the rx descriptor */
>>> +        ftmac110_write_rxdesc(off, &rxd);
>>> +
>>> +        if (rxd.end) {
>>> +            s->rx_idx = 0;
>>> +        } else {
>>> +            s->rx_idx += 1;
>>> +        }
>>> +    }
>>> +
>>> +    /* update interrupt signal */
>>> +    s->isr |= ISR_RPKT_OK | ISR_RPKT_FINISH;
>>> +    ftmac110_update_irq(s);
>>> +
>>> +    return (ssize_t)((uint32_t)ptr - (uint32_t)buf);
>>> +}
>>> +
>>> +static void ftmac110_transmit(Ftmac110State *s, uint32_t *bar, uint32_t *idx)
>>> +{
>>> +    hwaddr off;
>>> +    uint8_t *buf;
>>> +    int ftl, proto;
>>> +    Ftmac110TXD txd;
>>> +
>>> +    if ((s->maccr & (MACCR_XMT_EN | MACCR_XDMA_EN))
>>> +            != (MACCR_XMT_EN | MACCR_XDMA_EN)) {
>>> +        return;
>>> +    }
>>> +
>>> +    do {
>>> +        off = (*bar) + (*idx) * sizeof(txd);
>>> +        ftmac110_read_txdesc(off, &txd);
>>> +        if (!txd.owner) {
>>> +            s->isr |= ISR_NOTXBUF;
>>> +            break;
>>> +        }
>>> +        if (txd.fts) {
>>> +            s->txbuff.len = 0;
>>> +        }
>>> +        if (txd.len + s->txbuff.len > sizeof(s->txbuff.buf)) {
>>> +            hw_error("ftmac110: tx buffer overflow!\n");
>>> +            exit(1);
>>> +        }
>>> +        buf = s->txbuff.buf + s->txbuff.len;
>>> +        cpu_physical_memory_read(txd.buf, (uint8_t *)buf, txd.len);
>>> +        s->txbuff.len += txd.len;
>>> +        /* Check if it's a long frame. (CRC32 is excluded) */
>>> +        proto = (s->txbuff.buf[12] << 8) | s->txbuff.buf[13];
>>> +        if (proto == 0x8100) {
>>> +            ftl = (s->txbuff.len > 1518) ? 1 : 0;
>>> +        } else {
>>> +            ftl = (s->txbuff.len > 1514) ? 1 : 0;
>>> +        }
>>> +        if (ftl) {
>>> +            hw_error("ftmac110_transmit: frame too long\n");
>>> +            exit(1);
>>> +        }
>>> +        if (txd.lts) {
>>> +            if (s->maccr & MACCR_LOOP_EN) {
>>> +                ftmac110_receive(&s->nic->nc, s->txbuff.buf, s->txbuff.len);
>>> +            } else {
>>> +                qemu_send_packet(&s->nic->nc, s->txbuff.buf, s->txbuff.len);
>>> +            }
>>> +        }
>>> +        if (txd.end) {
>>> +            *idx = 0;
>>> +        } else {
>>> +            *idx += 1;
>>> +        }
>>> +        if (txd.tx2fic) {
>>> +            s->isr |= ISR_XPKT_OK;
>>> +        }
>>> +        if (txd.txic) {
>>> +            s->isr |= ISR_XPKT_FINISH;
>>> +        }
>>> +        txd.owner = 0;
>>> +        ftmac110_write_txdesc(off, &txd);
>>> +    } while (1);
>>> +}
>>> +
>>> +static void ftmac110_bh(void *opaque)
>>> +{
>>> +    Ftmac110State *s = FTMAC110(opaque);
>>> +
>>> +    if (s->tx_bar) {
>>> +        ftmac110_transmit(s, &s->tx_bar, &s->tx_idx);
>>> +    }
>>> +
>>> +    ftmac110_update_irq(s);
>>> +}
>>> +
>>> +static uint64_t ftmac110_mem_read(void    *opaque,
>>> +                                  hwaddr   addr,
>>> +                                  unsigned size)
>>> +{
>>> +    Ftmac110State *s = FTMAC110(opaque);
>>> +    uint32_t rc = 0;
>>> +
>>> +    switch (addr) {
>>> +    case REG_ISR:
>>> +        rc = s->isr;
>>> +        s->isr = 0;
>>> +        ftmac110_update_irq(s);
>>> +        break;
>>> +    case REG_IMR:
>>> +        return s->ier;
>>> +    case REG_HMAC:
>>> +        return s->conf.macaddr.a[1]
>>> +               | (s->conf.macaddr.a[0] << 8);
>>> +    case REG_LMAC:
>>> +        return s->conf.macaddr.a[5]
>>> +               | (s->conf.macaddr.a[4] << 8)
>>> +               | (s->conf.macaddr.a[3] << 16)
>>> +               | (s->conf.macaddr.a[2] << 24);
>>> +    case REG_MHASH0:
>>> +        return s->mhash[0];
>>> +    case REG_MHASH1:
>>> +        return s->mhash[1];
>>> +    case REG_TXBAR:
>>> +        return s->tx_bar;
>>> +    case REG_RXBAR:
>>> +        return s->rx_bar;
>>> +    case REG_MACCR:
>>> +        return s->maccr;
>>> +    case REG_MACSR:
>>> +        rc = s->macsr;
>>> +        s->macsr = 0;
>>> +        break;
>>> +    case REG_PHYCTRL:
>>> +        do {
>>> +            uint8_t dev = (s->phycr >> 16) & 0x1f;
>>> +            uint8_t reg = (s->phycr >> 21) & 0x1f;
>>> +            if (dev != 0) {
>>> +                break;
>>> +            }
>>> +            if (s->phycr_rd) {
>>> +                switch (reg) {
>>> +                case 0:    /* PHY control register */
>>> +                    return s->phycr | 0x1140;
>>> +                case 1:    /* PHY status register */
>>> +                    return s->phycr | 0x796d;
>>> +                case 2:    /* PHY ID 1 register */
>>> +                    return s->phycr | 0x0141;
>>> +                case 3:    /* PHY ID 2 register */
>>> +                    return s->phycr | 0x0cc2;
>>> +                case 4:    /* Autonegotiation advertisement register */
>>> +                    return s->phycr | 0x0de1;
>>> +                case 5:    /* Autonegotiation partner abilities register */
>>> +                    return s->phycr | 0x45e1;
>>> +                }
>>> +            }
>>> +        } while (0);
>>> +        break;
>>> +    case REG_FCR:
>>> +        return 0x0000a400;
>>> +    case REG_BPR:
>>> +        return 0x00000400;
>>> +    case REG_TXPKT:
>>> +        return s->tx_pkt;
>>> +    case REG_RXPKT:
>>> +        return s->rx_pkt;
>>> +    case REG_RXBCST:
>>> +        return s->rx_bcst;
>>> +    case REG_RXMCST:
>>> +        return s->rx_mcst;
>>> +    case REG_RXRUNT:
>>> +        return s->rx_runt << 16;
>>> +    case REG_RXCRCFTL:
>>> +        return (s->rx_crc << 16) | (s->rx_ftl);
>>> +    case REG_REV:
>>> +        return 0x00000700;
>>> +    case REG_FEA:
>>> +        return 0x00000007;
>>> +    default:
>>> +        break;
>>> +    }
>>> +
>>> +    return rc;
>>> +}
>>> +
>>> +static void ftmac110_chip_reset(Ftmac110State *s)
>>> +{
>>> +    s->isr = 0;
>>> +    s->ier = 0;
>>> +    s->mhash[0] = 0;
>>> +    s->mhash[1] = 0;
>>> +    s->tx_bar = 0;
>>> +    s->rx_bar = 0;
>>> +    s->tx_idx = 0;
>>> +    s->rx_idx = 0;
>>> +    s->maccr = 0;
>>> +    s->macsr = 0;
>>> +    s->phycr = 0;
>>> +    s->txbuff.len = 0;
>>> +    s->rx_pkt = 0;
>>> +    s->rx_bcst = 0;
>>> +    s->rx_mcst = 0;
>>> +    s->rx_runt = 0;
>>> +    s->rx_drop = 0;
>>> +    s->rx_crc = 0;
>>> +    s->rx_ftl = 0;
>>> +    s->tx_pkt = 0;
>>> +
>>
>> Theres a lot of code for managing your registers that would be a lot
>> shorter if implemented your reigster set as an array.
>>
>
> Got it, thanks
>
>>> +    if (s->bh) {
>>> +        qemu_bh_cancel(s->bh);
>>> +    }
>>> +
>>> +    ftmac110_update_irq(s);
>>> +}
>>> +
>>> +static void ftmac110_mem_write(void    *opaque,
>>> +                               hwaddr   addr,
>>> +                               uint64_t val,
>>> +                               unsigned size)
>>> +{
>>> +    Ftmac110State *s = FTMAC110(opaque);
>>> +
>>> +    switch (addr) {
>>> +    case REG_IMR:
>>> +        s->ier = (uint32_t)val;
>>> +        ftmac110_update_irq(s);
>>> +        break;
>>> +    case REG_HMAC:
>>> +        s->conf.macaddr.a[1] = (val >> 0) & 0xff;
>>> +        s->conf.macaddr.a[0] = (val >> 8) & 0xff;
>>> +        break;
>>> +    case REG_LMAC:
>>> +        s->conf.macaddr.a[5] = (val >> 0) & 0xff;
>>> +        s->conf.macaddr.a[4] = (val >> 8) & 0xff;
>>> +        s->conf.macaddr.a[3] = (val >> 16) & 0xff;
>>> +        s->conf.macaddr.a[2] = (val >> 24) & 0xff;
>>> +        break;
>>> +    case REG_MHASH0:
>>> +        s->mhash[0] = (uint32_t)val;
>>> +        break;
>>> +    case REG_MHASH1:
>>> +        s->mhash[1] = (uint32_t)val;
>>> +        break;
>>> +    case REG_TXBAR:
>>> +        s->tx_bar = (uint32_t)val;
>>> +        break;
>>> +    case REG_RXBAR:
>>> +        s->rx_bar = (uint32_t)val;
>>> +        break;
>>> +    case REG_MACCR:
>>> +        s->maccr = (uint32_t)val;
>>> +        if (s->maccr & MACCR_SW_RST) {
>>> +            ftmac110_chip_reset(s);
>>> +            s->maccr &= ~MACCR_SW_RST;
>>> +        }
>>
>> Changes to s->maccr can affect the can_receive() state. You should
>> check for queued packets here using qemu_flush_queued_packets().
>>
>
> Got it, thanks
>
>> Regards,
>> Peter
>>
>>> +        break;
>>> +    case REG_PHYCTRL:
>>> +        s->phycr = (uint32_t)val;
>>> +        if (s->phycr & PHYCR_MDIORD) {
>>> +            s->phycr_rd = 1;
>>> +        } else {
>>> +            s->phycr_rd = 0;
>>> +        }
>>> +        s->phycr &= ~(PHYCR_MDIOWR | PHYCR_MDIORD);
>>> +        break;
>>> +    case REG_TXPD:
>>> +        qemu_bh_schedule(s->bh);
>>> +        break;
>>> +    default:
>>> +        break;
>>> +    }
>>> +}
>>> +
>>> +static const MemoryRegionOps bus_ops = {
>>> +    .read  = ftmac110_mem_read,
>>> +    .write = ftmac110_mem_write,
>>> +    .endianness = DEVICE_LITTLE_ENDIAN,
>>> +    .valid = {
>>> +        .min_access_size = 4,
>>> +        .max_access_size = 4
>>> +    }
>>> +};
>>> +
>>> +static void ftmac110_cleanup(NetClientState *nc)
>>> +{
>>> +    Ftmac110State *s = FTMAC110(DO_UPCAST(NICState, nc, nc)->opaque);
>>> +
>>> +    s->nic = NULL;
>>> +}
>>> +
>>> +static NetClientInfo net_ftmac110_info = {
>>> +    .type = NET_CLIENT_OPTIONS_KIND_NIC,
>>> +    .size = sizeof(NICState),
>>> +    .can_receive = ftmac110_can_receive,
>>> +    .receive = ftmac110_receive,
>>> +    .cleanup = ftmac110_cleanup,
>>> +};
>>> +
>>> +static void ftmac110_reset(DeviceState *ds)
>>> +{
>>> +    SysBusDevice *busdev = SYS_BUS_DEVICE(ds);
>>> +    Ftmac110State *s = FTMAC110(FROM_SYSBUS(Ftmac110State, busdev));
>>> +
>>> +    ftmac110_chip_reset(s);
>>> +}
>>> +
>>> +static int ftmac110_init1(SysBusDevice *dev)
>>> +{
>>> +    Ftmac110State *s = FTMAC110(FROM_SYSBUS(Ftmac110State, dev));
>>> +
>>> +    memory_region_init_io(&s->mmio, &bus_ops, s, TYPE_FTMAC110, 0x1000);
>>> +    sysbus_init_mmio(dev, &s->mmio);
>>> +    sysbus_init_irq(dev, &s->irq);
>>> +
>>> +    qemu_macaddr_default_if_unset(&s->conf.macaddr);
>>> +    s->nic = qemu_new_nic(&net_ftmac110_info, &s->conf,
>>> +                          object_get_typename(OBJECT(dev)), dev->qdev.id, s);
>>> +    qemu_format_nic_info_str(&s->nic->nc, s->conf.macaddr.a);
>>> +
>>> +    s->bh = qemu_bh_new(ftmac110_bh, s);
>>> +
>>> +    ftmac110_chip_reset(s);
>>> +
>>> +    return 0;
>>> +}
>>> +
>>> +static const VMStateDescription vmstate_ftmac110 = {
>>> +    .name = TYPE_FTMAC110,
>>> +    .version_id = 1,
>>> +    .minimum_version_id = 1,
>>> +    .minimum_version_id_old = 1,
>>> +    .fields = (VMStateField[]) {
>>> +        VMSTATE_UINT32(ier, Ftmac110State),
>>> +        VMSTATE_UINT32(tx_bar, Ftmac110State),
>>> +        VMSTATE_UINT32(rx_bar, Ftmac110State),
>>> +        VMSTATE_UINT32(tx_idx, Ftmac110State),
>>> +        VMSTATE_UINT32(rx_idx, Ftmac110State),
>>> +        VMSTATE_UINT32(maccr, Ftmac110State),
>>> +        VMSTATE_UINT32(macsr, Ftmac110State),
>>> +        VMSTATE_UINT32(phycr, Ftmac110State),
>>> +        VMSTATE_UINT32_ARRAY(mhash, Ftmac110State, 2),
>>> +        VMSTATE_END_OF_LIST()
>>> +    }
>>> +};
>>> +
>>> +static Property ftmac110_properties[] = {
>>> +    DEFINE_NIC_PROPERTIES(Ftmac110State, conf),
>>> +    DEFINE_PROP_END_OF_LIST(),
>>> +};
>>> +
>>> +static void ftmac110_class_init(ObjectClass *klass, void *data)
>>> +{
>>> +    DeviceClass *dc = DEVICE_CLASS(klass);
>>> +    SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
>>> +
>>> +    k->init   = ftmac110_init1;
>>> +    dc->reset = ftmac110_reset;
>>> +    dc->vmsd  = &vmstate_ftmac110;
>>> +    dc->props = ftmac110_properties;
>>> +}
>>> +
>>> +static const TypeInfo ftmac110_info = {
>>> +    .name           = TYPE_FTMAC110,
>>> +    .parent         = TYPE_SYS_BUS_DEVICE,
>>> +    .instance_size  = sizeof(Ftmac110State),
>>> +    .class_init     = ftmac110_class_init,
>>> +};
>>> +
>>> +static void ftmac110_register_types(void)
>>> +{
>>> +    type_register_static(&ftmac110_info);
>>> +}
>>> +
>>> +/* Legacy helper function.  Should go away when machine config files are
>>> +   implemented.  */
>>> +void ftmac110_init(NICInfo *nd, uint32_t base, qemu_irq irq)
>>> +{
>>> +    DeviceState *dev;
>>> +    SysBusDevice *s;
>>> +
>>> +    qemu_check_nic_model(nd, TYPE_FTMAC110);
>>> +    dev = qdev_create(NULL, TYPE_FTMAC110);
>>> +    qdev_set_nic_properties(dev, nd);
>>> +    qdev_init_nofail(dev);
>>> +    s = SYS_BUS_DEVICE(dev);
>>> +    sysbus_mmio_map(s, 0, base);
>>> +    sysbus_connect_irq(s, 0, irq);
>>> +}
>>> +
>>> +type_init(ftmac110_register_types)
>>> diff --git a/hw/arm/ftmac110.h b/hw/arm/ftmac110.h
>>> new file mode 100644
>>> index 0000000..61f44e7
>>> --- /dev/null
>>> +++ b/hw/arm/ftmac110.h
>>> @@ -0,0 +1,131 @@
>>> +/*
>>> + * QEMU model of the FTMAC110 Controller
>>> + *
>>> + * Copyright (C) 2012 Faraday Technology
>>> + * Written by Dante Su <dantesu@faraday-tech.com>
>>> + *
>>> + * This file is licensed under GNU GPL v2+.
>>> + */
>>> +
>>> +#ifndef HW_ARM_FTMAC110_H
>>> +#define HW_ARM_FTMAC110_H
>>> +
>>> +#define REG_ISR             0x00
>>> +#define REG_IMR             0x04
>>> +#define REG_HMAC            0x08
>>> +#define REG_LMAC            0x0c
>>> +#define REG_MHASH0          0x10
>>> +#define REG_MHASH1          0x14
>>> +#define REG_TXPD            0x18
>>> +#define REG_RXPD            0x1c
>>> +#define REG_TXBAR           0x20
>>> +#define REG_RXBAR           0x24
>>> +#define REG_ITC             0x28
>>> +#define REG_APTC            0x2C
>>> +#define REG_DBLAC           0x30
>>> +#define REG_REV             0x34
>>> +#define REG_FEA             0x38
>>> +
>>> +#define REG_MACCR           0x88
>>> +#define REG_MACSR           0x8C
>>> +#define REG_PHYCTRL         0x90
>>> +#define REG_PHYDATA         0x94
>>> +#define REG_FCR             0x98
>>> +#define REG_BPR             0x9c
>>> +
>>> +#define REG_TXPKT           0xf8
>>> +#define REG_RXPKT           0xf4
>>> +#define REG_RXBCST          0xec
>>> +#define REG_RXMCST          0xf0
>>> +#define REG_RXRUNT          0xe0
>>> +#define REG_RXCRCFTL        0xe4
>>> +
>>> +/* interrupt status register */
>>> +#define ISR_PHYSTS_CHG      (1UL<<9)
>>> +#define ISR_AHB_ERR         (1UL<<8)
>>> +#define ISR_RPKT_LOST       (1UL<<7)
>>> +#define ISR_RPKT_OK         (1UL<<6)
>>> +#define ISR_XPKT_LOST       (1UL<<5)
>>> +#define ISR_XPKT_OK         (1UL<<4)
>>> +#define ISR_NOTXBUF         (1UL<<3)
>>> +#define ISR_XPKT_FINISH     (1UL<<2)
>>> +#define ISR_NORXBUF         (1UL<<1)
>>> +#define ISR_RPKT_FINISH     (1UL<<0)
>>> +
>>> +/* MAC control register */
>>> +#define MACCR_100M              (1UL<<18)
>>> +#define MACCR_RX_BROADPKT       (1UL<<17)
>>> +#define MACCR_RX_MULTIPKT       (1UL<<16)
>>> +#define MACCR_FULLDUP           (1UL<<15)
>>> +#define MACCR_CRC_APD           (1UL<<14)
>>> +#define MACCR_RCV_ALL           (1UL<<12)
>>> +#define MACCR_RX_FTL            (1UL<<11)
>>> +#define MACCR_RX_RUNT           (1UL<<10)
>>> +#define MACCR_HT_MULTI_EN       (1UL<<9)
>>> +#define MACCR_RCV_EN            (1UL<<8)
>>> +#define MACCR_ENRX_IN_HALFTX    (1UL<<6)
>>> +#define MACCR_XMT_EN            (1UL<<5)
>>> +#define MACCR_CRC_DIS           (1UL<<4)
>>> +#define MACCR_LOOP_EN           (1UL<<3)
>>> +#define MACCR_SW_RST            (1UL<<2)
>>> +#define MACCR_RDMA_EN           (1UL<<1)
>>> +#define MACCR_XDMA_EN           (1UL<<0)
>>> +
>>> +/*
>>> + * MDIO
>>> + */
>>> +#define PHYCR_MDIOWR            (1 << 27)
>>> +#define PHYCR_MDIORD            (1 << 26)
>>> +
>>> +/*
>>> + * Tx/Rx descriptors
>>> + */
>>> +typedef struct Ftmac110RXD {
>>> +    /* RXDES0 */
>>> +    uint32_t len:11;
>>> +    uint32_t rsvd1:5;
>>> +    uint32_t mcast:1;
>>> +    uint32_t bcast:1;
>>> +    uint32_t error:5;
>>> +    uint32_t rsvd2:5;
>>> +    uint32_t lrs:1;
>>> +    uint32_t frs:1;
>>> +    uint32_t rsvd3:1;
>>> +    uint32_t owner:1;   /* BIT: 31 - 1:HW, 0: SW */
>>> +
>>> +    /* RXDES1 */
>>> +    uint32_t bufsz:11;
>>> +    uint32_t rsvd4:20;
>>> +    uint32_t end:1;     /* BIT: 31 */
>>> +
>>> +    /* RXDES2 */
>>> +    uint32_t buf;
>>> +
>>> +    /* RXDES3 */
>>> +    void     *skb;
>>> +} __attribute__ ((aligned (16))) Ftmac110RXD;
>>> +
>>> +typedef struct Ftmac110TXD {
>>> +    /* TXDES0 */
>>> +    uint32_t error:2;
>>> +    uint32_t rsvd1:29;
>>> +    uint32_t owner:1;   /* BIT: 31 - 1:HW, 0: SW */
>>> +
>>> +    /* TXDES1 */
>>> +    uint32_t len:11;
>>> +    uint32_t rsvd2:16;
>>> +    uint32_t lts:1;
>>> +    uint32_t fts:1;
>>> +    uint32_t tx2fic:1;
>>> +    uint32_t txic:1;
>>> +    uint32_t end:1;     /* BIT: 31 */
>>> +
>>> +    /* TXDES2 */
>>> +    uint32_t buf;
>>> +
>>> +    /* TXDES3 */
>>> +    void     *skb;
>>> +
>>> +} __attribute__ ((aligned (16))) Ftmac110TXD;
>>> +
>>> +#endif  /* FTMAC_H */
>>> --
>>> 1.7.9.5
>>>
>>>
>
>
>
> --
> Best wishes,
> Kuo-Jung Su
>
Stefan Hajnoczi Feb. 18, 2013, 9:36 a.m. UTC | #6
On Wed, Feb 06, 2013 at 05:45:19PM +0800, Kuo-Jung Su wrote:
> From: Kuo-Jung Su <dantesu@faraday-tech.com>
> 
> The FTMAC110 is an Ethernet controller that provides AHB master capability
> and is in full compliance with the IEEE 802.3 10/100 Mbps specifications.
> Its DMA controller handles all data transfers between system memory
> and on-chip memories.
> It supports half-word data transfer for Linux. However it has a weird DMA
> alignment issue:
> 
> (1) Tx DMA Buffer Address:
>     1 bytes aligned: Invalid
>     2 bytes aligned: O.K
>     4 bytes aligned: O.K
> 
> (2) Rx DMA Buffer Address:
>     1 bytes aligned: Invalid
>     2 bytes aligned: O.K
>     4 bytes aligned: Invalid (It means 0x0, 0x4, 0x8, 0xC are invalid)
> 
> Signed-off-by: Kuo-Jung Su <dantesu@faraday-tech.com>
> ---
>  hw/arm/Makefile.objs  |    1 +
>  hw/arm/faraday_a360.c |   10 +
>  hw/arm/ftmac110.c     |  681 +++++++++++++++++++++++++++++++++++++++++++++++++
>  hw/arm/ftmac110.h     |  131 ++++++++++
>  4 files changed, 823 insertions(+)
>  create mode 100644 hw/arm/ftmac110.c
>  create mode 100644 hw/arm/ftmac110.h

Hi Kuo-Jung,
Is there a datasheet and/or driver programming guide for this Ethernet
controller?

Stefan
Kuo-Jung Su Feb. 18, 2013, 9:44 a.m. UTC | #7
2013/2/18 Stefan Hajnoczi <stefanha@gmail.com>:
> On Wed, Feb 06, 2013 at 05:45:19PM +0800, Kuo-Jung Su wrote:
>> From: Kuo-Jung Su <dantesu@faraday-tech.com>
>>
>> The FTMAC110 is an Ethernet controller that provides AHB master capability
>> and is in full compliance with the IEEE 802.3 10/100 Mbps specifications.
>> Its DMA controller handles all data transfers between system memory
>> and on-chip memories.
>> It supports half-word data transfer for Linux. However it has a weird DMA
>> alignment issue:
>>
>> (1) Tx DMA Buffer Address:
>>     1 bytes aligned: Invalid
>>     2 bytes aligned: O.K
>>     4 bytes aligned: O.K
>>
>> (2) Rx DMA Buffer Address:
>>     1 bytes aligned: Invalid
>>     2 bytes aligned: O.K
>>     4 bytes aligned: Invalid (It means 0x0, 0x4, 0x8, 0xC are invalid)
>>
>> Signed-off-by: Kuo-Jung Su <dantesu@faraday-tech.com>
>> ---
>>  hw/arm/Makefile.objs  |    1 +
>>  hw/arm/faraday_a360.c |   10 +
>>  hw/arm/ftmac110.c     |  681 +++++++++++++++++++++++++++++++++++++++++++++++++
>>  hw/arm/ftmac110.h     |  131 ++++++++++
>>  4 files changed, 823 insertions(+)
>>  create mode 100644 hw/arm/ftmac110.c
>>  create mode 100644 hw/arm/ftmac110.h
>
> Hi Kuo-Jung,
> Is there a datasheet and/or driver programming guide for this Ethernet
> controller?
>

The datasheet distribution is prohibited in Faraday, while it's absolutely O.K
to deliver software codes along with register description.

So, I can't share the datasheets with you, but I can add register description
to the source files. Is this what you need?

> Stefan



--
Best wishes,
Kuo-Jung Su
Stefan Hajnoczi Feb. 18, 2013, 2 p.m. UTC | #8
On Mon, Feb 18, 2013 at 05:44:38PM +0800, Kuo-Jung Su wrote:
> 2013/2/18 Stefan Hajnoczi <stefanha@gmail.com>:
> > On Wed, Feb 06, 2013 at 05:45:19PM +0800, Kuo-Jung Su wrote:
> >> From: Kuo-Jung Su <dantesu@faraday-tech.com>
> >>
> >> The FTMAC110 is an Ethernet controller that provides AHB master capability
> >> and is in full compliance with the IEEE 802.3 10/100 Mbps specifications.
> >> Its DMA controller handles all data transfers between system memory
> >> and on-chip memories.
> >> It supports half-word data transfer for Linux. However it has a weird DMA
> >> alignment issue:
> >>
> >> (1) Tx DMA Buffer Address:
> >>     1 bytes aligned: Invalid
> >>     2 bytes aligned: O.K
> >>     4 bytes aligned: O.K
> >>
> >> (2) Rx DMA Buffer Address:
> >>     1 bytes aligned: Invalid
> >>     2 bytes aligned: O.K
> >>     4 bytes aligned: Invalid (It means 0x0, 0x4, 0x8, 0xC are invalid)
> >>
> >> Signed-off-by: Kuo-Jung Su <dantesu@faraday-tech.com>
> >> ---
> >>  hw/arm/Makefile.objs  |    1 +
> >>  hw/arm/faraday_a360.c |   10 +
> >>  hw/arm/ftmac110.c     |  681 +++++++++++++++++++++++++++++++++++++++++++++++++
> >>  hw/arm/ftmac110.h     |  131 ++++++++++
> >>  4 files changed, 823 insertions(+)
> >>  create mode 100644 hw/arm/ftmac110.c
> >>  create mode 100644 hw/arm/ftmac110.h
> >
> > Hi Kuo-Jung,
> > Is there a datasheet and/or driver programming guide for this Ethernet
> > controller?
> >
> 
> The datasheet distribution is prohibited in Faraday, while it's absolutely O.K
> to deliver software codes along with register description.
> 
> So, I can't share the datasheets with you, but I can add register description
> to the source files. Is this what you need?

I'm asking because I'd like to confirm that the device keeps checking
for available receive descriptors via DMA reads (owner bit) and there is
no hw register access to kick the Ethernet controller?

You work at Faraday, so maybe you have the definitive answer to this :).

Stefan
Kuo-Jung Su Feb. 19, 2013, 1:14 a.m. UTC | #9
2013/2/18 Stefan Hajnoczi <stefanha@gmail.com>:
> On Mon, Feb 18, 2013 at 05:44:38PM +0800, Kuo-Jung Su wrote:
>> 2013/2/18 Stefan Hajnoczi <stefanha@gmail.com>:
>> > On Wed, Feb 06, 2013 at 05:45:19PM +0800, Kuo-Jung Su wrote:
>> >> From: Kuo-Jung Su <dantesu@faraday-tech.com>
>> >>
>> >> The FTMAC110 is an Ethernet controller that provides AHB master capability
>> >> and is in full compliance with the IEEE 802.3 10/100 Mbps specifications.
>> >> Its DMA controller handles all data transfers between system memory
>> >> and on-chip memories.
>> >> It supports half-word data transfer for Linux. However it has a weird DMA
>> >> alignment issue:
>> >>
>> >> (1) Tx DMA Buffer Address:
>> >>     1 bytes aligned: Invalid
>> >>     2 bytes aligned: O.K
>> >>     4 bytes aligned: O.K
>> >>
>> >> (2) Rx DMA Buffer Address:
>> >>     1 bytes aligned: Invalid
>> >>     2 bytes aligned: O.K
>> >>     4 bytes aligned: Invalid (It means 0x0, 0x4, 0x8, 0xC are invalid)
>> >>
>> >> Signed-off-by: Kuo-Jung Su <dantesu@faraday-tech.com>
>> >> ---
>> >>  hw/arm/Makefile.objs  |    1 +
>> >>  hw/arm/faraday_a360.c |   10 +
>> >>  hw/arm/ftmac110.c     |  681 +++++++++++++++++++++++++++++++++++++++++++++++++
>> >>  hw/arm/ftmac110.h     |  131 ++++++++++
>> >>  4 files changed, 823 insertions(+)
>> >>  create mode 100644 hw/arm/ftmac110.c
>> >>  create mode 100644 hw/arm/ftmac110.h
>> >
>> > Hi Kuo-Jung,
>> > Is there a datasheet and/or driver programming guide for this Ethernet
>> > controller?
>> >
>>
>> The datasheet distribution is prohibited in Faraday, while it's absolutely O.K
>> to deliver software codes along with register description.
>>
>> So, I can't share the datasheets with you, but I can add register description
>> to the source files. Is this what you need?
>
> I'm asking because I'd like to confirm that the device keeps checking
> for available receive descriptors via DMA reads (owner bit) and there is
> no hw register access to kick the Ethernet controller?
>

In a descriptor based ethernet MAC, there is usually a dedicated
register to kick-off DMA
engine, and of course, FTMAC110/FTGMAC100 has such register, too.

They are:

1. ftgmac100:
    1-1. REG_TXPD (0x18):  Kick-off Tx DMA engine
    1-2. REG_RXPD (0x1c): Kick-off Rx DMA engine
    1-3. REG_HPTXPD (0x28): Kick-off High Priority Tx DMA engine

2. ftmac110:
    2-1. REG_TXPD (0x18):  Kick-off Tx DMA engine
    2-2. REG_RXPD (0x1c): Kick-off Rx DMA engine

Writing an arbitrary value to these registers would trigger the
corresponding DMA engine.

> You work at Faraday, so maybe you have the definitive answer to this :).
>
> Stefan



--
Best wishes,
Kuo-Jung Su
Kuo-Jung Su Feb. 19, 2013, 1:43 a.m. UTC | #10
2013/2/19 Kuo-Jung Su <dantesu@gmail.com>:
> 2013/2/18 Stefan Hajnoczi <stefanha@gmail.com>:
>> On Mon, Feb 18, 2013 at 05:44:38PM +0800, Kuo-Jung Su wrote:
>>> 2013/2/18 Stefan Hajnoczi <stefanha@gmail.com>:
>>> > On Wed, Feb 06, 2013 at 05:45:19PM +0800, Kuo-Jung Su wrote:
>>> >> From: Kuo-Jung Su <dantesu@faraday-tech.com>
>>> >>
>>> >> The FTMAC110 is an Ethernet controller that provides AHB master capability
>>> >> and is in full compliance with the IEEE 802.3 10/100 Mbps specifications.
>>> >> Its DMA controller handles all data transfers between system memory
>>> >> and on-chip memories.
>>> >> It supports half-word data transfer for Linux. However it has a weird DMA
>>> >> alignment issue:
>>> >>
>>> >> (1) Tx DMA Buffer Address:
>>> >>     1 bytes aligned: Invalid
>>> >>     2 bytes aligned: O.K
>>> >>     4 bytes aligned: O.K
>>> >>
>>> >> (2) Rx DMA Buffer Address:
>>> >>     1 bytes aligned: Invalid
>>> >>     2 bytes aligned: O.K
>>> >>     4 bytes aligned: Invalid (It means 0x0, 0x4, 0x8, 0xC are invalid)
>>> >>
>>> >> Signed-off-by: Kuo-Jung Su <dantesu@faraday-tech.com>
>>> >> ---
>>> >>  hw/arm/Makefile.objs  |    1 +
>>> >>  hw/arm/faraday_a360.c |   10 +
>>> >>  hw/arm/ftmac110.c     |  681 +++++++++++++++++++++++++++++++++++++++++++++++++
>>> >>  hw/arm/ftmac110.h     |  131 ++++++++++
>>> >>  4 files changed, 823 insertions(+)
>>> >>  create mode 100644 hw/arm/ftmac110.c
>>> >>  create mode 100644 hw/arm/ftmac110.h
>>> >
>>> > Hi Kuo-Jung,
>>> > Is there a datasheet and/or driver programming guide for this Ethernet
>>> > controller?
>>> >
>>>
>>> The datasheet distribution is prohibited in Faraday, while it's absolutely O.K
>>> to deliver software codes along with register description.
>>>
>>> So, I can't share the datasheets with you, but I can add register description
>>> to the source files. Is this what you need?
>>
>> I'm asking because I'd like to confirm that the device keeps checking
>> for available receive descriptors via DMA reads (owner bit) and there is
>> no hw register access to kick the Ethernet controller?
>>
>
> In a descriptor based ethernet MAC, there is usually a dedicated
> register to kick-off DMA
> engine, and of course, FTMAC110/FTGMAC100 has such register, too.
>
> They are:
>
> 1. ftgmac100:
>     1-1. REG_TXPD (0x18):  Kick-off Tx DMA engine
>     1-2. REG_RXPD (0x1c): Kick-off Rx DMA engine
>     1-3. REG_HPTXPD (0x28): Kick-off High Priority Tx DMA engine
>
> 2. ftmac110:
>     2-1. REG_TXPD (0x18):  Kick-off Tx DMA engine
>     2-2. REG_RXPD (0x1c): Kick-off Rx DMA engine
>
> Writing an arbitrary value to these registers would trigger the
> corresponding DMA engine.
>
>> You work at Faraday, so maybe you have the definitive answer to this :).
>>
>> Stefan

If you're looking for a basic example code, I'll recommend the u-boot drivers,
The files are available from my own github:

https://github.com/dantesu1218/u-boot/blob/master/drivers/net/ftgmac100.c
https://github.com/dantesu1218/u-boot/blob/master/drivers/net/ftmac110.c


>
>
>
> --
> Best wishes,
> Kuo-Jung Su



--
Best wishes,
Kuo-Jung Su
Stefan Hajnoczi Feb. 19, 2013, 1:34 p.m. UTC | #11
On Tue, Feb 19, 2013 at 09:43:27AM +0800, Kuo-Jung Su wrote:
> 2013/2/19 Kuo-Jung Su <dantesu@gmail.com>:
> > 2013/2/18 Stefan Hajnoczi <stefanha@gmail.com>:
> >> On Mon, Feb 18, 2013 at 05:44:38PM +0800, Kuo-Jung Su wrote:
> >>> 2013/2/18 Stefan Hajnoczi <stefanha@gmail.com>:
> >>> > On Wed, Feb 06, 2013 at 05:45:19PM +0800, Kuo-Jung Su wrote:
> >>> >> From: Kuo-Jung Su <dantesu@faraday-tech.com>
> >>> >>
> >>> >> The FTMAC110 is an Ethernet controller that provides AHB master capability
> >>> >> and is in full compliance with the IEEE 802.3 10/100 Mbps specifications.
> >>> >> Its DMA controller handles all data transfers between system memory
> >>> >> and on-chip memories.
> >>> >> It supports half-word data transfer for Linux. However it has a weird DMA
> >>> >> alignment issue:
> >>> >>
> >>> >> (1) Tx DMA Buffer Address:
> >>> >>     1 bytes aligned: Invalid
> >>> >>     2 bytes aligned: O.K
> >>> >>     4 bytes aligned: O.K
> >>> >>
> >>> >> (2) Rx DMA Buffer Address:
> >>> >>     1 bytes aligned: Invalid
> >>> >>     2 bytes aligned: O.K
> >>> >>     4 bytes aligned: Invalid (It means 0x0, 0x4, 0x8, 0xC are invalid)
> >>> >>
> >>> >> Signed-off-by: Kuo-Jung Su <dantesu@faraday-tech.com>
> >>> >> ---
> >>> >>  hw/arm/Makefile.objs  |    1 +
> >>> >>  hw/arm/faraday_a360.c |   10 +
> >>> >>  hw/arm/ftmac110.c     |  681 +++++++++++++++++++++++++++++++++++++++++++++++++
> >>> >>  hw/arm/ftmac110.h     |  131 ++++++++++
> >>> >>  4 files changed, 823 insertions(+)
> >>> >>  create mode 100644 hw/arm/ftmac110.c
> >>> >>  create mode 100644 hw/arm/ftmac110.h
> >>> >
> >>> > Hi Kuo-Jung,
> >>> > Is there a datasheet and/or driver programming guide for this Ethernet
> >>> > controller?
> >>> >
> >>>
> >>> The datasheet distribution is prohibited in Faraday, while it's absolutely O.K
> >>> to deliver software codes along with register description.
> >>>
> >>> So, I can't share the datasheets with you, but I can add register description
> >>> to the source files. Is this what you need?
> >>
> >> I'm asking because I'd like to confirm that the device keeps checking
> >> for available receive descriptors via DMA reads (owner bit) and there is
> >> no hw register access to kick the Ethernet controller?
> >>
> >
> > In a descriptor based ethernet MAC, there is usually a dedicated
> > register to kick-off DMA
> > engine, and of course, FTMAC110/FTGMAC100 has such register, too.
> >
> > They are:
> >
> > 1. ftgmac100:
> >     1-1. REG_TXPD (0x18):  Kick-off Tx DMA engine
> >     1-2. REG_RXPD (0x1c): Kick-off Rx DMA engine
> >     1-3. REG_HPTXPD (0x28): Kick-off High Priority Tx DMA engine
> >
> > 2. ftmac110:
> >     2-1. REG_TXPD (0x18):  Kick-off Tx DMA engine
> >     2-2. REG_RXPD (0x1c): Kick-off Rx DMA engine
> >
> > Writing an arbitrary value to these registers would trigger the
> > corresponding DMA engine.
> >
> >> You work at Faraday, so maybe you have the definitive answer to this :).
> >>
> >> Stefan
> 
> If you're looking for a basic example code, I'll recommend the u-boot drivers,
> The files are available from my own github:
> 
> https://github.com/dantesu1218/u-boot/blob/master/drivers/net/ftgmac100.c
> https://github.com/dantesu1218/u-boot/blob/master/drivers/net/ftmac110.c

Both your u-boot and the Linux driver for ftmac110 do not kick the NIC.
They simply set the hw owner bit in the rx descriptor.

As Peter and Anthony discussed previously, this design does not lend
itself nicely to emulation because QEMU has to poll or try to trap
memory accesses (which we don't do today).

Normally, we'd like to stop host tap device rx when the emulated NIC has
no rx buffers.  When the guest refills rx buffers we can restart rx.
All this happens without polling if the guest kicks the NIC to refill rx
buffers.

I think Peter's suggestion earlier in this thread needs to be used.

Stefan
Kuo-Jung Su Feb. 20, 2013, 1:18 a.m. UTC | #12
2013/2/19 Stefan Hajnoczi <stefanha@gmail.com>:
> On Tue, Feb 19, 2013 at 09:43:27AM +0800, Kuo-Jung Su wrote:
>> 2013/2/19 Kuo-Jung Su <dantesu@gmail.com>:
>> > 2013/2/18 Stefan Hajnoczi <stefanha@gmail.com>:
>> >> On Mon, Feb 18, 2013 at 05:44:38PM +0800, Kuo-Jung Su wrote:
>> >>> 2013/2/18 Stefan Hajnoczi <stefanha@gmail.com>:
>> >>> > On Wed, Feb 06, 2013 at 05:45:19PM +0800, Kuo-Jung Su wrote:
>> >>> >> From: Kuo-Jung Su <dantesu@faraday-tech.com>
>> >>> >>
>> >>> >> The FTMAC110 is an Ethernet controller that provides AHB master capability
>> >>> >> and is in full compliance with the IEEE 802.3 10/100 Mbps specifications.
>> >>> >> Its DMA controller handles all data transfers between system memory
>> >>> >> and on-chip memories.
>> >>> >> It supports half-word data transfer for Linux. However it has a weird DMA
>> >>> >> alignment issue:
>> >>> >>
>> >>> >> (1) Tx DMA Buffer Address:
>> >>> >>     1 bytes aligned: Invalid
>> >>> >>     2 bytes aligned: O.K
>> >>> >>     4 bytes aligned: O.K
>> >>> >>
>> >>> >> (2) Rx DMA Buffer Address:
>> >>> >>     1 bytes aligned: Invalid
>> >>> >>     2 bytes aligned: O.K
>> >>> >>     4 bytes aligned: Invalid (It means 0x0, 0x4, 0x8, 0xC are invalid)
>> >>> >>
>> >>> >> Signed-off-by: Kuo-Jung Su <dantesu@faraday-tech.com>
>> >>> >> ---
>> >>> >>  hw/arm/Makefile.objs  |    1 +
>> >>> >>  hw/arm/faraday_a360.c |   10 +
>> >>> >>  hw/arm/ftmac110.c     |  681 +++++++++++++++++++++++++++++++++++++++++++++++++
>> >>> >>  hw/arm/ftmac110.h     |  131 ++++++++++
>> >>> >>  4 files changed, 823 insertions(+)
>> >>> >>  create mode 100644 hw/arm/ftmac110.c
>> >>> >>  create mode 100644 hw/arm/ftmac110.h
>> >>> >
>> >>> > Hi Kuo-Jung,
>> >>> > Is there a datasheet and/or driver programming guide for this Ethernet
>> >>> > controller?
>> >>> >
>> >>>
>> >>> The datasheet distribution is prohibited in Faraday, while it's absolutely O.K
>> >>> to deliver software codes along with register description.
>> >>>
>> >>> So, I can't share the datasheets with you, but I can add register description
>> >>> to the source files. Is this what you need?
>> >>
>> >> I'm asking because I'd like to confirm that the device keeps checking
>> >> for available receive descriptors via DMA reads (owner bit) and there is
>> >> no hw register access to kick the Ethernet controller?
>> >>
>> >
>> > In a descriptor based ethernet MAC, there is usually a dedicated
>> > register to kick-off DMA
>> > engine, and of course, FTMAC110/FTGMAC100 has such register, too.
>> >
>> > They are:
>> >
>> > 1. ftgmac100:
>> >     1-1. REG_TXPD (0x18):  Kick-off Tx DMA engine
>> >     1-2. REG_RXPD (0x1c): Kick-off Rx DMA engine
>> >     1-3. REG_HPTXPD (0x28): Kick-off High Priority Tx DMA engine
>> >
>> > 2. ftmac110:
>> >     2-1. REG_TXPD (0x18):  Kick-off Tx DMA engine
>> >     2-2. REG_RXPD (0x1c): Kick-off Rx DMA engine
>> >
>> > Writing an arbitrary value to these registers would trigger the
>> > corresponding DMA engine.
>> >
>> >> You work at Faraday, so maybe you have the definitive answer to this :).
>> >>
>> >> Stefan
>>
>> If you're looking for a basic example code, I'll recommend the u-boot drivers,
>> The files are available from my own github:
>>
>> https://github.com/dantesu1218/u-boot/blob/master/drivers/net/ftgmac100.c
>> https://github.com/dantesu1218/u-boot/blob/master/drivers/net/ftmac110.c
>
> Both your u-boot and the Linux driver for ftmac110 do not kick the NIC.
> They simply set the hw owner bit in the rx descriptor.
>

Descriptor based Ethernet MAC usually triggers the Rx DMA by the
hardware MII pin
called CRS (MII Carier Sense), so most of them, only has a Tx Poll
register, no Rx Poll.

For example:

1. BCM963xx builtin Ethernet mac

    http://git.kernel.org/?p=linux/kernel/git/stable/linux-stable.git;a=blob;f=drivers/net/ethernet/broadcom/bcm63xx_enet.c;h=39387d67b7222beee3a5fd122218c645851f90c6;hb=19f949f52599ba7c3f67a5897ac6be14bfcb1200

2. RealTek 8139C+:

    http://git.kernel.org/?p=linux/kernel/git/stable/linux-stable.git;a=blob;f=drivers/net/ethernet/realtek/8139cp.c;h=5ac93323a40cfa40995fbbaa3a4bc5ce0c3bea0a;hb=19f949f52599ba7c3f67a5897ac6be14bfcb1200

3. RDC R6040:

    http://git.kernel.org/?p=linux/kernel/git/stable/linux-stable.git;a=blob;f=drivers/net/ethernet/rdc/r6040.c;h=63c13125db6c9d0a58dcc70438d4af4058bcfd1e;hb=19f949f52599ba7c3f67a5897ac6be14bfcb1200

It's also a mystery to me that why we have a Rx Poll register in
Faraday Ethernet MACs.
They're all designed long before I participate in Faraday. I guess
this rx poll register might be
designed for test purpose only.

> As Peter and Anthony discussed previously, this design does not lend
> itself nicely to emulation because QEMU has to poll or try to trap
> memory accesses (which we don't do today).
>
> Normally, we'd like to stop host tap device rx when the emulated NIC has
> no rx buffers.  When the guest refills rx buffers we can restart rx.
> All this happens without polling if the guest kicks the NIC to refill rx
> buffers.
>
> I think Peter's suggestion earlier in this thread needs to be used.
>
> Stefan



--
Best wishes,
Kuo-Jung Su
diff mbox

Patch

diff --git a/hw/arm/Makefile.objs b/hw/arm/Makefile.objs
index 70d4f25..f5eeaeb 100644
--- a/hw/arm/Makefile.objs
+++ b/hw/arm/Makefile.objs
@@ -47,3 +47,4 @@  obj-y += ftapbbrg020.o
 obj-y += ftnandc021.o
 obj-y += fti2c010.o
 obj-y += ftssp010.o
+obj-y += ftmac110.o
diff --git a/hw/arm/faraday_a360.c b/hw/arm/faraday_a360.c
index 52cfcec..51e8649 100644
--- a/hw/arm/faraday_a360.c
+++ b/hw/arm/faraday_a360.c
@@ -31,6 +31,7 @@  a360_device_init(A360State *s)
     qemu_irq *pic;
     DeviceState *ds, *fl;
     SSIBus *spi;
+    int done_nic = 0;
     int i, nr_flash;
     qemu_irq cs_line;
     qemu_irq ack, req;
@@ -122,6 +123,15 @@  a360_device_init(A360State *s)
     req = qdev_get_gpio_in(s->pdma[0], 2);
     qdev_connect_gpio_out(s->pdma[0], 2, ack);
     qdev_connect_gpio_out(ds, 1, req);
+
+    /* ftmac110 */
+    for (i = 0; i < nb_nics; i++) {
+        NICInfo *nd = &nd_table[i];
+        if (!done_nic && (!nd->model || strcmp(nd->model, "ftmac110") == 0)) {
+            ftmac110_init(nd, 0x90900000, pic[25]);
+            done_nic = 1;
+        }
+    }
 }
 
 static void
diff --git a/hw/arm/ftmac110.c b/hw/arm/ftmac110.c
new file mode 100644
index 0000000..d45f4ba
--- /dev/null
+++ b/hw/arm/ftmac110.c
@@ -0,0 +1,681 @@ 
+/*
+ * QEMU model of the FTMAC110 Controller
+ *
+ * Copyright (C) 2012 Faraday Technology
+ * Written by Dante Su <dantesu@faraday-tech.com>
+ *
+ * This file is licensed under GNU GPL v2+.
+ */
+
+/*******************************************************************/
+/*               FTMAC110 DMA design issue                         */
+/*                                             Dante Su 2010.02.03 */
+/*                                                                 */
+/* The DMA engine has a weird restriction that its Rx DMA engine   */
+/* accepts only 16-bits aligned address, 32-bits aligned is still  */
+/* invalid. However this restriction does not apply to Tx DMA.     */
+/* Conclusion:                                                     */
+/* (1) Tx DMA Buffer Address:                                      */
+/*     1 bytes aligned: Invalid                                    */
+/*     2 bytes aligned: O.K                                        */
+/*     4 bytes aligned: O.K (-> u-boot ZeroCopy is possible)       */
+/* (2) Rx DMA Buffer Address:                                      */
+/*     1 bytes aligned: Invalid                                    */
+/*     2 bytes aligned: O.K                                        */
+/*     4 bytes aligned: Invalid                                    */
+/*******************************************************************/
+
+#include <hw/sysbus.h>
+#include <sysemu/sysemu.h>
+#include <net/net.h>
+
+#include "faraday.h"
+#include "ftmac110.h"
+
+#define TYPE_FTMAC110    "ftmac110"
+
+typedef struct Ftmac110State {
+    SysBusDevice busdev;
+    MemoryRegion mmio;
+
+    QEMUBH *bh;
+    qemu_irq irq;
+    NICState *nic;
+    NICConf conf;
+
+    uint32_t isr;
+    uint32_t ier;
+    uint32_t mhash[2];
+    uint32_t tx_bar;
+    uint32_t rx_bar;
+    uint32_t tx_idx;
+    uint32_t rx_idx;
+    uint32_t maccr;
+    uint32_t macsr;
+    uint32_t phycr;
+    uint32_t phycr_rd;
+
+    struct {
+        uint8_t  buf[2048];
+        uint32_t len;
+    } txbuff;
+
+    uint32_t rx_pkt;
+    uint32_t rx_bcst;
+    uint32_t rx_mcst;
+    uint16_t rx_runt;
+    uint16_t rx_drop;
+    uint16_t rx_crc;
+    uint16_t rx_ftl;
+    uint32_t tx_pkt;
+
+} Ftmac110State;
+
+#define FTMAC110(obj) \
+    OBJECT_CHECK(Ftmac110State, obj, TYPE_FTMAC110)
+
+static uint8_t bitrev8(uint8_t v)
+{
+    int i;
+    uint8_t r = 0;
+    for (i = 0; i < 8; ++i) {
+        if (v & (1 << i)) {
+            r |= (1 << (7 - i));
+        }
+    }
+    return r;
+}
+
+static int ftmac110_mcast_hash(int len, const uint8_t *p)
+{
+#define CRCPOLY_LE 0xedb88320
+    int i;
+    uint32_t crc = 0xFFFFFFFF;
+
+    while (len--) {
+        crc ^= *p++;
+        for (i = 0; i < 8; i++) {
+            crc = (crc >> 1) ^ ((crc & 1) ? CRCPOLY_LE : 0);
+        }
+    }
+
+    /* Reverse CRC32 and return MSB 6 bits only */
+    return bitrev8(crc >> 24) >> 2;
+}
+
+static void ftmac110_read_rxdesc(hwaddr addr, Ftmac110RXD *desc)
+{
+    int i;
+    uint32_t *p = (uint32_t *)desc;
+
+    if (addr & 0x0f) {
+        hw_error("ftmac110: Rx desc is not 16-byte aligned!\n"
+                 "It's fine in QEMU but the real HW would panic.\n");
+    }
+
+    cpu_physical_memory_read(addr, desc, sizeof(*desc));
+
+    for (i = 0; i < sizeof(*desc); i += 4) {
+        *p = le32_to_cpu(*p);
+    }
+
+    if ((desc->buf & 0x1) || !(desc->buf % 4)) {
+        hw_error("ftmac110: rx buffer is not exactly 16-bit aligned.\n");
+    }
+}
+
+static void ftmac110_write_rxdesc(hwaddr addr, Ftmac110RXD *desc)
+{
+    int i;
+    uint32_t *p = (uint32_t *)desc;
+
+    for (i = 0; i < sizeof(*desc); i += 4) {
+        *p = cpu_to_le32(*p);
+    }
+
+    cpu_physical_memory_write(addr, desc, sizeof(*desc));
+}
+
+static void ftmac110_read_txdesc(hwaddr addr, Ftmac110TXD *desc)
+{
+    int i;
+    uint32_t *p = (uint32_t *)desc;
+
+    if (addr & 0x0f) {
+        hw_error("ftmac110: Tx desc is not 16-byte aligned!\n"
+                 "It's fine in QEMU but the real HW would panic.\n");
+    }
+
+    cpu_physical_memory_read(addr, desc, sizeof(*desc));
+
+    for (i = 0; i < sizeof(*desc); i += 4) {
+        *p = le32_to_cpu(*p);
+    }
+
+    if (desc->buf & 0x1) {
+        hw_error("ftmac110: tx buffer is not 16-bit aligned.\n");
+    }
+}
+
+static void ftmac110_write_txdesc(hwaddr addr, Ftmac110TXD *desc)
+{
+    int i;
+    uint32_t *p = (uint32_t *)desc;
+
+    for (i = 0; i < sizeof(*desc); i += 4) {
+        *p = cpu_to_le32(*p);
+    }
+
+    cpu_physical_memory_write(addr, desc, sizeof(*desc));
+}
+
+static void ftmac110_update_irq(Ftmac110State *s)
+{
+    if (s->isr & s->ier) {
+        qemu_set_irq(s->irq, 1);
+    } else {
+        qemu_set_irq(s->irq, 0);
+    }
+}
+
+static int ftmac110_can_receive(NetClientState *nc)
+{
+    Ftmac110State *s = FTMAC110(DO_UPCAST(NICState, nc, nc)->opaque);
+    Ftmac110RXD rxd;
+    hwaddr off = s->rx_bar + s->rx_idx * sizeof(rxd);
+
+    if ((s->maccr & (MACCR_RCV_EN | MACCR_RDMA_EN))
+            != (MACCR_RCV_EN | MACCR_RDMA_EN)) {
+        return 0;
+    }
+
+    ftmac110_read_rxdesc(off, &rxd);
+
+    return rxd.owner;
+}
+
+static ssize_t ftmac110_receive(NetClientState *nc,
+                                const uint8_t  *buf,
+                                size_t          size)
+{
+    const uint8_t *ptr = buf;
+    hwaddr off;
+    size_t len;
+    Ftmac110RXD rxd;
+    Ftmac110State *s = FTMAC110(DO_UPCAST(NICState, nc, nc)->opaque);
+    int bcst, mcst, ftl, proto;
+
+    if ((s->maccr & (MACCR_RCV_EN | MACCR_RDMA_EN))
+            != (MACCR_RCV_EN | MACCR_RDMA_EN)) {
+        return -1;
+    }
+
+    /*
+     * Check if it's a long frame. (CRC32 is excluded)
+     */
+    proto = (buf[12] << 8) | buf[13];
+    if (proto == 0x8100) {  /* 802.1Q VLAN */
+        ftl = (size > 1518) ? 1 : 0;
+    } else {
+        ftl = (size > 1514) ? 1 : 0;
+    }
+    if (ftl) {
+        DPRINTF("ftmac110_receive: frame too long, drop it\n");
+        return -1;
+    }
+
+    /* if it's a broadcast */
+    if ((buf[0] == 0xff) && (buf[1] == 0xff) && (buf[2] == 0xff)
+            && (buf[3] == 0xff) && (buf[4] == 0xff) && (buf[5] == 0xff)) {
+        bcst = 1;
+        if (!(s->maccr & MACCR_RCV_ALL) && !(s->maccr & MACCR_RX_BROADPKT)) {
+            DPRINTF("ftmac110_receive: bcst filtered\n");
+            return -1;
+        }
+    } else {
+        bcst = 0;
+    }
+
+    /* if it's a multicast */
+    if ((buf[0] == 0x01) && (buf[1] == 0x00) && (buf[2] == 0x5e)
+        && (buf[3] <= 0x7f)) {
+        mcst = 1;
+        if (!(s->maccr & MACCR_RCV_ALL) && !(s->maccr & MACCR_RX_MULTIPKT)) {
+            int hash;
+            if (!(s->maccr & MACCR_HT_MULTI_EN)) {
+                DPRINTF("ftmac110_receive: mcst filtered\n");
+                return -1;
+            }
+            hash = ftmac110_mcast_hash(6, buf);
+            if (!(s->mhash[hash / 32] & (1 << (hash % 32)))) {
+                DPRINTF("ftmac110_receive: mcst filtered\n");
+                return -1;
+            }
+        }
+    } else {
+        mcst = 0;
+    }
+
+    /* check if the destination matches NIC mac address */
+    if (!(s->maccr & MACCR_RCV_ALL) && !bcst && !mcst) {
+        if (memcmp(s->conf.macaddr.a, buf, 6)) {
+            return -1;
+        }
+    }
+
+    while (size > 0) {
+        off = s->rx_bar + s->rx_idx * sizeof(rxd);
+        ftmac110_read_rxdesc(off, &rxd);
+        if (!rxd.owner) {
+            s->isr |= ISR_NORXBUF;
+            DPRINTF("ftmac110: out of rxd!?\n");
+            return -1;
+        }
+
+        if (ptr == buf) {
+            rxd.frs = 1;
+        } else {
+            rxd.frs = 0;
+        }
+
+        len = size > rxd.bufsz ? rxd.bufsz : size;
+        cpu_physical_memory_write(rxd.buf, (uint8_t *)ptr, len);
+        ptr  += len;
+        size -= len;
+
+        if (size <= 0) {
+            rxd.lrs = 1;
+        } else {
+            rxd.lrs = 0;
+        }
+
+        rxd.len = len;
+        rxd.bcast = bcst;
+        rxd.mcast = mcst;
+        rxd.owner = 0;
+
+        /* write-back the rx descriptor */
+        ftmac110_write_rxdesc(off, &rxd);
+
+        if (rxd.end) {
+            s->rx_idx = 0;
+        } else {
+            s->rx_idx += 1;
+        }
+    }
+
+    /* update interrupt signal */
+    s->isr |= ISR_RPKT_OK | ISR_RPKT_FINISH;
+    ftmac110_update_irq(s);
+
+    return (ssize_t)((uint32_t)ptr - (uint32_t)buf);
+}
+
+static void ftmac110_transmit(Ftmac110State *s, uint32_t *bar, uint32_t *idx)
+{
+    hwaddr off;
+    uint8_t *buf;
+    int ftl, proto;
+    Ftmac110TXD txd;
+
+    if ((s->maccr & (MACCR_XMT_EN | MACCR_XDMA_EN))
+            != (MACCR_XMT_EN | MACCR_XDMA_EN)) {
+        return;
+    }
+
+    do {
+        off = (*bar) + (*idx) * sizeof(txd);
+        ftmac110_read_txdesc(off, &txd);
+        if (!txd.owner) {
+            s->isr |= ISR_NOTXBUF;
+            break;
+        }
+        if (txd.fts) {
+            s->txbuff.len = 0;
+        }
+        if (txd.len + s->txbuff.len > sizeof(s->txbuff.buf)) {
+            hw_error("ftmac110: tx buffer overflow!\n");
+            exit(1);
+        }
+        buf = s->txbuff.buf + s->txbuff.len;
+        cpu_physical_memory_read(txd.buf, (uint8_t *)buf, txd.len);
+        s->txbuff.len += txd.len;
+        /* Check if it's a long frame. (CRC32 is excluded) */
+        proto = (s->txbuff.buf[12] << 8) | s->txbuff.buf[13];
+        if (proto == 0x8100) {
+            ftl = (s->txbuff.len > 1518) ? 1 : 0;
+        } else {
+            ftl = (s->txbuff.len > 1514) ? 1 : 0;
+        }
+        if (ftl) {
+            hw_error("ftmac110_transmit: frame too long\n");
+            exit(1);
+        }
+        if (txd.lts) {
+            if (s->maccr & MACCR_LOOP_EN) {
+                ftmac110_receive(&s->nic->nc, s->txbuff.buf, s->txbuff.len);
+            } else {
+                qemu_send_packet(&s->nic->nc, s->txbuff.buf, s->txbuff.len);
+            }
+        }
+        if (txd.end) {
+            *idx = 0;
+        } else {
+            *idx += 1;
+        }
+        if (txd.tx2fic) {
+            s->isr |= ISR_XPKT_OK;
+        }
+        if (txd.txic) {
+            s->isr |= ISR_XPKT_FINISH;
+        }
+        txd.owner = 0;
+        ftmac110_write_txdesc(off, &txd);
+    } while (1);
+}
+
+static void ftmac110_bh(void *opaque)
+{
+    Ftmac110State *s = FTMAC110(opaque);
+
+    if (s->tx_bar) {
+        ftmac110_transmit(s, &s->tx_bar, &s->tx_idx);
+    }
+
+    ftmac110_update_irq(s);
+}
+
+static uint64_t ftmac110_mem_read(void    *opaque,
+                                  hwaddr   addr,
+                                  unsigned size)
+{
+    Ftmac110State *s = FTMAC110(opaque);
+    uint32_t rc = 0;
+
+    switch (addr) {
+    case REG_ISR:
+        rc = s->isr;
+        s->isr = 0;
+        ftmac110_update_irq(s);
+        break;
+    case REG_IMR:
+        return s->ier;
+    case REG_HMAC:
+        return s->conf.macaddr.a[1]
+               | (s->conf.macaddr.a[0] << 8);
+    case REG_LMAC:
+        return s->conf.macaddr.a[5]
+               | (s->conf.macaddr.a[4] << 8)
+               | (s->conf.macaddr.a[3] << 16)
+               | (s->conf.macaddr.a[2] << 24);
+    case REG_MHASH0:
+        return s->mhash[0];
+    case REG_MHASH1:
+        return s->mhash[1];
+    case REG_TXBAR:
+        return s->tx_bar;
+    case REG_RXBAR:
+        return s->rx_bar;
+    case REG_MACCR:
+        return s->maccr;
+    case REG_MACSR:
+        rc = s->macsr;
+        s->macsr = 0;
+        break;
+    case REG_PHYCTRL:
+        do {
+            uint8_t dev = (s->phycr >> 16) & 0x1f;
+            uint8_t reg = (s->phycr >> 21) & 0x1f;
+            if (dev != 0) {
+                break;
+            }
+            if (s->phycr_rd) {
+                switch (reg) {
+                case 0:    /* PHY control register */
+                    return s->phycr | 0x1140;
+                case 1:    /* PHY status register */
+                    return s->phycr | 0x796d;
+                case 2:    /* PHY ID 1 register */
+                    return s->phycr | 0x0141;
+                case 3:    /* PHY ID 2 register */
+                    return s->phycr | 0x0cc2;
+                case 4:    /* Autonegotiation advertisement register */
+                    return s->phycr | 0x0de1;
+                case 5:    /* Autonegotiation partner abilities register */
+                    return s->phycr | 0x45e1;
+                }
+            }
+        } while (0);
+        break;
+    case REG_FCR:
+        return 0x0000a400;
+    case REG_BPR:
+        return 0x00000400;
+    case REG_TXPKT:
+        return s->tx_pkt;
+    case REG_RXPKT:
+        return s->rx_pkt;
+    case REG_RXBCST:
+        return s->rx_bcst;
+    case REG_RXMCST:
+        return s->rx_mcst;
+    case REG_RXRUNT:
+        return s->rx_runt << 16;
+    case REG_RXCRCFTL:
+        return (s->rx_crc << 16) | (s->rx_ftl);
+    case REG_REV:
+        return 0x00000700;
+    case REG_FEA:
+        return 0x00000007;
+    default:
+        break;
+    }
+
+    return rc;
+}
+
+static void ftmac110_chip_reset(Ftmac110State *s)
+{
+    s->isr = 0;
+    s->ier = 0;
+    s->mhash[0] = 0;
+    s->mhash[1] = 0;
+    s->tx_bar = 0;
+    s->rx_bar = 0;
+    s->tx_idx = 0;
+    s->rx_idx = 0;
+    s->maccr = 0;
+    s->macsr = 0;
+    s->phycr = 0;
+    s->txbuff.len = 0;
+    s->rx_pkt = 0;
+    s->rx_bcst = 0;
+    s->rx_mcst = 0;
+    s->rx_runt = 0;
+    s->rx_drop = 0;
+    s->rx_crc = 0;
+    s->rx_ftl = 0;
+    s->tx_pkt = 0;
+
+    if (s->bh) {
+        qemu_bh_cancel(s->bh);
+    }
+
+    ftmac110_update_irq(s);
+}
+
+static void ftmac110_mem_write(void    *opaque,
+                               hwaddr   addr,
+                               uint64_t val,
+                               unsigned size)
+{
+    Ftmac110State *s = FTMAC110(opaque);
+
+    switch (addr) {
+    case REG_IMR:
+        s->ier = (uint32_t)val;
+        ftmac110_update_irq(s);
+        break;
+    case REG_HMAC:
+        s->conf.macaddr.a[1] = (val >> 0) & 0xff;
+        s->conf.macaddr.a[0] = (val >> 8) & 0xff;
+        break;
+    case REG_LMAC:
+        s->conf.macaddr.a[5] = (val >> 0) & 0xff;
+        s->conf.macaddr.a[4] = (val >> 8) & 0xff;
+        s->conf.macaddr.a[3] = (val >> 16) & 0xff;
+        s->conf.macaddr.a[2] = (val >> 24) & 0xff;
+        break;
+    case REG_MHASH0:
+        s->mhash[0] = (uint32_t)val;
+        break;
+    case REG_MHASH1:
+        s->mhash[1] = (uint32_t)val;
+        break;
+    case REG_TXBAR:
+        s->tx_bar = (uint32_t)val;
+        break;
+    case REG_RXBAR:
+        s->rx_bar = (uint32_t)val;
+        break;
+    case REG_MACCR:
+        s->maccr = (uint32_t)val;
+        if (s->maccr & MACCR_SW_RST) {
+            ftmac110_chip_reset(s);
+            s->maccr &= ~MACCR_SW_RST;
+        }
+        break;
+    case REG_PHYCTRL:
+        s->phycr = (uint32_t)val;
+        if (s->phycr & PHYCR_MDIORD) {
+            s->phycr_rd = 1;
+        } else {
+            s->phycr_rd = 0;
+        }
+        s->phycr &= ~(PHYCR_MDIOWR | PHYCR_MDIORD);
+        break;
+    case REG_TXPD:
+        qemu_bh_schedule(s->bh);
+        break;
+    default:
+        break;
+    }
+}
+
+static const MemoryRegionOps bus_ops = {
+    .read  = ftmac110_mem_read,
+    .write = ftmac110_mem_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4
+    }
+};
+
+static void ftmac110_cleanup(NetClientState *nc)
+{
+    Ftmac110State *s = FTMAC110(DO_UPCAST(NICState, nc, nc)->opaque);
+
+    s->nic = NULL;
+}
+
+static NetClientInfo net_ftmac110_info = {
+    .type = NET_CLIENT_OPTIONS_KIND_NIC,
+    .size = sizeof(NICState),
+    .can_receive = ftmac110_can_receive,
+    .receive = ftmac110_receive,
+    .cleanup = ftmac110_cleanup,
+};
+
+static void ftmac110_reset(DeviceState *ds)
+{
+    SysBusDevice *busdev = SYS_BUS_DEVICE(ds);
+    Ftmac110State *s = FTMAC110(FROM_SYSBUS(Ftmac110State, busdev));
+
+    ftmac110_chip_reset(s);
+}
+
+static int ftmac110_init1(SysBusDevice *dev)
+{
+    Ftmac110State *s = FTMAC110(FROM_SYSBUS(Ftmac110State, dev));
+
+    memory_region_init_io(&s->mmio, &bus_ops, s, TYPE_FTMAC110, 0x1000);
+    sysbus_init_mmio(dev, &s->mmio);
+    sysbus_init_irq(dev, &s->irq);
+
+    qemu_macaddr_default_if_unset(&s->conf.macaddr);
+    s->nic = qemu_new_nic(&net_ftmac110_info, &s->conf,
+                          object_get_typename(OBJECT(dev)), dev->qdev.id, s);
+    qemu_format_nic_info_str(&s->nic->nc, s->conf.macaddr.a);
+
+    s->bh = qemu_bh_new(ftmac110_bh, s);
+
+    ftmac110_chip_reset(s);
+
+    return 0;
+}
+
+static const VMStateDescription vmstate_ftmac110 = {
+    .name = TYPE_FTMAC110,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .minimum_version_id_old = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT32(ier, Ftmac110State),
+        VMSTATE_UINT32(tx_bar, Ftmac110State),
+        VMSTATE_UINT32(rx_bar, Ftmac110State),
+        VMSTATE_UINT32(tx_idx, Ftmac110State),
+        VMSTATE_UINT32(rx_idx, Ftmac110State),
+        VMSTATE_UINT32(maccr, Ftmac110State),
+        VMSTATE_UINT32(macsr, Ftmac110State),
+        VMSTATE_UINT32(phycr, Ftmac110State),
+        VMSTATE_UINT32_ARRAY(mhash, Ftmac110State, 2),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static Property ftmac110_properties[] = {
+    DEFINE_NIC_PROPERTIES(Ftmac110State, conf),
+    DEFINE_PROP_END_OF_LIST(),
+};
+
+static void ftmac110_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+    k->init   = ftmac110_init1;
+    dc->reset = ftmac110_reset;
+    dc->vmsd  = &vmstate_ftmac110;
+    dc->props = ftmac110_properties;
+}
+
+static const TypeInfo ftmac110_info = {
+    .name           = TYPE_FTMAC110,
+    .parent         = TYPE_SYS_BUS_DEVICE,
+    .instance_size  = sizeof(Ftmac110State),
+    .class_init     = ftmac110_class_init,
+};
+
+static void ftmac110_register_types(void)
+{
+    type_register_static(&ftmac110_info);
+}
+
+/* Legacy helper function.  Should go away when machine config files are
+   implemented.  */
+void ftmac110_init(NICInfo *nd, uint32_t base, qemu_irq irq)
+{
+    DeviceState *dev;
+    SysBusDevice *s;
+
+    qemu_check_nic_model(nd, TYPE_FTMAC110);
+    dev = qdev_create(NULL, TYPE_FTMAC110);
+    qdev_set_nic_properties(dev, nd);
+    qdev_init_nofail(dev);
+    s = SYS_BUS_DEVICE(dev);
+    sysbus_mmio_map(s, 0, base);
+    sysbus_connect_irq(s, 0, irq);
+}
+
+type_init(ftmac110_register_types)
diff --git a/hw/arm/ftmac110.h b/hw/arm/ftmac110.h
new file mode 100644
index 0000000..61f44e7
--- /dev/null
+++ b/hw/arm/ftmac110.h
@@ -0,0 +1,131 @@ 
+/*
+ * QEMU model of the FTMAC110 Controller
+ *
+ * Copyright (C) 2012 Faraday Technology
+ * Written by Dante Su <dantesu@faraday-tech.com>
+ *
+ * This file is licensed under GNU GPL v2+.
+ */
+
+#ifndef HW_ARM_FTMAC110_H
+#define HW_ARM_FTMAC110_H
+
+#define REG_ISR             0x00
+#define REG_IMR             0x04
+#define REG_HMAC            0x08
+#define REG_LMAC            0x0c
+#define REG_MHASH0          0x10
+#define REG_MHASH1          0x14
+#define REG_TXPD            0x18
+#define REG_RXPD            0x1c
+#define REG_TXBAR           0x20
+#define REG_RXBAR           0x24
+#define REG_ITC             0x28
+#define REG_APTC            0x2C
+#define REG_DBLAC           0x30
+#define REG_REV             0x34
+#define REG_FEA             0x38
+
+#define REG_MACCR           0x88
+#define REG_MACSR           0x8C
+#define REG_PHYCTRL         0x90
+#define REG_PHYDATA         0x94
+#define REG_FCR             0x98
+#define REG_BPR             0x9c
+
+#define REG_TXPKT           0xf8
+#define REG_RXPKT           0xf4
+#define REG_RXBCST          0xec
+#define REG_RXMCST          0xf0
+#define REG_RXRUNT          0xe0
+#define REG_RXCRCFTL        0xe4
+
+/* interrupt status register */
+#define ISR_PHYSTS_CHG      (1UL<<9)
+#define ISR_AHB_ERR         (1UL<<8)
+#define ISR_RPKT_LOST       (1UL<<7)
+#define ISR_RPKT_OK         (1UL<<6)
+#define ISR_XPKT_LOST       (1UL<<5)
+#define ISR_XPKT_OK         (1UL<<4)
+#define ISR_NOTXBUF         (1UL<<3)
+#define ISR_XPKT_FINISH     (1UL<<2)
+#define ISR_NORXBUF         (1UL<<1)
+#define ISR_RPKT_FINISH     (1UL<<0)
+
+/* MAC control register */
+#define MACCR_100M              (1UL<<18)
+#define MACCR_RX_BROADPKT       (1UL<<17)
+#define MACCR_RX_MULTIPKT       (1UL<<16)
+#define MACCR_FULLDUP           (1UL<<15)
+#define MACCR_CRC_APD           (1UL<<14)
+#define MACCR_RCV_ALL           (1UL<<12)
+#define MACCR_RX_FTL            (1UL<<11)
+#define MACCR_RX_RUNT           (1UL<<10)
+#define MACCR_HT_MULTI_EN       (1UL<<9)
+#define MACCR_RCV_EN            (1UL<<8)
+#define MACCR_ENRX_IN_HALFTX    (1UL<<6)
+#define MACCR_XMT_EN            (1UL<<5)
+#define MACCR_CRC_DIS           (1UL<<4)
+#define MACCR_LOOP_EN           (1UL<<3)
+#define MACCR_SW_RST            (1UL<<2)
+#define MACCR_RDMA_EN           (1UL<<1)
+#define MACCR_XDMA_EN           (1UL<<0)
+
+/*
+ * MDIO
+ */
+#define PHYCR_MDIOWR            (1 << 27)
+#define PHYCR_MDIORD            (1 << 26)
+
+/*
+ * Tx/Rx descriptors
+ */
+typedef struct Ftmac110RXD {
+    /* RXDES0 */
+    uint32_t len:11;
+    uint32_t rsvd1:5;
+    uint32_t mcast:1;
+    uint32_t bcast:1;
+    uint32_t error:5;
+    uint32_t rsvd2:5;
+    uint32_t lrs:1;
+    uint32_t frs:1;
+    uint32_t rsvd3:1;
+    uint32_t owner:1;   /* BIT: 31 - 1:HW, 0: SW */
+
+    /* RXDES1 */
+    uint32_t bufsz:11;
+    uint32_t rsvd4:20;
+    uint32_t end:1;     /* BIT: 31 */
+
+    /* RXDES2 */
+    uint32_t buf;
+
+    /* RXDES3 */
+    void     *skb;
+} __attribute__ ((aligned (16))) Ftmac110RXD;
+
+typedef struct Ftmac110TXD {
+    /* TXDES0 */
+    uint32_t error:2;
+    uint32_t rsvd1:29;
+    uint32_t owner:1;   /* BIT: 31 - 1:HW, 0: SW */
+
+    /* TXDES1 */
+    uint32_t len:11;
+    uint32_t rsvd2:16;
+    uint32_t lts:1;
+    uint32_t fts:1;
+    uint32_t tx2fic:1;
+    uint32_t txic:1;
+    uint32_t end:1;     /* BIT: 31 */
+
+    /* TXDES2 */
+    uint32_t buf;
+
+    /* TXDES3 */
+    void     *skb;
+
+} __attribute__ ((aligned (16))) Ftmac110TXD;
+
+#endif  /* FTMAC_H */