diff mbox series

[16/20] hw/net: Add Renesas On-chip Ethernet MAC

Message ID 20200827123859.81793-17-ysato@users.sourceforge.jp
State New
Headers show
Series RX target update | expand

Commit Message

Yoshinori Sato Aug. 27, 2020, 12:38 p.m. UTC
Signed-off-by: Yoshinori Sato <ysato@users.sourceforge.jp>
---
 include/hw/net/renesas_eth.h |  57 +++
 hw/net/renesas_eth.c         | 875 +++++++++++++++++++++++++++++++++++
 hw/net/Kconfig               |   5 +
 hw/net/meson.build           |   1 +
 4 files changed, 938 insertions(+)
 create mode 100644 include/hw/net/renesas_eth.h
 create mode 100644 hw/net/renesas_eth.c

Comments

Philippe Mathieu-Daudé Oct. 24, 2020, 9:37 p.m. UTC | #1
Cc'ing Jason Wang, maintainer of network devices.

On 8/27/20 2:38 PM, Yoshinori Sato wrote:
> Signed-off-by: Yoshinori Sato <ysato@users.sourceforge.jp>
> ---
>   include/hw/net/renesas_eth.h |  57 +++
>   hw/net/renesas_eth.c         | 875 +++++++++++++++++++++++++++++++++++
>   hw/net/Kconfig               |   5 +
>   hw/net/meson.build           |   1 +
>   4 files changed, 938 insertions(+)
>   create mode 100644 include/hw/net/renesas_eth.h
>   create mode 100644 hw/net/renesas_eth.c
> 
> diff --git a/include/hw/net/renesas_eth.h b/include/hw/net/renesas_eth.h
> new file mode 100644
> index 0000000000..e0026c6434
> --- /dev/null
> +++ b/include/hw/net/renesas_eth.h
> @@ -0,0 +1,57 @@
> +/*
> + *  Renesas ETHERC / EDMAC
> + *
> + *  Copyright 2019 Yoshinori Sato <ysato@users.sourceforge.jp>
> + *
> + *  This program is free software; you can redistribute it and/or modify
> + *  it under the terms of the GNU General Public License as published by
> + *  the Free Software Foundation; under version 2 of the License.
> + *
> + *  This program is distributed in the hope that it will be useful,
> + *  but WITHOUT ANY WARRANTY; without even the implied warranty of
> + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + *  GNU General Public License for more details.
> + *
> + *  You should have received a copy of the GNU General Public License along
> + *  with this program; if not, see <http://www.gnu.org/licenses/>.
> + *
> + *  Contributions after 2012-01-13 are licensed under the terms of the
> + *  GNU GPL, version 2 or (at your option) any later version.
> + */
> +
> +#include "hw/sysbus.h"
> +#include "net/net.h"
> +#include "hw/net/mdio.h"
> +#include "hw/register.h"
> +#include "hw/clock.h"
> +
> +#define TYPE_RENESAS_ETH "renesas_eth"
> +#define RenesasEth(obj) OBJECT_CHECK(RenesasEthState, (obj), TYPE_RENESAS_ETH)
> +
> +#define RENESAS_ETHERC_R_MAX (0x100 / 4)
> +#define RENESAS_EDMAC_R_MAX  (0x100 / 4)
> +
> +typedef struct RenesasEthState {
> +    SysBusDevice parent_obj;
> +
> +    NICState *nic;
> +    NICConf conf;
> +    MemoryRegion etherc_mem;
> +    MemoryRegion edmac_mem;
> +    qemu_irq irq;
> +
> +    /* ETHERC registers */
> +    RegisterInfo etherc_regs_info[RENESAS_ETHERC_R_MAX];
> +    uint32_t etherc_regs[RENESAS_ETHERC_R_MAX];
> +
> +    /* EDMAC register */
> +    RegisterInfo edmac_regs_info[RENESAS_EDMAC_R_MAX];
> +    uint32_t edmac_regs[RENESAS_EDMAC_R_MAX];
> +
> +    int descsize;
> +    int rcv_bcast;
> +    uint8_t macadr[6];
> +    int link_sta;
> +    MDIOState *mdiodev;
> +    Clock *ick;
> +} RenesasEthState;
> diff --git a/hw/net/renesas_eth.c b/hw/net/renesas_eth.c
> new file mode 100644
> index 0000000000..d5fc2bb30c
> --- /dev/null
> +++ b/hw/net/renesas_eth.c
> @@ -0,0 +1,875 @@
> +/*
> + *  Renesas ETHERC / EDMAC
> + *
> + *  Copyright 2019 Yoshinori Sato <ysato@users.sourceforge.jp>
> + *
> + *  This program is free software; you can redistribute it and/or modify
> + *  it under the terms of the GNU General Public License as published by
> + *  the Free Software Foundation; under version 2 of the License.
> + *
> + *  This program is distributed in the hope that it will be useful,
> + *  but WITHOUT ANY WARRANTY; without even the implied warranty of
> + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + *  GNU General Public License for more details.
> + *
> + *  You should have received a copy of the GNU General Public License along
> + *  with this program; if not, see <http://www.gnu.org/licenses/>.
> + *
> + *  Contributions after 2012-01-13 are licensed under the terms of the
> + *  GNU GPL, version 2 or (at your option) any later version.
> + */
> +
> +#include "qemu/osdep.h"
> +
> +#include "hw/hw.h"
> +#include "sysemu/dma.h"
> +#include "qemu/log.h"
> +#include "hw/qdev-properties.h"
> +#include "hw/qdev-clock.h"
> +#include "net/net.h"
> +#include "sysemu/sysemu.h"
> +#include "hw/irq.h"
> +#include "hw/net/renesas_eth.h"
> +
> +/* ETHERC Registers */
> +REG32(ECMR, 0x00)
> +  FIELD(ECMR, PRM, 0, 1)
> +  FIELD(ECMR, DM, 1, 1)
> +  FIELD(ECMR, RTM, 2, 1)
> +  FIELD(ECMR, ILB, 3, 1)
> +  FIELD(ECMR, TE, 5, 1)
> +  FIELD(ECMR, RE, 6, 1)
> +  FIELD(ECMR, MPDE, 9, 1)
> +  FIELD(ECMR, PRCREF, 12, 1)
> +  FIELD(ECMR, TXF, 16, 1)
> +  FIELD(ECMR, RXF, 17, 1)
> +  FIELD(ECMR, PFR, 18, 1)
> +  FIELD(ECMR, ZPF, 19, 1)
> +  FIELD(ECMR, TPC, 20, 1)
> +REG32(RFLR, 0x08)
> +  FIELD(RFLR, RFL, 0, 12)
> +REG32(ECSR, 0x10)
> +  FIELD(ECSR, ICD, 0, 1)
> +  FIELD(ECSR, MPD, 1, 1)
> +  FIELD(ECSR, LCHNG, 2, 1)
> +  FIELD(ECSR, PSRTO, 4, 1)
> +  FIELD(ECSR, BFR, 5, 1)
> +REG32(ECSIPR, 0x18)
> +  FIELD(ECSIPR, ICDIP, 0, 1)
> +  FIELD(ECSIPR, MPDIP, 1, 1)
> +  FIELD(ECSIPR, LCHNGIP, 2, 1)
> +  FIELD(ECSIPR, PSRTOIP, 4, 1)
> +  FIELD(ECSIPR, BFSIPR, 5, 1)
> +REG32(PIR, 0x20)
> +  FIELD(PIR, MDC, 0, 1)
> +  FIELD(PIR, MMD, 1, 1)
> +  FIELD(PIR, MDO, 2, 1)
> +  FIELD(PIR, MDI, 3, 1)
> +REG32(PSR, 0x28)
> +  FIELD(PSR, LMON, 0, 1)
> +REG32(RDMLR, 0x40)
> +  FIELD(RDMLR, RMD, 0, 20)
> +REG32(IPGR, 0x50)
> +  FIELD(IPGR, IPG, 0, 5)
> +REG32(APR, 0x54)
> +  FIELD(APR, AP, 0, 16)
> +REG32(MPR, 0x58)
> +  FIELD(MPR, MP, 0, 16)
> +REG32(RFCF, 0x60)
> +  FIELD(RFCF, RPAUSE, 0, 8)
> +REG32(TPAUSER, 0x64)
> +REG32(TPAUSECR, 0x68)
> +  FIELD(TPAUSECR, TXP, 0, 8)
> +  FIELD(TPAUSER, TPAUSE, 0, 16)
> +REG32(BCFRR, 0x6c)
> +  FIELD(BCFRR, BCF, 0, 16)
> +REG32(MAHR, 0xc0)
> +  FIELD(MAHR, MA, 0, 32)
> +REG32(MALR, 0xc8)
> +  FIELD(MALR, MA, 0, 16)
> +REG32(TROCR, 0xd0)
> +REG32(CDCR, 0xd4)
> +REG32(LCCR, 0xd8)
> +REG32(CNDCR, 0xdc)
> +REG32(CEFCR, 0xe4)
> +REG32(FRECR, 0xe8)
> +REG32(TSFRCR, 0xec)
> +REG32(TLFRCR, 0xf0)
> +REG32(RFCR, 0xf4)
> +REG32(MAFCR, 0xf8)
> +
> +/* EDMAC register */
> +REG32(EDMR, 0x00)
> +  FIELD(EDMR, SWR, 0, 1)
> +  FIELD(EDMR, DL, 4, 2)
> +  FIELD(EDMR, DE, 6, 1)
> +REG32(EDTRR, 0x08)
> +  FIELD(EDTRR, TR, 0, 1)
> +REG32(EDRRR, 0x10)
> +  FIELD(EDRRR, RR, 0, 1)
> +REG32(TDLAR, 0x18)
> +REG32(RDLAR, 0x20)
> +REG32(EESR, 0x28)
> +  FIELD(EESR, CERF, 0, 1)
> +  FIELD(EESR, PRE,  1, 1)
> +  FIELD(EESR, RTSF, 2, 1)
> +  FIELD(EESR, RTLF, 3, 1)
> +  FIELD(EESR, RRF,  4, 1)
> +  FIELD(EESR, RMAF, 7, 1)
> +  FIELD(EESR, TRO,  8, 1)
> +  FIELD(EESR, CD,   9, 1)
> +  FIELD(EESR, RDESC, 0, 10)
> +  FIELD(EESR, DLC,  10, 1)
> +  FIELD(EESR, CND,  11, 1)
> +  FIELD(EESR, RDOF, 16, 1)
> +  FIELD(EESR, RDE,  17, 1)
> +  FIELD(EESR, FR,   18, 1)
> +  FIELD(EESR, TFUF, 19, 1)
> +  FIELD(EESR, TDE,  20, 1)
> +  FIELD(EESR, TC,   21, 1)
> +  FIELD(EESR, ECI,  22, 1)
> +  FIELD(EESR, ADE,  23, 1)
> +  FIELD(EESR, RFCOF, 24, 1)
> +  FIELD(EESR, RABT, 25, 1)
> +  FIELD(EESR, TABT, 26, 1)
> +  FIELD(EESR, TWB,  30, 1)
> +REG32(EESIPR, 0x30)
> +  FIELD(EESIPR, CERFIP, 0, 1)
> +  FIELD(EESIPR, PREIP,  1, 1)
> +  FIELD(EESIPR, RTSFIP, 2, 1)
> +  FIELD(EESIPR, RTLFIP, 3, 1)
> +  FIELD(EESIPR, RRFIP,  4, 1)
> +  FIELD(EESIPR, RMAFIP, 7, 1)
> +  FIELD(EESIPR, TROIP,  8, 1)
> +  FIELD(EESIPR, CDIP,   9, 1)
> +  FIELD(EESIPR, DLCIP,  10, 1)
> +  FIELD(EESIPR, CNDIP,  11, 1)
> +  FIELD(EESIPR, RDOFIP, 16, 1)
> +  FIELD(EESIPR, RDEIP,  17, 1)
> +  FIELD(EESIPR, FRIP,   18, 1)
> +  FIELD(EESIPR, TFUFIP, 19, 1)
> +  FIELD(EESIPR, TDEIP,  20, 1)
> +  FIELD(EESIPR, TCIP,   21, 1)
> +  FIELD(EESIPR, ECIIP,  22, 1)
> +  FIELD(EESIPR, ADEIP,  23, 1)
> +  FIELD(EESIPR, RFCOFIP, 24, 1)
> +  FIELD(EESIPR, RABTIP, 25, 1)
> +  FIELD(EESIPR, TABTIP, 26, 1)
> +  FIELD(EESIPR, TWBIP,  30, 1)
> +REG32(TRSCER, 0x38)
> +  FIELD(TRSCER, RRFCE, 4, 1)
> +  FIELD(TRSCER, RMAFCE, 7, 1)
> +REG32(RMFCR, 0x40)
> +  FIELD(RMFCR, MFC, 0, 16)
> +REG32(TFTR, 0x48)
> +  FIELD(TFTR, TFT, 0, 11)
> +REG32(FDR, 0x50)
> +  FIELD(FDR, RFD, 0, 5)
> +  FIELD(FDR, TFD, 8, 5)
> +REG32(RMCR, 0x58)
> +  FIELD(RMCR, RNR, 0, 1)
> +  FIELD(RMCR, RNC, 1, 1)
> +REG32(TFUCR, 0x64)
> +  FIELD(TFUCR, UNDER, 0, 16)
> +REG32(RFOCR, 0x68)
> +  FIELD(RFOCR, OVER, 0, 16)
> +REG32(IOSR, 0x6c)
> +  FIELD(IOSR, ELB, 0, 1);
> +REG32(FCFTR, 0x70)
> +  FIELD(FCFTR, RFDO, 0, 3)
> +  FIELD(FCFTR, RFFO, 16, 3)
> +REG32(RPADIR, 0x78)
> +  FIELD(RPADIR, PADR, 0, 6)
> +  FIELD(RPADIR, PADS, 16, 2)
> +REG32(TRIMD, 0x7c)
> +  FIELD(TRIMD, TIS, 0, 1)
> +  FIELD(TRIMD, TIM, 4, 1)
> +REG32(RBWAR, 0xc8)
> +REG32(RDFAR, 0xcc)
> +REG32(TBRAR, 0xd4)
> +REG32(TDFAR, 0xd8)
> +
> +/* Transmit Descriptor */
> +REG32(TD0, 0x0000)
> +  FIELD(TD0, TFS0, 0, 1)
> +  FIELD(TD0, TFS1, 1, 1)
> +  FIELD(TD0, TFS2, 2, 1)
> +  FIELD(TD0, TFS3, 3, 1)
> +  FIELD(TD0, TFS8, 8, 1)
> +  FIELD(TD0, TWBI, 26, 1)
> +  FIELD(TD0, TFE,  27, 1)
> +  FIELD(TD0, TFP,  28, 2)
> +  FIELD(TD0, TDLE, 30, 1)
> +  FIELD(TD0, TACT, 31, 1)
> +REG32(TD1, 0x0004)
> +  FIELD(TD1, TBL, 16, 16)
> +REG32(TD2, 0x0008)
> +  FIELD(TD2, TBA, 0, 32)
> +
> +/* Receive Descriptor */
> +REG32(RD0, 0x0000)
> +  FIELD(RD0, RFS,  0, 10)
> +    FIELD(RD0, RFS0, 0, 1)
> +    FIELD(RD0, RFS1, 1, 1)
> +    FIELD(RD0, RFS2, 2, 1)
> +    FIELD(RD0, RFS3, 3, 1)
> +    FIELD(RD0, RFS4, 4, 1)
> +    FIELD(RD0, RFS7, 7, 1)
> +    FIELD(RD0, RFS8, 8, 1)
> +    FIELD(RD0, RFS9, 9, 1)
> +  FIELD(RD0, RFE,  27, 1)
> +  FIELD(RD0, RFP,  28, 2)
> +  FIELD(RD0, RFP0, 28, 1)
> +  FIELD(RD0, RDLE, 30, 1)
> +  FIELD(RD0, RACT, 31, 1)
> +REG32(RD1, 0x0004)
> +  FIELD(RD1, RFL, 0, 16)
> +  FIELD(RD1, RBL, 16, 16)
> +REG32(RD2, 0x0008)
> +  FIELD(RD2, RBA, 0, 32)
> +
> +static void renesas_eth_set_irq(RenesasEthState *s)
> +{
> +    if (s->edmac_regs[R_EESR] & s->edmac_regs[R_EESIPR]) {
> +        qemu_set_irq(s->irq, 1);
> +    } else {
> +        qemu_set_irq(s->irq, 0);
> +    }
> +}
> +
> +static bool renesas_eth_can_receive(NetClientState *nc)
> +{
> +    RenesasEthState *s = RenesasEth(qemu_get_nic_opaque(nc));
> +
> +    return FIELD_EX32(s->edmac_regs[R_EDRRR], EDRRR, RR);
> +}
> +
> +static void set_ecsr(RenesasEthState *s, int bit)
> +{
> +    s->etherc_regs[R_ECSR] = deposit32(s->etherc_regs[R_ECSR], bit, 1, 1);
> +    if (s->etherc_regs[R_ECSR] & s->etherc_regs[R_ECSIPR]) {
> +        s->edmac_regs[R_EESR] = FIELD_DP32(s->edmac_regs[R_EESR],
> +                                           EESR, ECI, 1);
> +    }
> +    renesas_eth_set_irq(s);
> +}
> +
> +static void renesas_eth_set_link_status(NetClientState *nc)
> +{
> +    RenesasEthState *s = RenesasEth(qemu_get_nic_opaque(nc));
> +    int old_lmon, new_lmon;
> +    if (s->mdiodev) {
> +        old_lmon = mdio_phy_linksta(mdio_get_phy(s->mdiodev));
> +        mdio_phy_set_link(mdio_get_phy(s->mdiodev), !nc->link_down);
> +        new_lmon = mdio_phy_linksta(mdio_get_phy(s->mdiodev));
> +        if (old_lmon ^ new_lmon) {
> +            set_ecsr(s, R_ECSR_LCHNG_SHIFT);
> +        }
> +    }
> +}
> +
> +static void edmac_write(RenesasEthState *s, const uint8_t *buf,
> +                        size_t size, int pad)
> +{
> +    uint32_t rdesc[3];
> +    uint32_t eesr;
> +    int state = 0;
> +
> +    while (size > 0) {
> +        size_t wsize;
> +        /* RDESC read */
> +        dma_memory_read(&address_space_memory,
> +                        s->edmac_regs[R_RDFAR], rdesc, sizeof(rdesc));
> +        if (FIELD_EX32(rdesc[0], RD0, RACT)) {
> +            if (state == 0) {
> +                /* Fist block */
> +                rdesc[0] = FIELD_DP32(rdesc[0], RD0, RFP, 2);
> +            }
> +            state++;
> +            s->edmac_regs[R_RBWAR] = rdesc[2];
> +            wsize = MIN(FIELD_EX32(rdesc[1], RD1, RBL), size);
> +            /* Write receive data */
> +            dma_memory_write(&address_space_memory,
> +                             s->edmac_regs[R_RBWAR], buf, wsize);
> +            buf += wsize;
> +            size -= wsize;
> +            rdesc[1] = FIELD_DP32(rdesc[1], RD1, RFL, wsize);
> +            if (size == 0) {
> +                /* Last descriptor */
> +                rdesc[0] = FIELD_DP32(rdesc[0], RD0, RFP0, 1);
> +                if (FIELD_EX32(s->edmac_regs[R_RMCR], RMCR, RNR) == 0) {
> +                    s->edmac_regs[R_EDRRR] = FIELD_DP32(s->edmac_regs[R_EDRRR],
> +                                                  EDRRR, RR, 0);
> +                }
> +                s->edmac_regs[R_EESR] = FIELD_DP32(s->edmac_regs[R_EESR],
> +                                                   EESR, FR, 1);
> +                renesas_eth_set_irq(s);
> +            }
> +            eesr = FIELD_EX32(s->edmac_regs[R_EESR], EESR, RDESC);
> +            rdesc[0] = FIELD_DP32(rdesc[0], RD0, RFS,
> +                                  eesr & ~(s->edmac_regs[R_TRSCER]));
> +            rdesc[0] = FIELD_DP32(rdesc[0], RD0, RFE, eesr != 0);
> +            rdesc[0] = FIELD_DP32(rdesc[0], RD0, RACT, 0);
> +            /* RDESC write back */
> +            dma_memory_write(&address_space_memory,
> +                             s->edmac_regs[R_RDFAR], rdesc, sizeof(rdesc));
> +            if (FIELD_EX32(rdesc[0], RD0, RDLE)) {
> +                s->edmac_regs[R_RDFAR] = s->edmac_regs[R_RDLAR];
> +            } else {
> +                s->edmac_regs[R_RDFAR] += s->descsize;
> +            }
> +            s->edmac_regs[R_EESR] = FIELD_DP32(s->edmac_regs[R_EESR],
> +                                               EESR, FR, 1);
> +        } else {
> +            /* no active RDESC */
> +            if (FIELD_EX32(s->edmac_regs[R_RMCR], RMCR, RNC) == 0) {
> +                s->edmac_regs[R_EDRRR] = FIELD_DP32(s->edmac_regs[R_EDRRR],
> +                                                    EDRRR, RR, 0);
> +            }
> +            s->edmac_regs[R_EESR] = FIELD_DP32(s->edmac_regs[R_EESR],
> +                                               EESR, RDE, 1);
> +            break;
> +        }
> +    }
> +    renesas_eth_set_irq(s);
> +}
> +
> +static inline void update_count(uint32_t *cnt)
> +{
> +    if (*cnt < UINT32_MAX) {
> +        /* Satulate on 32bit value */
> +        (*cnt)++;
> +    }
> +}
> +
> +#define MIN_BUF_SIZE 60
> +static ssize_t renesas_eth_receive(NetClientState *nc,
> +                            const uint8_t *buf, size_t size)
> +{
> +    RenesasEthState *s = RenesasEth(qemu_get_nic_opaque(nc));
> +    static const uint8_t bcast_addr[] = {
> +        0xff, 0xff, 0xff, 0xff, 0xff, 0xff
> +    };
> +    static const uint8_t pad[3] = { 0 };
> +    uint8_t buf1[MIN_BUF_SIZE];
> +    bool receive = false;
> +    size_t pads;
> +    uint32_t rflr;
> +
> +    if (size >= 6) {
> +        if (memcmp(buf, bcast_addr, sizeof(bcast_addr)) == 0) {
> +            /* broadcast */
> +            if (s->etherc_regs[R_BCFRR] == 0 ||
> +                s->etherc_regs[R_BCFRR] < s->rcv_bcast) {
> +                s->rcv_bcast++;
> +                receive = true;
> +            }
> +        } else if (buf[0] & 0x1) {
> +            /* multicast */
> +            receive = true;
> +            s->edmac_regs[R_EESR] = FIELD_DP32(s->edmac_regs[R_EESR],
> +                                               EESR, RMAF, 1);
> +            update_count(&s->edmac_regs[R_MAFCR]);
> +        } else if (FIELD_EX32(s->edmac_regs[R_ECMR], ECMR, PRM)) {
> +            /* promiscas */
> +            receive = true;
> +        } else if (memcmp(buf, s->macadr, sizeof(s->macadr)) == 0) {
> +            /* normal */
> +            receive = true;
> +        }
> +    }
> +    if (!receive) {
> +        return size;
> +    }
> +    /* if too small buffer, then expand it */
> +    if (size < MIN_BUF_SIZE) {
> +        memcpy(buf1, buf, size);
> +        memset(buf1 + size, 0, MIN_BUF_SIZE - size);
> +        buf = buf1;
> +        size = MIN_BUF_SIZE;
> +    }
> +
> +    rflr = FIELD_EX32(s->etherc_regs[R_RFLR], RFLR, RFL);
> +    rflr = MAX(rflr, 1518);
> +    if (size > rflr) {
> +        update_count(&s->etherc_regs[R_TLFRCR]);
> +        s->edmac_regs[R_EESR] = FIELD_DP32(s->edmac_regs[R_EESR],
> +                                           EESR, RTLF, 1);
> +    }
> +    pads = FIELD_EX32(s->edmac_regs[R_RPADIR], RPADIR, PADS);
> +    if (pads > 0) {
> +        int pos = FIELD_EX32(s->edmac_regs[R_RPADIR], RPADIR, PADR);
> +        uint8_t *padbuf = g_new(uint8_t, size + pads);
> +        if (size > pos) {
> +            if (pos > 0) {
> +                memcpy(padbuf, buf, pos);
> +            }
> +            memcpy(padbuf + pos, pad, pads);
> +            memcpy(padbuf + pos + pads, buf + pos, size - pos);
> +        } else {
> +            pads = 0;
> +        }
> +        edmac_write(s, padbuf, size + pads, pads);
> +        g_free(padbuf);
> +    } else {
> +        edmac_write(s, buf, size, 0);
> +    }
> +    return size;
> +}
> +
> +static size_t edmac_read(RenesasEthState *s, uint8_t **buf)
> +{
> +    uint32_t tdesc[3];
> +    uint32_t size = 0;
> +
> +    *buf = NULL;
> +    for (;;) {
> +        size_t rsize;
> +        dma_memory_read(&address_space_memory,
> +                        s->edmac_regs[R_TDFAR], tdesc, sizeof(tdesc));
> +        if (FIELD_EX32(tdesc[0], TD0, TACT)) {
> +            s->edmac_regs[R_TBRAR] = tdesc[2];
> +            rsize = FIELD_EX32(tdesc[1], TD1, TBL);
> +            *buf = g_realloc(*buf, size + rsize);
> +            dma_memory_read(&address_space_memory,
> +                            s->edmac_regs[R_TBRAR], *buf + size, rsize);
> +            tdesc[0] = FIELD_DP32(tdesc[0], TD0, TACT, 0);
> +            dma_memory_write(&address_space_memory,
> +                            s->edmac_regs[R_TDFAR], tdesc, sizeof(tdesc));
> +            size += rsize;
> +            if (FIELD_EX32(tdesc[0], TD0, TDLE)) {
> +                s->edmac_regs[R_TDFAR] = s->edmac_regs[R_TDLAR];
> +            } else {
> +                s->edmac_regs[R_TDFAR] += s->descsize;
> +            }
> +            if (FIELD_EX32(tdesc[0], TD0, TFP) & 1) {
> +                break;
> +            }
> +        } else {
> +            s->edmac_regs[R_EESR] = FIELD_DP32(s->edmac_regs[R_EESR],
> +                                               EESR, TDE, 1);
> +            renesas_eth_set_irq(s);
> +            break;
> +        }
> +    }
> +    return size;
> +}
> +
> +static void renesas_eth_start_xmit(RenesasEthState *s)
> +{
> +    uint8_t *txbuf;
> +    size_t size;
> +
> +    size = edmac_read(s, &txbuf);
> +    qemu_send_packet(qemu_get_queue(s->nic), txbuf, size);
> +    g_free(txbuf);
> +    s->edmac_regs[R_EESR] = FIELD_DP32(s->edmac_regs[R_EESR], EESR, TWB, 1);
> +    s->edmac_regs[R_EDTRR] = FIELD_DP32(s->edmac_regs[R_EDTRR], EDTRR, TR, 0);
> +    renesas_eth_set_irq(s);
> +}
> +
> +static void renesas_eth_reset(RenesasEthState *s)
> +{
> +    int i;
> +
> +    for (i = 0; i < RENESAS_ETHERC_R_MAX; i++) {
> +        register_reset(&s->etherc_regs_info[i]);
> +    }
> +    for (i = 0; i < RENESAS_EDMAC_R_MAX; i++) {
> +        register_reset(&s->edmac_regs_info[i]);
> +    }
> +}
> +
> +static uint64_t ecsr_pre_write(RegisterInfo *reg, uint64_t val)
> +{
> +    RenesasEthState *s = RenesasEth(reg->opaque);
> +    uint32_t old_val = s->etherc_regs[R_ECSR];
> +
> +    val ^= old_val;
> +    val &= old_val;
> +    return val;
> +}
> +
> +static void ecsr_post_write(RegisterInfo *reg, uint64_t val)
> +{
> +    RenesasEthState *s = RenesasEth(reg->opaque);
> +
> +    if (s->etherc_regs[R_ECSR] & s->etherc_regs[R_ECSIPR]) {
> +        s->edmac_regs[R_EESR] = FIELD_DP32(s->edmac_regs[R_EESR],
> +                                           EESR, ECI, 1);
> +    } else {
> +        s->edmac_regs[R_EESR] = FIELD_DP32(s->edmac_regs[R_EESR],
> +                                           EESR, ECI, 0);
> +    }
> +    renesas_eth_set_irq(s);
> +}
> +
> +static void pir_post_write(RegisterInfo *reg, uint64_t val)
> +{
> +    RenesasEthState *s = RenesasEth(reg->opaque);
> +    if (s->mdiodev) {
> +        mdio_set_mdc_pin(s->mdiodev, FIELD_EX32(val, PIR, MDC));
> +        if (FIELD_EX32(val, PIR, MMD)) {
> +            mdio_set_mdo_pin(s->mdiodev, FIELD_EX32(val, PIR, MDO));
> +        }
> +    }
> +}
> +
> +static uint64_t pir_post_read(RegisterInfo *reg, uint64_t val)
> +{
> +    RenesasEthState *s = RenesasEth(reg->opaque);
> +    if (s->mdiodev) {
> +        val = FIELD_DP64(val, PIR, MDI, mdio_read_mdi_pin(s->mdiodev));
> +    }
> +    return val;
> +}
> +
> +static uint64_t mar_pre_write(RegisterInfo *reg, uint64_t val)
> +{
> +    RenesasEthState *s = RenesasEth(reg->opaque);
> +    if (FIELD_EX32(s->edmac_regs[R_EDTRR], EDTRR, TR) ||
> +        FIELD_EX32(s->edmac_regs[R_EDRRR], EDRRR, RR)) {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "renesas_eth: Tx/Rx enabled in MAR write.\n");
> +    }
> +    return val;
> +}
> +
> +static void mar_post_write(RegisterInfo *reg, uint64_t val)
> +{
> +    int i;
> +    RenesasEthState *s = RenesasEth(reg->opaque);
> +    for (i = 0; i < 4; i++) {
> +        s->macadr[i] = extract32(s->etherc_regs[R_MAHR], 8 * (3 - i), 8);
> +    }
> +    for (i = 0; i < 2; i++) {
> +        s->macadr[i + 4] = extract32(s->etherc_regs[R_MALR], 8 * (1 - i), 8);
> +    }
> +}
> +
> +static uint64_t etherc_counter_write(RegisterInfo *reg, uint64_t val)
> +{
> +    /* Counter register clear in any write operation */
> +    return 0;
> +}
> +
> +static void edmr_post_write(RegisterInfo *reg, uint64_t val)
> +{
> +    RenesasEthState *s = RenesasEth(reg->opaque);
> +    uint32_t TDLAR, RMFCR, TFUCR, RFOCR;
> +    int dl;
> +
> +    if (FIELD_EX32(val, EDMR, SWR)) {
> +        /* Following register keep for SWR */
> +        TDLAR = s->edmac_regs[R_TDLAR];
> +        RMFCR = s->edmac_regs[R_RMFCR];
> +        TFUCR = s->edmac_regs[R_TFUCR];
> +        RFOCR = s->edmac_regs[R_RFOCR];
> +        renesas_eth_reset(s);
> +        s->edmac_regs[R_TDLAR] = TDLAR;
> +        s->edmac_regs[R_RMFCR] = RMFCR;
> +        s->edmac_regs[R_TFUCR] = TFUCR;
> +        s->edmac_regs[R_RFOCR] = RFOCR;
> +    }
> +    dl = FIELD_EX32(val, EDMR, DL) % 3;
> +    s->descsize = 16 << dl;
> +}
> +
> +static void edtrr_post_write(RegisterInfo *reg, uint64_t val)
> +{
> +    RenesasEthState *s = RenesasEth(reg->opaque);
> +    if (FIELD_EX32(val, EDTRR, TR)) {
> +        renesas_eth_start_xmit(s);
> +    }
> +}
> +
> +static uint64_t eesr_pre_write(RegisterInfo *reg, uint64_t val)
> +{
> +    uint32_t eesr;
> +    RenesasEthState *s = RenesasEth(reg->opaque);
> +    /* flag clear for write 1 */
> +    eesr = s->edmac_regs[R_EESR];
> +    val = FIELD_DP64(val, EESR, ECI, 0); /* Keep ECI value */
> +    eesr &= ~val;
> +    return eesr;
> +}
> +
> +static void eesr_post_write(RegisterInfo *reg, uint64_t val)
> +{
> +    RenesasEthState *s = RenesasEth(reg->opaque);
> +    renesas_eth_set_irq(s);
> +}
> +
> +static void tdlar_post_write(RegisterInfo *reg, uint64_t val)
> +{
> +    RenesasEthState *s = RenesasEth(reg->opaque);
> +    s->edmac_regs[R_TDFAR] = s->edmac_regs[R_TDLAR];
> +}
> +
> +static void rdlar_post_write(RegisterInfo *reg, uint64_t val)
> +{
> +    RenesasEthState *s = RenesasEth(reg->opaque);
> +    s->edmac_regs[R_RDFAR] = s->edmac_regs[R_RDLAR];
> +}
> +
> +static uint64_t fdr_pre_write(RegisterInfo *reg, uint64_t val)
> +{
> +    RenesasEthState *s = RenesasEth(reg->opaque);
> +    if (FIELD_EX32(val, FDR, TFD) != 7 || FIELD_EX32(val, FDR, RFD) != 7) {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "renesas_eth: invalid FDR setting %"
> +                      HWADDR_PRIX ".\n", val);
> +    }
> +    if (FIELD_EX32(s->edmac_regs[R_EDTRR], EDTRR, TR) ||
> +        FIELD_EX32(s->edmac_regs[R_EDRRR], EDRRR, RR)) {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "renesas_eth: Tx/Rx enabled in FDR write.\n");
> +    }
> +    return val;
> +}
> +
> +static uint64_t edmac_reg_read(void *opaque, hwaddr addr, unsigned int size)
> +{
> +    RegisterInfoArray *ra = opaque;
> +    RenesasEthState *s = RenesasEth(ra->r[0]->opaque);
> +    if (clock_is_enabled(s->ick)) {
> +        return register_read_memory(ra, addr, size);
> +    } else {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "renesas_eth: EDMAC module stopped.\n");
> +        return UINT64_MAX;
> +    }
> +}
> +
> +static void edmac_reg_write(void *opaque, hwaddr addr,
> +                        uint64_t value, unsigned int size)
> +{
> +    RegisterInfoArray *ra = opaque;
> +    RenesasEthState *s = RenesasEth(ra->r[0]->opaque);
> +    if (clock_is_enabled(s->ick)) {
> +        register_write_memory(ra, addr, value, size);
> +    } else {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "renesas_eth: EDMAC module stopped.\n");
> +    }
> +}
> +
> +static const MemoryRegionOps renesas_etherc_ops = {
> +    .read = register_read_memory,
> +    .write = register_write_memory,
> +    .endianness = DEVICE_NATIVE_ENDIAN,
> +    .impl = {
> +        .min_access_size = 4,
> +        .max_access_size = 4,
> +    },
> +};
> +
> +static const MemoryRegionOps renesas_edmac_ops = {
> +    .read = edmac_reg_read,
> +    .write = edmac_reg_write,
> +    .endianness = DEVICE_NATIVE_ENDIAN,
> +    .impl = {
> +        .min_access_size = 4,
> +        .max_access_size = 4,
> +    },
> +};
> +
> +static NetClientInfo net_renesas_eth_info = {
> +    .type = NET_CLIENT_DRIVER_NIC,
> +    .size = sizeof(NICState),
> +    .can_receive = renesas_eth_can_receive,
> +    .receive = renesas_eth_receive,
> +    .link_status_changed = renesas_eth_set_link_status,
> +};
> +
> +static const RegisterAccessInfo renesas_etherc_regs_info[] = {
> +    { .name = "ECMR", .addr = A_ECMR,
> +      .rsvd = 0xffe0ed90, },
> +    { .name = "RFLR", .addr = A_RFLR,
> +      .rsvd = 0xfffff000, },
> +    { .name = "ECSR", .addr = A_ECSR,
> +      .rsvd = 0xffffffc8,
> +      .pre_write = ecsr_pre_write,
> +      .post_write = ecsr_post_write, },
> +    { .name = "ECSIPR", .addr = A_ECSIPR,
> +      .rsvd = 0xffffffc8,
> +      .post_write = ecsr_post_write, },
> +    { .name = "PIR", .addr = A_PIR,
> +      .rsvd = 0xfffffff0,
> +      .post_write = pir_post_write,
> +      .post_read = pir_post_read, },
> +    { .name = "PSR", .addr = A_PSR,
> +      .rsvd = 0xfffffffe, },
> +    { .name = "RDMLR", .addr = A_RDMLR,
> +      .rsvd = 0xfff00000, },
> +    { .name = "IPGR", .addr = A_IPGR,
> +      .rsvd = 0xffffffe0, .reset = 0x00000014, },
> +    { .name = "APR", .addr = A_APR,
> +      .rsvd = 0xffff0000, },
> +    { .name = "MPR", .addr = A_MPR,
> +      .rsvd = 0xffff0000, },
> +    { .name = "RFCF", .addr = A_RFCF,
> +      .rsvd = 0xffffff00, },
> +    { .name = "TPAUSER", .addr = A_TPAUSER,
> +      .rsvd = 0xffff0000, },
> +    { .name = "TPAUSECR", .addr = A_TPAUSECR,
> +      .rsvd = 0xffffff00, },
> +    { .name = "BCFRR", .addr = A_BCFRR,
> +      .rsvd = 0xffff0000, },
> +    { .name = "MAHR", .addr = A_MAHR,
> +      .pre_write = mar_pre_write,
> +      .post_write = mar_post_write, },
> +    { .name = "MALR", .addr = A_MALR,
> +      .rsvd = 0xffff0000,
> +      .pre_write = mar_pre_write,
> +      .post_write = mar_post_write, },
> +    { .name = "TROCR", .addr = A_TROCR,
> +      .pre_write = etherc_counter_write, },
> +    { .name = "CDCR", .addr = A_CDCR,
> +      .pre_write = etherc_counter_write, },
> +    { .name = "LCCR", .addr = A_LCCR,
> +      .pre_write = etherc_counter_write, },
> +    { .name = "CNDCR", .addr = A_CNDCR,
> +      .pre_write = etherc_counter_write, },
> +    { .name = "CEFCR", .addr = A_CEFCR,
> +      .pre_write = etherc_counter_write, },
> +    { .name = "FRECR", .addr = A_FRECR,
> +      .pre_write = etherc_counter_write, },
> +    { .name = "TSFRCR", .addr = A_TSFRCR,
> +      .pre_write = etherc_counter_write, },
> +    { .name = "TLFRCR", .addr = A_TLFRCR,
> +      .pre_write = etherc_counter_write, },
> +    { .name = "RFCR", .addr = A_RFCR,
> +      .pre_write = etherc_counter_write, },
> +    { .name = "MAFCR", .addr = A_MAFCR,
> +      .pre_write = etherc_counter_write, },
> +};
> +
> +static const RegisterAccessInfo renesas_edmac_regs_info[] = {
> +    { .name = "EDMR", .addr = A_EDMR,
> +      .rsvd = 0xfffffff8e,
> +      .post_write = edmr_post_write, },
> +    { .name = "EDTRR", .addr = A_EDTRR,
> +      .rsvd = 0xffffffffe,
> +      .post_write = edtrr_post_write, },
> +    { .name = "EDRRR", .addr = A_EDRRR,
> +      .rsvd = 0xffffffffe, },
> +    { .name = "TDLAR", .addr = A_TDLAR,
> +      .post_write = tdlar_post_write, },
> +    { .name = "RDLAR", .addr = A_RDLAR,
> +      .post_write = rdlar_post_write, },
> +    { .name = "EESR", .addr = A_EESR,
> +      .rsvd = 0xb800f0c0, .ro = 0x00400000,
> +      .pre_write = eesr_pre_write,
> +      .post_write = eesr_post_write, },
> +    { .name = "EESIPR", .addr = A_EESIPR,
> +      .rsvd = 0xb800f060,
> +      .post_write = eesr_post_write, },
> +    { .name = "TRSCER", .addr = A_TRSCER,
> +      .rsvd = 0xfffffd6f, },
> +    { .name = "RMFCR", .addr = A_RMFCR,
> +      .rsvd = 0xffff0000, },
> +    { .name = "TFTR", .addr = A_TFTR,
> +      .rsvd = 0xfffff800, },
> +    { .name = "FDR", .addr = A_FDR,
> +      .rsvd = 0xffffe0e0,
> +      .pre_write = fdr_pre_write, },
> +    { .name = "RMCR", .addr = A_RMCR,
> +      .rsvd = 0xfffffffc, },
> +    { .name = "TFUCR", .addr = A_TFUCR,
> +      .rsvd = 0xffff0000,
> +      .pre_write = etherc_counter_write, },
> +    { .name = "RFOCR", .addr = A_RFOCR,
> +      .rsvd = 0xffff0000,
> +      .pre_write = etherc_counter_write, },
> +    { .name = "RBWAR", .addr = A_RBWAR,
> +      .ro = 0xffffffff, .rsvd = 0xffff0000, },
> +    { .name = "RDFAR", .addr = A_RDFAR,
> +      .ro = 0xffffffff, .rsvd = 0xffff0000, },
> +    { .name = "TBRAR", .addr = A_TBRAR,
> +      .ro = 0xffffffff, .rsvd = 0xffff0000, },
> +    { .name = "TDFAR", .addr = A_TDFAR,
> +      .ro = 0xffffffff, .rsvd = 0xffff0000, },
> +    { .name = "FCFTR", .addr = A_FCFTR,
> +      .rsvd = 0xfff8fff8, },
> +    { .name = "RPADIR", .addr = A_RPADIR,
> +      .rsvd = 0xfffcffc0, },
> +    { .name = "TRIMD", .addr = A_TRIMD,
> +      .rsvd = 0xffffffee, },
> +    { .name = "IOSR", .addr = A_IOSR,
> +      .rsvd = 0xfffffffe, },
> +};
> +
> +static void renesas_eth_realize(DeviceState *dev, Error **errp)
> +{
> +    RenesasEthState *s = RenesasEth(dev);
> +
> +    s->nic = qemu_new_nic(&net_renesas_eth_info, &s->conf,
> +                          object_get_typename(OBJECT(s)), dev->id, s);
> +
> +    renesas_eth_reset(s);
> +    if (s->mdiodev) {
> +        mdio_phy_set_link(mdio_get_phy(s->mdiodev),
> +                          !qemu_get_queue(s->nic)->link_down);
> +    }
> +}
> +
> +static Property renesas_eth_properties[] = {
> +    DEFINE_NIC_PROPERTIES(RenesasEthState, conf),
> +    DEFINE_PROP_LINK("mdio", RenesasEthState, mdiodev, TYPE_ETHER_MDIO_BB,
> +                     MDIOState *),
> +    DEFINE_PROP_END_OF_LIST(),
> +};
> +
> +static void renesas_eth_init(Object *obj)
> +{
> +    SysBusDevice *d = SYS_BUS_DEVICE(obj);
> +    RenesasEthState *s = RenesasEth(obj);
> +    RegisterInfoArray *ra_etherc;
> +    RegisterInfoArray *ra_edmac;
> +
> +    memory_region_init(&s->etherc_mem, obj, "renesas-etherc", 0x100);
> +    ra_etherc = register_init_block32(DEVICE(d), renesas_etherc_regs_info,
> +                                      ARRAY_SIZE(renesas_etherc_regs_info),
> +                                      s->etherc_regs_info, s->etherc_regs,
> +                                      &renesas_etherc_ops,
> +                                      false, 0x100);
> +    memory_region_add_subregion(&s->etherc_mem, 0x00, &ra_etherc->mem);
> +    sysbus_init_mmio(d, &s->etherc_mem);
> +
> +    memory_region_init(&s->edmac_mem, obj, "renesas-edmac", 0x100);
> +    ra_edmac = register_init_block32(DEVICE(d), renesas_edmac_regs_info,
> +                                     ARRAY_SIZE(renesas_edmac_regs_info),
> +                                     s->edmac_regs_info, s->edmac_regs,
> +                                     &renesas_edmac_ops,
> +                                     false, 0x100);
> +    memory_region_add_subregion(&s->edmac_mem, 0x00, &ra_edmac->mem);
> +    sysbus_init_mmio(d, &s->edmac_mem);
> +
> +    sysbus_init_irq(d, &s->irq);
> +    s->ick =  qdev_init_clock_in(DEVICE(d), "ick", NULL, NULL);
> +}
> +
> +static void renesas_eth_class_init(ObjectClass *klass, void *data)
> +{
> +    DeviceClass *dc = DEVICE_CLASS(klass);
> +
> +    set_bit(DEVICE_CATEGORY_NETWORK, dc->categories);
> +    device_class_set_props(dc, renesas_eth_properties);
> +    dc->realize = renesas_eth_realize;
> +}
> +
> +static const TypeInfo renesas_eth_info = {
> +    .name          = TYPE_RENESAS_ETH,
> +    .parent        = TYPE_SYS_BUS_DEVICE,
> +    .instance_size = sizeof(RenesasEthState),
> +    .instance_init = renesas_eth_init,
> +    .class_init    = renesas_eth_class_init,
> +};
> +
> +static void renesas_eth_register_types(void)
> +{
> +    type_register_static(&renesas_eth_info);
> +}
> +
> +type_init(renesas_eth_register_types)
> diff --git a/hw/net/Kconfig b/hw/net/Kconfig
> index e6a32a2ab0..7cb3aeadeb 100644
> --- a/hw/net/Kconfig
> +++ b/hw/net/Kconfig
> @@ -146,3 +146,8 @@ config CAN_SJA1000
>   
>   config MDIO_PHY
>       bool
> +
> +config RENESAS_ETH
> +    bool
> +    select MDIO_PHY
> +    select REGISTER
> diff --git a/hw/net/meson.build b/hw/net/meson.build
> index faa4e3d2c0..0f64af7b8f 100644
> --- a/hw/net/meson.build
> +++ b/hw/net/meson.build
> @@ -65,5 +65,6 @@ softmmu_ss.add(when: 'CONFIG_ROCKER', if_true: files(
>   softmmu_ss.add(when: 'CONFIG_ALL', if_true: files('rocker/qmp-norocker.c'))
>   
>   softmmu_ss.add(when: 'CONFIG_MDIO_PHY', if_true: files('mdio.c'))
> +softmmu_ss.add(when: 'CONFIG_RENESAS_ETH', if_true: files('renesas_eth.c'))
>   
>   subdir('can')
>
diff mbox series

Patch

diff --git a/include/hw/net/renesas_eth.h b/include/hw/net/renesas_eth.h
new file mode 100644
index 0000000000..e0026c6434
--- /dev/null
+++ b/include/hw/net/renesas_eth.h
@@ -0,0 +1,57 @@ 
+/*
+ *  Renesas ETHERC / EDMAC
+ *
+ *  Copyright 2019 Yoshinori Sato <ysato@users.sourceforge.jp>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; under version 2 of the License.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ *  Contributions after 2012-01-13 are licensed under the terms of the
+ *  GNU GPL, version 2 or (at your option) any later version.
+ */
+
+#include "hw/sysbus.h"
+#include "net/net.h"
+#include "hw/net/mdio.h"
+#include "hw/register.h"
+#include "hw/clock.h"
+
+#define TYPE_RENESAS_ETH "renesas_eth"
+#define RenesasEth(obj) OBJECT_CHECK(RenesasEthState, (obj), TYPE_RENESAS_ETH)
+
+#define RENESAS_ETHERC_R_MAX (0x100 / 4)
+#define RENESAS_EDMAC_R_MAX  (0x100 / 4)
+
+typedef struct RenesasEthState {
+    SysBusDevice parent_obj;
+
+    NICState *nic;
+    NICConf conf;
+    MemoryRegion etherc_mem;
+    MemoryRegion edmac_mem;
+    qemu_irq irq;
+
+    /* ETHERC registers */
+    RegisterInfo etherc_regs_info[RENESAS_ETHERC_R_MAX];
+    uint32_t etherc_regs[RENESAS_ETHERC_R_MAX];
+
+    /* EDMAC register */
+    RegisterInfo edmac_regs_info[RENESAS_EDMAC_R_MAX];
+    uint32_t edmac_regs[RENESAS_EDMAC_R_MAX];
+
+    int descsize;
+    int rcv_bcast;
+    uint8_t macadr[6];
+    int link_sta;
+    MDIOState *mdiodev;
+    Clock *ick;
+} RenesasEthState;
diff --git a/hw/net/renesas_eth.c b/hw/net/renesas_eth.c
new file mode 100644
index 0000000000..d5fc2bb30c
--- /dev/null
+++ b/hw/net/renesas_eth.c
@@ -0,0 +1,875 @@ 
+/*
+ *  Renesas ETHERC / EDMAC
+ *
+ *  Copyright 2019 Yoshinori Sato <ysato@users.sourceforge.jp>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; under version 2 of the License.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ *  Contributions after 2012-01-13 are licensed under the terms of the
+ *  GNU GPL, version 2 or (at your option) any later version.
+ */
+
+#include "qemu/osdep.h"
+
+#include "hw/hw.h"
+#include "sysemu/dma.h"
+#include "qemu/log.h"
+#include "hw/qdev-properties.h"
+#include "hw/qdev-clock.h"
+#include "net/net.h"
+#include "sysemu/sysemu.h"
+#include "hw/irq.h"
+#include "hw/net/renesas_eth.h"
+
+/* ETHERC Registers */
+REG32(ECMR, 0x00)
+  FIELD(ECMR, PRM, 0, 1)
+  FIELD(ECMR, DM, 1, 1)
+  FIELD(ECMR, RTM, 2, 1)
+  FIELD(ECMR, ILB, 3, 1)
+  FIELD(ECMR, TE, 5, 1)
+  FIELD(ECMR, RE, 6, 1)
+  FIELD(ECMR, MPDE, 9, 1)
+  FIELD(ECMR, PRCREF, 12, 1)
+  FIELD(ECMR, TXF, 16, 1)
+  FIELD(ECMR, RXF, 17, 1)
+  FIELD(ECMR, PFR, 18, 1)
+  FIELD(ECMR, ZPF, 19, 1)
+  FIELD(ECMR, TPC, 20, 1)
+REG32(RFLR, 0x08)
+  FIELD(RFLR, RFL, 0, 12)
+REG32(ECSR, 0x10)
+  FIELD(ECSR, ICD, 0, 1)
+  FIELD(ECSR, MPD, 1, 1)
+  FIELD(ECSR, LCHNG, 2, 1)
+  FIELD(ECSR, PSRTO, 4, 1)
+  FIELD(ECSR, BFR, 5, 1)
+REG32(ECSIPR, 0x18)
+  FIELD(ECSIPR, ICDIP, 0, 1)
+  FIELD(ECSIPR, MPDIP, 1, 1)
+  FIELD(ECSIPR, LCHNGIP, 2, 1)
+  FIELD(ECSIPR, PSRTOIP, 4, 1)
+  FIELD(ECSIPR, BFSIPR, 5, 1)
+REG32(PIR, 0x20)
+  FIELD(PIR, MDC, 0, 1)
+  FIELD(PIR, MMD, 1, 1)
+  FIELD(PIR, MDO, 2, 1)
+  FIELD(PIR, MDI, 3, 1)
+REG32(PSR, 0x28)
+  FIELD(PSR, LMON, 0, 1)
+REG32(RDMLR, 0x40)
+  FIELD(RDMLR, RMD, 0, 20)
+REG32(IPGR, 0x50)
+  FIELD(IPGR, IPG, 0, 5)
+REG32(APR, 0x54)
+  FIELD(APR, AP, 0, 16)
+REG32(MPR, 0x58)
+  FIELD(MPR, MP, 0, 16)
+REG32(RFCF, 0x60)
+  FIELD(RFCF, RPAUSE, 0, 8)
+REG32(TPAUSER, 0x64)
+REG32(TPAUSECR, 0x68)
+  FIELD(TPAUSECR, TXP, 0, 8)
+  FIELD(TPAUSER, TPAUSE, 0, 16)
+REG32(BCFRR, 0x6c)
+  FIELD(BCFRR, BCF, 0, 16)
+REG32(MAHR, 0xc0)
+  FIELD(MAHR, MA, 0, 32)
+REG32(MALR, 0xc8)
+  FIELD(MALR, MA, 0, 16)
+REG32(TROCR, 0xd0)
+REG32(CDCR, 0xd4)
+REG32(LCCR, 0xd8)
+REG32(CNDCR, 0xdc)
+REG32(CEFCR, 0xe4)
+REG32(FRECR, 0xe8)
+REG32(TSFRCR, 0xec)
+REG32(TLFRCR, 0xf0)
+REG32(RFCR, 0xf4)
+REG32(MAFCR, 0xf8)
+
+/* EDMAC register */
+REG32(EDMR, 0x00)
+  FIELD(EDMR, SWR, 0, 1)
+  FIELD(EDMR, DL, 4, 2)
+  FIELD(EDMR, DE, 6, 1)
+REG32(EDTRR, 0x08)
+  FIELD(EDTRR, TR, 0, 1)
+REG32(EDRRR, 0x10)
+  FIELD(EDRRR, RR, 0, 1)
+REG32(TDLAR, 0x18)
+REG32(RDLAR, 0x20)
+REG32(EESR, 0x28)
+  FIELD(EESR, CERF, 0, 1)
+  FIELD(EESR, PRE,  1, 1)
+  FIELD(EESR, RTSF, 2, 1)
+  FIELD(EESR, RTLF, 3, 1)
+  FIELD(EESR, RRF,  4, 1)
+  FIELD(EESR, RMAF, 7, 1)
+  FIELD(EESR, TRO,  8, 1)
+  FIELD(EESR, CD,   9, 1)
+  FIELD(EESR, RDESC, 0, 10)
+  FIELD(EESR, DLC,  10, 1)
+  FIELD(EESR, CND,  11, 1)
+  FIELD(EESR, RDOF, 16, 1)
+  FIELD(EESR, RDE,  17, 1)
+  FIELD(EESR, FR,   18, 1)
+  FIELD(EESR, TFUF, 19, 1)
+  FIELD(EESR, TDE,  20, 1)
+  FIELD(EESR, TC,   21, 1)
+  FIELD(EESR, ECI,  22, 1)
+  FIELD(EESR, ADE,  23, 1)
+  FIELD(EESR, RFCOF, 24, 1)
+  FIELD(EESR, RABT, 25, 1)
+  FIELD(EESR, TABT, 26, 1)
+  FIELD(EESR, TWB,  30, 1)
+REG32(EESIPR, 0x30)
+  FIELD(EESIPR, CERFIP, 0, 1)
+  FIELD(EESIPR, PREIP,  1, 1)
+  FIELD(EESIPR, RTSFIP, 2, 1)
+  FIELD(EESIPR, RTLFIP, 3, 1)
+  FIELD(EESIPR, RRFIP,  4, 1)
+  FIELD(EESIPR, RMAFIP, 7, 1)
+  FIELD(EESIPR, TROIP,  8, 1)
+  FIELD(EESIPR, CDIP,   9, 1)
+  FIELD(EESIPR, DLCIP,  10, 1)
+  FIELD(EESIPR, CNDIP,  11, 1)
+  FIELD(EESIPR, RDOFIP, 16, 1)
+  FIELD(EESIPR, RDEIP,  17, 1)
+  FIELD(EESIPR, FRIP,   18, 1)
+  FIELD(EESIPR, TFUFIP, 19, 1)
+  FIELD(EESIPR, TDEIP,  20, 1)
+  FIELD(EESIPR, TCIP,   21, 1)
+  FIELD(EESIPR, ECIIP,  22, 1)
+  FIELD(EESIPR, ADEIP,  23, 1)
+  FIELD(EESIPR, RFCOFIP, 24, 1)
+  FIELD(EESIPR, RABTIP, 25, 1)
+  FIELD(EESIPR, TABTIP, 26, 1)
+  FIELD(EESIPR, TWBIP,  30, 1)
+REG32(TRSCER, 0x38)
+  FIELD(TRSCER, RRFCE, 4, 1)
+  FIELD(TRSCER, RMAFCE, 7, 1)
+REG32(RMFCR, 0x40)
+  FIELD(RMFCR, MFC, 0, 16)
+REG32(TFTR, 0x48)
+  FIELD(TFTR, TFT, 0, 11)
+REG32(FDR, 0x50)
+  FIELD(FDR, RFD, 0, 5)
+  FIELD(FDR, TFD, 8, 5)
+REG32(RMCR, 0x58)
+  FIELD(RMCR, RNR, 0, 1)
+  FIELD(RMCR, RNC, 1, 1)
+REG32(TFUCR, 0x64)
+  FIELD(TFUCR, UNDER, 0, 16)
+REG32(RFOCR, 0x68)
+  FIELD(RFOCR, OVER, 0, 16)
+REG32(IOSR, 0x6c)
+  FIELD(IOSR, ELB, 0, 1);
+REG32(FCFTR, 0x70)
+  FIELD(FCFTR, RFDO, 0, 3)
+  FIELD(FCFTR, RFFO, 16, 3)
+REG32(RPADIR, 0x78)
+  FIELD(RPADIR, PADR, 0, 6)
+  FIELD(RPADIR, PADS, 16, 2)
+REG32(TRIMD, 0x7c)
+  FIELD(TRIMD, TIS, 0, 1)
+  FIELD(TRIMD, TIM, 4, 1)
+REG32(RBWAR, 0xc8)
+REG32(RDFAR, 0xcc)
+REG32(TBRAR, 0xd4)
+REG32(TDFAR, 0xd8)
+
+/* Transmit Descriptor */
+REG32(TD0, 0x0000)
+  FIELD(TD0, TFS0, 0, 1)
+  FIELD(TD0, TFS1, 1, 1)
+  FIELD(TD0, TFS2, 2, 1)
+  FIELD(TD0, TFS3, 3, 1)
+  FIELD(TD0, TFS8, 8, 1)
+  FIELD(TD0, TWBI, 26, 1)
+  FIELD(TD0, TFE,  27, 1)
+  FIELD(TD0, TFP,  28, 2)
+  FIELD(TD0, TDLE, 30, 1)
+  FIELD(TD0, TACT, 31, 1)
+REG32(TD1, 0x0004)
+  FIELD(TD1, TBL, 16, 16)
+REG32(TD2, 0x0008)
+  FIELD(TD2, TBA, 0, 32)
+
+/* Receive Descriptor */
+REG32(RD0, 0x0000)
+  FIELD(RD0, RFS,  0, 10)
+    FIELD(RD0, RFS0, 0, 1)
+    FIELD(RD0, RFS1, 1, 1)
+    FIELD(RD0, RFS2, 2, 1)
+    FIELD(RD0, RFS3, 3, 1)
+    FIELD(RD0, RFS4, 4, 1)
+    FIELD(RD0, RFS7, 7, 1)
+    FIELD(RD0, RFS8, 8, 1)
+    FIELD(RD0, RFS9, 9, 1)
+  FIELD(RD0, RFE,  27, 1)
+  FIELD(RD0, RFP,  28, 2)
+  FIELD(RD0, RFP0, 28, 1)
+  FIELD(RD0, RDLE, 30, 1)
+  FIELD(RD0, RACT, 31, 1)
+REG32(RD1, 0x0004)
+  FIELD(RD1, RFL, 0, 16)
+  FIELD(RD1, RBL, 16, 16)
+REG32(RD2, 0x0008)
+  FIELD(RD2, RBA, 0, 32)
+
+static void renesas_eth_set_irq(RenesasEthState *s)
+{
+    if (s->edmac_regs[R_EESR] & s->edmac_regs[R_EESIPR]) {
+        qemu_set_irq(s->irq, 1);
+    } else {
+        qemu_set_irq(s->irq, 0);
+    }
+}
+
+static bool renesas_eth_can_receive(NetClientState *nc)
+{
+    RenesasEthState *s = RenesasEth(qemu_get_nic_opaque(nc));
+
+    return FIELD_EX32(s->edmac_regs[R_EDRRR], EDRRR, RR);
+}
+
+static void set_ecsr(RenesasEthState *s, int bit)
+{
+    s->etherc_regs[R_ECSR] = deposit32(s->etherc_regs[R_ECSR], bit, 1, 1);
+    if (s->etherc_regs[R_ECSR] & s->etherc_regs[R_ECSIPR]) {
+        s->edmac_regs[R_EESR] = FIELD_DP32(s->edmac_regs[R_EESR],
+                                           EESR, ECI, 1);
+    }
+    renesas_eth_set_irq(s);
+}
+
+static void renesas_eth_set_link_status(NetClientState *nc)
+{
+    RenesasEthState *s = RenesasEth(qemu_get_nic_opaque(nc));
+    int old_lmon, new_lmon;
+    if (s->mdiodev) {
+        old_lmon = mdio_phy_linksta(mdio_get_phy(s->mdiodev));
+        mdio_phy_set_link(mdio_get_phy(s->mdiodev), !nc->link_down);
+        new_lmon = mdio_phy_linksta(mdio_get_phy(s->mdiodev));
+        if (old_lmon ^ new_lmon) {
+            set_ecsr(s, R_ECSR_LCHNG_SHIFT);
+        }
+    }
+}
+
+static void edmac_write(RenesasEthState *s, const uint8_t *buf,
+                        size_t size, int pad)
+{
+    uint32_t rdesc[3];
+    uint32_t eesr;
+    int state = 0;
+
+    while (size > 0) {
+        size_t wsize;
+        /* RDESC read */
+        dma_memory_read(&address_space_memory,
+                        s->edmac_regs[R_RDFAR], rdesc, sizeof(rdesc));
+        if (FIELD_EX32(rdesc[0], RD0, RACT)) {
+            if (state == 0) {
+                /* Fist block */
+                rdesc[0] = FIELD_DP32(rdesc[0], RD0, RFP, 2);
+            }
+            state++;
+            s->edmac_regs[R_RBWAR] = rdesc[2];
+            wsize = MIN(FIELD_EX32(rdesc[1], RD1, RBL), size);
+            /* Write receive data */
+            dma_memory_write(&address_space_memory,
+                             s->edmac_regs[R_RBWAR], buf, wsize);
+            buf += wsize;
+            size -= wsize;
+            rdesc[1] = FIELD_DP32(rdesc[1], RD1, RFL, wsize);
+            if (size == 0) {
+                /* Last descriptor */
+                rdesc[0] = FIELD_DP32(rdesc[0], RD0, RFP0, 1);
+                if (FIELD_EX32(s->edmac_regs[R_RMCR], RMCR, RNR) == 0) {
+                    s->edmac_regs[R_EDRRR] = FIELD_DP32(s->edmac_regs[R_EDRRR],
+                                                  EDRRR, RR, 0);
+                }
+                s->edmac_regs[R_EESR] = FIELD_DP32(s->edmac_regs[R_EESR],
+                                                   EESR, FR, 1);
+                renesas_eth_set_irq(s);
+            }
+            eesr = FIELD_EX32(s->edmac_regs[R_EESR], EESR, RDESC);
+            rdesc[0] = FIELD_DP32(rdesc[0], RD0, RFS,
+                                  eesr & ~(s->edmac_regs[R_TRSCER]));
+            rdesc[0] = FIELD_DP32(rdesc[0], RD0, RFE, eesr != 0);
+            rdesc[0] = FIELD_DP32(rdesc[0], RD0, RACT, 0);
+            /* RDESC write back */
+            dma_memory_write(&address_space_memory,
+                             s->edmac_regs[R_RDFAR], rdesc, sizeof(rdesc));
+            if (FIELD_EX32(rdesc[0], RD0, RDLE)) {
+                s->edmac_regs[R_RDFAR] = s->edmac_regs[R_RDLAR];
+            } else {
+                s->edmac_regs[R_RDFAR] += s->descsize;
+            }
+            s->edmac_regs[R_EESR] = FIELD_DP32(s->edmac_regs[R_EESR],
+                                               EESR, FR, 1);
+        } else {
+            /* no active RDESC */
+            if (FIELD_EX32(s->edmac_regs[R_RMCR], RMCR, RNC) == 0) {
+                s->edmac_regs[R_EDRRR] = FIELD_DP32(s->edmac_regs[R_EDRRR],
+                                                    EDRRR, RR, 0);
+            }
+            s->edmac_regs[R_EESR] = FIELD_DP32(s->edmac_regs[R_EESR],
+                                               EESR, RDE, 1);
+            break;
+        }
+    }
+    renesas_eth_set_irq(s);
+}
+
+static inline void update_count(uint32_t *cnt)
+{
+    if (*cnt < UINT32_MAX) {
+        /* Satulate on 32bit value */
+        (*cnt)++;
+    }
+}
+
+#define MIN_BUF_SIZE 60
+static ssize_t renesas_eth_receive(NetClientState *nc,
+                            const uint8_t *buf, size_t size)
+{
+    RenesasEthState *s = RenesasEth(qemu_get_nic_opaque(nc));
+    static const uint8_t bcast_addr[] = {
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff
+    };
+    static const uint8_t pad[3] = { 0 };
+    uint8_t buf1[MIN_BUF_SIZE];
+    bool receive = false;
+    size_t pads;
+    uint32_t rflr;
+
+    if (size >= 6) {
+        if (memcmp(buf, bcast_addr, sizeof(bcast_addr)) == 0) {
+            /* broadcast */
+            if (s->etherc_regs[R_BCFRR] == 0 ||
+                s->etherc_regs[R_BCFRR] < s->rcv_bcast) {
+                s->rcv_bcast++;
+                receive = true;
+            }
+        } else if (buf[0] & 0x1) {
+            /* multicast */
+            receive = true;
+            s->edmac_regs[R_EESR] = FIELD_DP32(s->edmac_regs[R_EESR],
+                                               EESR, RMAF, 1);
+            update_count(&s->edmac_regs[R_MAFCR]);
+        } else if (FIELD_EX32(s->edmac_regs[R_ECMR], ECMR, PRM)) {
+            /* promiscas */
+            receive = true;
+        } else if (memcmp(buf, s->macadr, sizeof(s->macadr)) == 0) {
+            /* normal */
+            receive = true;
+        }
+    }
+    if (!receive) {
+        return size;
+    }
+    /* if too small buffer, then expand it */
+    if (size < MIN_BUF_SIZE) {
+        memcpy(buf1, buf, size);
+        memset(buf1 + size, 0, MIN_BUF_SIZE - size);
+        buf = buf1;
+        size = MIN_BUF_SIZE;
+    }
+
+    rflr = FIELD_EX32(s->etherc_regs[R_RFLR], RFLR, RFL);
+    rflr = MAX(rflr, 1518);
+    if (size > rflr) {
+        update_count(&s->etherc_regs[R_TLFRCR]);
+        s->edmac_regs[R_EESR] = FIELD_DP32(s->edmac_regs[R_EESR],
+                                           EESR, RTLF, 1);
+    }
+    pads = FIELD_EX32(s->edmac_regs[R_RPADIR], RPADIR, PADS);
+    if (pads > 0) {
+        int pos = FIELD_EX32(s->edmac_regs[R_RPADIR], RPADIR, PADR);
+        uint8_t *padbuf = g_new(uint8_t, size + pads);
+        if (size > pos) {
+            if (pos > 0) {
+                memcpy(padbuf, buf, pos);
+            }
+            memcpy(padbuf + pos, pad, pads);
+            memcpy(padbuf + pos + pads, buf + pos, size - pos);
+        } else {
+            pads = 0;
+        }
+        edmac_write(s, padbuf, size + pads, pads);
+        g_free(padbuf);
+    } else {
+        edmac_write(s, buf, size, 0);
+    }
+    return size;
+}
+
+static size_t edmac_read(RenesasEthState *s, uint8_t **buf)
+{
+    uint32_t tdesc[3];
+    uint32_t size = 0;
+
+    *buf = NULL;
+    for (;;) {
+        size_t rsize;
+        dma_memory_read(&address_space_memory,
+                        s->edmac_regs[R_TDFAR], tdesc, sizeof(tdesc));
+        if (FIELD_EX32(tdesc[0], TD0, TACT)) {
+            s->edmac_regs[R_TBRAR] = tdesc[2];
+            rsize = FIELD_EX32(tdesc[1], TD1, TBL);
+            *buf = g_realloc(*buf, size + rsize);
+            dma_memory_read(&address_space_memory,
+                            s->edmac_regs[R_TBRAR], *buf + size, rsize);
+            tdesc[0] = FIELD_DP32(tdesc[0], TD0, TACT, 0);
+            dma_memory_write(&address_space_memory,
+                            s->edmac_regs[R_TDFAR], tdesc, sizeof(tdesc));
+            size += rsize;
+            if (FIELD_EX32(tdesc[0], TD0, TDLE)) {
+                s->edmac_regs[R_TDFAR] = s->edmac_regs[R_TDLAR];
+            } else {
+                s->edmac_regs[R_TDFAR] += s->descsize;
+            }
+            if (FIELD_EX32(tdesc[0], TD0, TFP) & 1) {
+                break;
+            }
+        } else {
+            s->edmac_regs[R_EESR] = FIELD_DP32(s->edmac_regs[R_EESR],
+                                               EESR, TDE, 1);
+            renesas_eth_set_irq(s);
+            break;
+        }
+    }
+    return size;
+}
+
+static void renesas_eth_start_xmit(RenesasEthState *s)
+{
+    uint8_t *txbuf;
+    size_t size;
+
+    size = edmac_read(s, &txbuf);
+    qemu_send_packet(qemu_get_queue(s->nic), txbuf, size);
+    g_free(txbuf);
+    s->edmac_regs[R_EESR] = FIELD_DP32(s->edmac_regs[R_EESR], EESR, TWB, 1);
+    s->edmac_regs[R_EDTRR] = FIELD_DP32(s->edmac_regs[R_EDTRR], EDTRR, TR, 0);
+    renesas_eth_set_irq(s);
+}
+
+static void renesas_eth_reset(RenesasEthState *s)
+{
+    int i;
+
+    for (i = 0; i < RENESAS_ETHERC_R_MAX; i++) {
+        register_reset(&s->etherc_regs_info[i]);
+    }
+    for (i = 0; i < RENESAS_EDMAC_R_MAX; i++) {
+        register_reset(&s->edmac_regs_info[i]);
+    }
+}
+
+static uint64_t ecsr_pre_write(RegisterInfo *reg, uint64_t val)
+{
+    RenesasEthState *s = RenesasEth(reg->opaque);
+    uint32_t old_val = s->etherc_regs[R_ECSR];
+
+    val ^= old_val;
+    val &= old_val;
+    return val;
+}
+
+static void ecsr_post_write(RegisterInfo *reg, uint64_t val)
+{
+    RenesasEthState *s = RenesasEth(reg->opaque);
+
+    if (s->etherc_regs[R_ECSR] & s->etherc_regs[R_ECSIPR]) {
+        s->edmac_regs[R_EESR] = FIELD_DP32(s->edmac_regs[R_EESR],
+                                           EESR, ECI, 1);
+    } else {
+        s->edmac_regs[R_EESR] = FIELD_DP32(s->edmac_regs[R_EESR],
+                                           EESR, ECI, 0);
+    }
+    renesas_eth_set_irq(s);
+}
+
+static void pir_post_write(RegisterInfo *reg, uint64_t val)
+{
+    RenesasEthState *s = RenesasEth(reg->opaque);
+    if (s->mdiodev) {
+        mdio_set_mdc_pin(s->mdiodev, FIELD_EX32(val, PIR, MDC));
+        if (FIELD_EX32(val, PIR, MMD)) {
+            mdio_set_mdo_pin(s->mdiodev, FIELD_EX32(val, PIR, MDO));
+        }
+    }
+}
+
+static uint64_t pir_post_read(RegisterInfo *reg, uint64_t val)
+{
+    RenesasEthState *s = RenesasEth(reg->opaque);
+    if (s->mdiodev) {
+        val = FIELD_DP64(val, PIR, MDI, mdio_read_mdi_pin(s->mdiodev));
+    }
+    return val;
+}
+
+static uint64_t mar_pre_write(RegisterInfo *reg, uint64_t val)
+{
+    RenesasEthState *s = RenesasEth(reg->opaque);
+    if (FIELD_EX32(s->edmac_regs[R_EDTRR], EDTRR, TR) ||
+        FIELD_EX32(s->edmac_regs[R_EDRRR], EDRRR, RR)) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "renesas_eth: Tx/Rx enabled in MAR write.\n");
+    }
+    return val;
+}
+
+static void mar_post_write(RegisterInfo *reg, uint64_t val)
+{
+    int i;
+    RenesasEthState *s = RenesasEth(reg->opaque);
+    for (i = 0; i < 4; i++) {
+        s->macadr[i] = extract32(s->etherc_regs[R_MAHR], 8 * (3 - i), 8);
+    }
+    for (i = 0; i < 2; i++) {
+        s->macadr[i + 4] = extract32(s->etherc_regs[R_MALR], 8 * (1 - i), 8);
+    }
+}
+
+static uint64_t etherc_counter_write(RegisterInfo *reg, uint64_t val)
+{
+    /* Counter register clear in any write operation */
+    return 0;
+}
+
+static void edmr_post_write(RegisterInfo *reg, uint64_t val)
+{
+    RenesasEthState *s = RenesasEth(reg->opaque);
+    uint32_t TDLAR, RMFCR, TFUCR, RFOCR;
+    int dl;
+
+    if (FIELD_EX32(val, EDMR, SWR)) {
+        /* Following register keep for SWR */
+        TDLAR = s->edmac_regs[R_TDLAR];
+        RMFCR = s->edmac_regs[R_RMFCR];
+        TFUCR = s->edmac_regs[R_TFUCR];
+        RFOCR = s->edmac_regs[R_RFOCR];
+        renesas_eth_reset(s);
+        s->edmac_regs[R_TDLAR] = TDLAR;
+        s->edmac_regs[R_RMFCR] = RMFCR;
+        s->edmac_regs[R_TFUCR] = TFUCR;
+        s->edmac_regs[R_RFOCR] = RFOCR;
+    }
+    dl = FIELD_EX32(val, EDMR, DL) % 3;
+    s->descsize = 16 << dl;
+}
+
+static void edtrr_post_write(RegisterInfo *reg, uint64_t val)
+{
+    RenesasEthState *s = RenesasEth(reg->opaque);
+    if (FIELD_EX32(val, EDTRR, TR)) {
+        renesas_eth_start_xmit(s);
+    }
+}
+
+static uint64_t eesr_pre_write(RegisterInfo *reg, uint64_t val)
+{
+    uint32_t eesr;
+    RenesasEthState *s = RenesasEth(reg->opaque);
+    /* flag clear for write 1 */
+    eesr = s->edmac_regs[R_EESR];
+    val = FIELD_DP64(val, EESR, ECI, 0); /* Keep ECI value */
+    eesr &= ~val;
+    return eesr;
+}
+
+static void eesr_post_write(RegisterInfo *reg, uint64_t val)
+{
+    RenesasEthState *s = RenesasEth(reg->opaque);
+    renesas_eth_set_irq(s);
+}
+
+static void tdlar_post_write(RegisterInfo *reg, uint64_t val)
+{
+    RenesasEthState *s = RenesasEth(reg->opaque);
+    s->edmac_regs[R_TDFAR] = s->edmac_regs[R_TDLAR];
+}
+
+static void rdlar_post_write(RegisterInfo *reg, uint64_t val)
+{
+    RenesasEthState *s = RenesasEth(reg->opaque);
+    s->edmac_regs[R_RDFAR] = s->edmac_regs[R_RDLAR];
+}
+
+static uint64_t fdr_pre_write(RegisterInfo *reg, uint64_t val)
+{
+    RenesasEthState *s = RenesasEth(reg->opaque);
+    if (FIELD_EX32(val, FDR, TFD) != 7 || FIELD_EX32(val, FDR, RFD) != 7) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "renesas_eth: invalid FDR setting %"
+                      HWADDR_PRIX ".\n", val);
+    }
+    if (FIELD_EX32(s->edmac_regs[R_EDTRR], EDTRR, TR) ||
+        FIELD_EX32(s->edmac_regs[R_EDRRR], EDRRR, RR)) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "renesas_eth: Tx/Rx enabled in FDR write.\n");
+    }
+    return val;
+}
+
+static uint64_t edmac_reg_read(void *opaque, hwaddr addr, unsigned int size)
+{
+    RegisterInfoArray *ra = opaque;
+    RenesasEthState *s = RenesasEth(ra->r[0]->opaque);
+    if (clock_is_enabled(s->ick)) {
+        return register_read_memory(ra, addr, size);
+    } else {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "renesas_eth: EDMAC module stopped.\n");
+        return UINT64_MAX;
+    }
+}
+
+static void edmac_reg_write(void *opaque, hwaddr addr,
+                        uint64_t value, unsigned int size)
+{
+    RegisterInfoArray *ra = opaque;
+    RenesasEthState *s = RenesasEth(ra->r[0]->opaque);
+    if (clock_is_enabled(s->ick)) {
+        register_write_memory(ra, addr, value, size);
+    } else {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "renesas_eth: EDMAC module stopped.\n");
+    }
+}
+
+static const MemoryRegionOps renesas_etherc_ops = {
+    .read = register_read_memory,
+    .write = register_write_memory,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+    .impl = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+    },
+};
+
+static const MemoryRegionOps renesas_edmac_ops = {
+    .read = edmac_reg_read,
+    .write = edmac_reg_write,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+    .impl = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+    },
+};
+
+static NetClientInfo net_renesas_eth_info = {
+    .type = NET_CLIENT_DRIVER_NIC,
+    .size = sizeof(NICState),
+    .can_receive = renesas_eth_can_receive,
+    .receive = renesas_eth_receive,
+    .link_status_changed = renesas_eth_set_link_status,
+};
+
+static const RegisterAccessInfo renesas_etherc_regs_info[] = {
+    { .name = "ECMR", .addr = A_ECMR,
+      .rsvd = 0xffe0ed90, },
+    { .name = "RFLR", .addr = A_RFLR,
+      .rsvd = 0xfffff000, },
+    { .name = "ECSR", .addr = A_ECSR,
+      .rsvd = 0xffffffc8,
+      .pre_write = ecsr_pre_write,
+      .post_write = ecsr_post_write, },
+    { .name = "ECSIPR", .addr = A_ECSIPR,
+      .rsvd = 0xffffffc8,
+      .post_write = ecsr_post_write, },
+    { .name = "PIR", .addr = A_PIR,
+      .rsvd = 0xfffffff0,
+      .post_write = pir_post_write,
+      .post_read = pir_post_read, },
+    { .name = "PSR", .addr = A_PSR,
+      .rsvd = 0xfffffffe, },
+    { .name = "RDMLR", .addr = A_RDMLR,
+      .rsvd = 0xfff00000, },
+    { .name = "IPGR", .addr = A_IPGR,
+      .rsvd = 0xffffffe0, .reset = 0x00000014, },
+    { .name = "APR", .addr = A_APR,
+      .rsvd = 0xffff0000, },
+    { .name = "MPR", .addr = A_MPR,
+      .rsvd = 0xffff0000, },
+    { .name = "RFCF", .addr = A_RFCF,
+      .rsvd = 0xffffff00, },
+    { .name = "TPAUSER", .addr = A_TPAUSER,
+      .rsvd = 0xffff0000, },
+    { .name = "TPAUSECR", .addr = A_TPAUSECR,
+      .rsvd = 0xffffff00, },
+    { .name = "BCFRR", .addr = A_BCFRR,
+      .rsvd = 0xffff0000, },
+    { .name = "MAHR", .addr = A_MAHR,
+      .pre_write = mar_pre_write,
+      .post_write = mar_post_write, },
+    { .name = "MALR", .addr = A_MALR,
+      .rsvd = 0xffff0000,
+      .pre_write = mar_pre_write,
+      .post_write = mar_post_write, },
+    { .name = "TROCR", .addr = A_TROCR,
+      .pre_write = etherc_counter_write, },
+    { .name = "CDCR", .addr = A_CDCR,
+      .pre_write = etherc_counter_write, },
+    { .name = "LCCR", .addr = A_LCCR,
+      .pre_write = etherc_counter_write, },
+    { .name = "CNDCR", .addr = A_CNDCR,
+      .pre_write = etherc_counter_write, },
+    { .name = "CEFCR", .addr = A_CEFCR,
+      .pre_write = etherc_counter_write, },
+    { .name = "FRECR", .addr = A_FRECR,
+      .pre_write = etherc_counter_write, },
+    { .name = "TSFRCR", .addr = A_TSFRCR,
+      .pre_write = etherc_counter_write, },
+    { .name = "TLFRCR", .addr = A_TLFRCR,
+      .pre_write = etherc_counter_write, },
+    { .name = "RFCR", .addr = A_RFCR,
+      .pre_write = etherc_counter_write, },
+    { .name = "MAFCR", .addr = A_MAFCR,
+      .pre_write = etherc_counter_write, },
+};
+
+static const RegisterAccessInfo renesas_edmac_regs_info[] = {
+    { .name = "EDMR", .addr = A_EDMR,
+      .rsvd = 0xfffffff8e,
+      .post_write = edmr_post_write, },
+    { .name = "EDTRR", .addr = A_EDTRR,
+      .rsvd = 0xffffffffe,
+      .post_write = edtrr_post_write, },
+    { .name = "EDRRR", .addr = A_EDRRR,
+      .rsvd = 0xffffffffe, },
+    { .name = "TDLAR", .addr = A_TDLAR,
+      .post_write = tdlar_post_write, },
+    { .name = "RDLAR", .addr = A_RDLAR,
+      .post_write = rdlar_post_write, },
+    { .name = "EESR", .addr = A_EESR,
+      .rsvd = 0xb800f0c0, .ro = 0x00400000,
+      .pre_write = eesr_pre_write,
+      .post_write = eesr_post_write, },
+    { .name = "EESIPR", .addr = A_EESIPR,
+      .rsvd = 0xb800f060,
+      .post_write = eesr_post_write, },
+    { .name = "TRSCER", .addr = A_TRSCER,
+      .rsvd = 0xfffffd6f, },
+    { .name = "RMFCR", .addr = A_RMFCR,
+      .rsvd = 0xffff0000, },
+    { .name = "TFTR", .addr = A_TFTR,
+      .rsvd = 0xfffff800, },
+    { .name = "FDR", .addr = A_FDR,
+      .rsvd = 0xffffe0e0,
+      .pre_write = fdr_pre_write, },
+    { .name = "RMCR", .addr = A_RMCR,
+      .rsvd = 0xfffffffc, },
+    { .name = "TFUCR", .addr = A_TFUCR,
+      .rsvd = 0xffff0000,
+      .pre_write = etherc_counter_write, },
+    { .name = "RFOCR", .addr = A_RFOCR,
+      .rsvd = 0xffff0000,
+      .pre_write = etherc_counter_write, },
+    { .name = "RBWAR", .addr = A_RBWAR,
+      .ro = 0xffffffff, .rsvd = 0xffff0000, },
+    { .name = "RDFAR", .addr = A_RDFAR,
+      .ro = 0xffffffff, .rsvd = 0xffff0000, },
+    { .name = "TBRAR", .addr = A_TBRAR,
+      .ro = 0xffffffff, .rsvd = 0xffff0000, },
+    { .name = "TDFAR", .addr = A_TDFAR,
+      .ro = 0xffffffff, .rsvd = 0xffff0000, },
+    { .name = "FCFTR", .addr = A_FCFTR,
+      .rsvd = 0xfff8fff8, },
+    { .name = "RPADIR", .addr = A_RPADIR,
+      .rsvd = 0xfffcffc0, },
+    { .name = "TRIMD", .addr = A_TRIMD,
+      .rsvd = 0xffffffee, },
+    { .name = "IOSR", .addr = A_IOSR,
+      .rsvd = 0xfffffffe, },
+};
+
+static void renesas_eth_realize(DeviceState *dev, Error **errp)
+{
+    RenesasEthState *s = RenesasEth(dev);
+
+    s->nic = qemu_new_nic(&net_renesas_eth_info, &s->conf,
+                          object_get_typename(OBJECT(s)), dev->id, s);
+
+    renesas_eth_reset(s);
+    if (s->mdiodev) {
+        mdio_phy_set_link(mdio_get_phy(s->mdiodev),
+                          !qemu_get_queue(s->nic)->link_down);
+    }
+}
+
+static Property renesas_eth_properties[] = {
+    DEFINE_NIC_PROPERTIES(RenesasEthState, conf),
+    DEFINE_PROP_LINK("mdio", RenesasEthState, mdiodev, TYPE_ETHER_MDIO_BB,
+                     MDIOState *),
+    DEFINE_PROP_END_OF_LIST(),
+};
+
+static void renesas_eth_init(Object *obj)
+{
+    SysBusDevice *d = SYS_BUS_DEVICE(obj);
+    RenesasEthState *s = RenesasEth(obj);
+    RegisterInfoArray *ra_etherc;
+    RegisterInfoArray *ra_edmac;
+
+    memory_region_init(&s->etherc_mem, obj, "renesas-etherc", 0x100);
+    ra_etherc = register_init_block32(DEVICE(d), renesas_etherc_regs_info,
+                                      ARRAY_SIZE(renesas_etherc_regs_info),
+                                      s->etherc_regs_info, s->etherc_regs,
+                                      &renesas_etherc_ops,
+                                      false, 0x100);
+    memory_region_add_subregion(&s->etherc_mem, 0x00, &ra_etherc->mem);
+    sysbus_init_mmio(d, &s->etherc_mem);
+
+    memory_region_init(&s->edmac_mem, obj, "renesas-edmac", 0x100);
+    ra_edmac = register_init_block32(DEVICE(d), renesas_edmac_regs_info,
+                                     ARRAY_SIZE(renesas_edmac_regs_info),
+                                     s->edmac_regs_info, s->edmac_regs,
+                                     &renesas_edmac_ops,
+                                     false, 0x100);
+    memory_region_add_subregion(&s->edmac_mem, 0x00, &ra_edmac->mem);
+    sysbus_init_mmio(d, &s->edmac_mem);
+
+    sysbus_init_irq(d, &s->irq);
+    s->ick =  qdev_init_clock_in(DEVICE(d), "ick", NULL, NULL);
+}
+
+static void renesas_eth_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    set_bit(DEVICE_CATEGORY_NETWORK, dc->categories);
+    device_class_set_props(dc, renesas_eth_properties);
+    dc->realize = renesas_eth_realize;
+}
+
+static const TypeInfo renesas_eth_info = {
+    .name          = TYPE_RENESAS_ETH,
+    .parent        = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(RenesasEthState),
+    .instance_init = renesas_eth_init,
+    .class_init    = renesas_eth_class_init,
+};
+
+static void renesas_eth_register_types(void)
+{
+    type_register_static(&renesas_eth_info);
+}
+
+type_init(renesas_eth_register_types)
diff --git a/hw/net/Kconfig b/hw/net/Kconfig
index e6a32a2ab0..7cb3aeadeb 100644
--- a/hw/net/Kconfig
+++ b/hw/net/Kconfig
@@ -146,3 +146,8 @@  config CAN_SJA1000
 
 config MDIO_PHY
     bool
+
+config RENESAS_ETH
+    bool
+    select MDIO_PHY
+    select REGISTER
diff --git a/hw/net/meson.build b/hw/net/meson.build
index faa4e3d2c0..0f64af7b8f 100644
--- a/hw/net/meson.build
+++ b/hw/net/meson.build
@@ -65,5 +65,6 @@  softmmu_ss.add(when: 'CONFIG_ROCKER', if_true: files(
 softmmu_ss.add(when: 'CONFIG_ALL', if_true: files('rocker/qmp-norocker.c'))
 
 softmmu_ss.add(when: 'CONFIG_MDIO_PHY', if_true: files('mdio.c'))
+softmmu_ss.add(when: 'CONFIG_RENESAS_ETH', if_true: files('renesas_eth.c'))
 
 subdir('can')