hw/net: add support for Allwinner EMAC Fast Ethernet controller
This patch adds support for the Fast Ethernet MAC found on Allwinner SoCs, together with a basic emulation of Realtek RTL8201CP PHY. Since there is no public documentation of the Allwinner controller, the implementation is based on Linux kernel driver. Signed-off-by: Beniamino Galvani <b.galvani@gmail.com> Reviewed-by: Peter Crosthwaite <peter.crosthwaite@xilinx.com> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
		
							parent
							
								
									58892d4782
								
							
						
					
					
						commit
						22f90bcb2b
					
				| @ -27,6 +27,7 @@ CONFIG_SSI_SD=y | ||||
| CONFIG_SSI_M25P80=y | ||||
| CONFIG_LAN9118=y | ||||
| CONFIG_SMC91C111=y | ||||
| CONFIG_ALLWINNER_EMAC=y | ||||
| CONFIG_DS1338=y | ||||
| CONFIG_PFLASH_CFI01=y | ||||
| CONFIG_PFLASH_CFI02=y | ||||
|  | ||||
| @ -18,6 +18,7 @@ common-obj-$(CONFIG_OPENCORES_ETH) += opencores_eth.o | ||||
| common-obj-$(CONFIG_XGMAC) += xgmac.o | ||||
| common-obj-$(CONFIG_MIPSNET) += mipsnet.o | ||||
| common-obj-$(CONFIG_XILINX_AXI) += xilinx_axienet.o | ||||
| common-obj-$(CONFIG_ALLWINNER_EMAC) += allwinner_emac.o | ||||
| 
 | ||||
| common-obj-$(CONFIG_CADENCE) += cadence_gem.o | ||||
| common-obj-$(CONFIG_STELLARIS_ENET) += stellaris_enet.o | ||||
|  | ||||
							
								
								
									
										539
									
								
								hw/net/allwinner_emac.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										539
									
								
								hw/net/allwinner_emac.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,539 @@ | ||||
| /*
 | ||||
|  * Emulation of Allwinner EMAC Fast Ethernet controller and | ||||
|  * Realtek RTL8201CP PHY | ||||
|  * | ||||
|  * Copyright (C) 2014 Beniamino Galvani <b.galvani@gmail.com> | ||||
|  * | ||||
|  * This model is based on reverse-engineering of Linux kernel driver. | ||||
|  * | ||||
|  * This program is free software; you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU General Public License version 2 as | ||||
|  * published by the Free Software Foundation. | ||||
|  * | ||||
|  * 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. | ||||
|  * | ||||
|  */ | ||||
| #include "hw/sysbus.h" | ||||
| #include "net/net.h" | ||||
| #include "qemu/fifo8.h" | ||||
| #include "hw/net/allwinner_emac.h" | ||||
| #include <zlib.h> | ||||
| 
 | ||||
| static uint8_t padding[60]; | ||||
| 
 | ||||
| static void mii_set_link(RTL8201CPState *mii, bool link_ok) | ||||
| { | ||||
|     if (link_ok) { | ||||
|         mii->bmsr |= MII_BMSR_LINK_ST; | ||||
|         mii->anlpar |= MII_ANAR_TXFD | MII_ANAR_10FD | MII_ANAR_10 | | ||||
|                        MII_ANAR_CSMACD; | ||||
|     } else { | ||||
|         mii->bmsr &= ~MII_BMSR_LINK_ST; | ||||
|         mii->anlpar = MII_ANAR_TX; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static void mii_reset(RTL8201CPState *mii, bool link_ok) | ||||
| { | ||||
|     mii->bmcr = MII_BMCR_FD | MII_BMCR_AUTOEN | MII_BMCR_SPEED; | ||||
|     mii->bmsr = MII_BMSR_100TX_FD | MII_BMSR_100TX_HD | MII_BMSR_10T_FD | | ||||
|                 MII_BMSR_10T_HD | MII_BMSR_MFPS | MII_BMSR_AUTONEG; | ||||
|     mii->anar = MII_ANAR_TXFD | MII_ANAR_TX | MII_ANAR_10FD | MII_ANAR_10 | | ||||
|                 MII_ANAR_CSMACD; | ||||
|     mii->anlpar = MII_ANAR_TX; | ||||
| 
 | ||||
|     mii_set_link(mii, link_ok); | ||||
| } | ||||
| 
 | ||||
| static uint16_t RTL8201CP_mdio_read(AwEmacState *s, uint8_t addr, uint8_t reg) | ||||
| { | ||||
|     RTL8201CPState *mii = &s->mii; | ||||
|     uint16_t ret = 0xffff; | ||||
| 
 | ||||
|     if (addr == s->phy_addr) { | ||||
|         switch (reg) { | ||||
|         case MII_BMCR: | ||||
|             return mii->bmcr; | ||||
|         case MII_BMSR: | ||||
|             return mii->bmsr; | ||||
|         case MII_PHYID1: | ||||
|             return RTL8201CP_PHYID1; | ||||
|         case MII_PHYID2: | ||||
|             return RTL8201CP_PHYID2; | ||||
|         case MII_ANAR: | ||||
|             return mii->anar; | ||||
|         case MII_ANLPAR: | ||||
|             return mii->anlpar; | ||||
|         case MII_ANER: | ||||
|         case MII_NSR: | ||||
|         case MII_LBREMR: | ||||
|         case MII_REC: | ||||
|         case MII_SNRDR: | ||||
|         case MII_TEST: | ||||
|             qemu_log_mask(LOG_UNIMP, | ||||
|                           "allwinner_emac: read from unimpl. mii reg 0x%x\n", | ||||
|                           reg); | ||||
|             return 0; | ||||
|         default: | ||||
|             qemu_log_mask(LOG_GUEST_ERROR, | ||||
|                           "allwinner_emac: read from invalid mii reg 0x%x\n", | ||||
|                           reg); | ||||
|             return 0; | ||||
|         } | ||||
|     } | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| static void RTL8201CP_mdio_write(AwEmacState *s, uint8_t addr, uint8_t reg, | ||||
|                                  uint16_t value) | ||||
| { | ||||
|     RTL8201CPState *mii = &s->mii; | ||||
|     NetClientState *nc; | ||||
| 
 | ||||
|     if (addr == s->phy_addr) { | ||||
|         switch (reg) { | ||||
|         case MII_BMCR: | ||||
|             if (value & MII_BMCR_RESET) { | ||||
|                 nc = qemu_get_queue(s->nic); | ||||
|                 mii_reset(mii, !nc->link_down); | ||||
|             } else { | ||||
|                 mii->bmcr = value; | ||||
|             } | ||||
|             break; | ||||
|         case MII_ANAR: | ||||
|             mii->anar = value; | ||||
|             break; | ||||
|         case MII_BMSR: | ||||
|         case MII_PHYID1: | ||||
|         case MII_PHYID2: | ||||
|         case MII_ANLPAR: | ||||
|         case MII_ANER: | ||||
|             qemu_log_mask(LOG_GUEST_ERROR, | ||||
|                           "allwinner_emac: write to read-only mii reg 0x%x\n", | ||||
|                           reg); | ||||
|             break; | ||||
|         case MII_NSR: | ||||
|         case MII_LBREMR: | ||||
|         case MII_REC: | ||||
|         case MII_SNRDR: | ||||
|         case MII_TEST: | ||||
|             qemu_log_mask(LOG_UNIMP, | ||||
|                           "allwinner_emac: write to unimpl. mii reg 0x%x\n", | ||||
|                           reg); | ||||
|             break; | ||||
|         default: | ||||
|             qemu_log_mask(LOG_GUEST_ERROR, | ||||
|                           "allwinner_emac: write to invalid mii reg 0x%x\n", | ||||
|                           reg); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static void aw_emac_update_irq(AwEmacState *s) | ||||
| { | ||||
|     qemu_set_irq(s->irq, (s->int_sta & s->int_ctl) != 0); | ||||
| } | ||||
| 
 | ||||
| static void aw_emac_tx_reset(AwEmacState *s, int chan) | ||||
| { | ||||
|     fifo8_reset(&s->tx_fifo[chan]); | ||||
|     s->tx_length[chan] = 0; | ||||
| } | ||||
| 
 | ||||
| static void aw_emac_rx_reset(AwEmacState *s) | ||||
| { | ||||
|     fifo8_reset(&s->rx_fifo); | ||||
|     s->rx_num_packets = 0; | ||||
|     s->rx_packet_size = 0; | ||||
|     s->rx_packet_pos = 0; | ||||
| } | ||||
| 
 | ||||
| static void fifo8_push_word(Fifo8 *fifo, uint32_t val) | ||||
| { | ||||
|     fifo8_push(fifo, val); | ||||
|     fifo8_push(fifo, val >> 8); | ||||
|     fifo8_push(fifo, val >> 16); | ||||
|     fifo8_push(fifo, val >> 24); | ||||
| } | ||||
| 
 | ||||
| static uint32_t fifo8_pop_word(Fifo8 *fifo) | ||||
| { | ||||
|     uint32_t ret; | ||||
| 
 | ||||
|     ret = fifo8_pop(fifo); | ||||
|     ret |= fifo8_pop(fifo) << 8; | ||||
|     ret |= fifo8_pop(fifo) << 16; | ||||
|     ret |= fifo8_pop(fifo) << 24; | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| static int aw_emac_can_receive(NetClientState *nc) | ||||
| { | ||||
|     AwEmacState *s = qemu_get_nic_opaque(nc); | ||||
| 
 | ||||
|     /*
 | ||||
|      * To avoid packet drops, allow reception only when there is space | ||||
|      * for a full frame: 1522 + 8 (rx headers) + 2 (padding). | ||||
|      */ | ||||
|     return (s->ctl & EMAC_CTL_RX_EN) && (fifo8_num_free(&s->rx_fifo) >= 1532); | ||||
| } | ||||
| 
 | ||||
| static ssize_t aw_emac_receive(NetClientState *nc, const uint8_t *buf, | ||||
|                                size_t size) | ||||
| { | ||||
|     AwEmacState *s = qemu_get_nic_opaque(nc); | ||||
|     Fifo8 *fifo = &s->rx_fifo; | ||||
|     size_t padded_size, total_size; | ||||
|     uint32_t crc; | ||||
| 
 | ||||
|     padded_size = size > 60 ? size : 60; | ||||
|     total_size = QEMU_ALIGN_UP(RX_HDR_SIZE + padded_size + CRC_SIZE, 4); | ||||
| 
 | ||||
|     if (!(s->ctl & EMAC_CTL_RX_EN) || (fifo8_num_free(fifo) < total_size)) { | ||||
|         return -1; | ||||
|     } | ||||
| 
 | ||||
|     fifo8_push_word(fifo, EMAC_UNDOCUMENTED_MAGIC); | ||||
|     fifo8_push_word(fifo, EMAC_RX_HEADER(padded_size + CRC_SIZE, | ||||
|                                          EMAC_RX_IO_DATA_STATUS_OK)); | ||||
|     fifo8_push_all(fifo, buf, size); | ||||
|     crc = crc32(~0, buf, size); | ||||
| 
 | ||||
|     if (padded_size != size) { | ||||
|         fifo8_push_all(fifo, padding, padded_size - size); | ||||
|         crc = crc32(crc, padding, padded_size - size); | ||||
|     } | ||||
| 
 | ||||
|     fifo8_push_word(fifo, crc); | ||||
|     fifo8_push_all(fifo, padding, QEMU_ALIGN_UP(padded_size, 4) - padded_size); | ||||
|     s->rx_num_packets++; | ||||
| 
 | ||||
|     s->int_sta |= EMAC_INT_RX; | ||||
|     aw_emac_update_irq(s); | ||||
| 
 | ||||
|     return size; | ||||
| } | ||||
| 
 | ||||
| static void aw_emac_cleanup(NetClientState *nc) | ||||
| { | ||||
|     AwEmacState *s = qemu_get_nic_opaque(nc); | ||||
| 
 | ||||
|     s->nic = NULL; | ||||
| } | ||||
| 
 | ||||
| static void aw_emac_reset(DeviceState *dev) | ||||
| { | ||||
|     AwEmacState *s = AW_EMAC(dev); | ||||
|     NetClientState *nc = qemu_get_queue(s->nic); | ||||
| 
 | ||||
|     s->ctl = 0; | ||||
|     s->tx_mode = 0; | ||||
|     s->int_ctl = 0; | ||||
|     s->int_sta = 0; | ||||
|     s->tx_channel = 0; | ||||
|     s->phy_target = 0; | ||||
| 
 | ||||
|     aw_emac_tx_reset(s, 0); | ||||
|     aw_emac_tx_reset(s, 1); | ||||
|     aw_emac_rx_reset(s); | ||||
| 
 | ||||
|     mii_reset(&s->mii, !nc->link_down); | ||||
| } | ||||
| 
 | ||||
| static uint64_t aw_emac_read(void *opaque, hwaddr offset, unsigned size) | ||||
| { | ||||
|     AwEmacState *s = opaque; | ||||
|     Fifo8 *fifo = &s->rx_fifo; | ||||
|     NetClientState *nc; | ||||
|     uint64_t ret; | ||||
| 
 | ||||
|     switch (offset) { | ||||
|     case EMAC_CTL_REG: | ||||
|         return s->ctl; | ||||
|     case EMAC_TX_MODE_REG: | ||||
|         return s->tx_mode; | ||||
|     case EMAC_TX_INS_REG: | ||||
|         return s->tx_channel; | ||||
|     case EMAC_RX_CTL_REG: | ||||
|         return s->rx_ctl; | ||||
|     case EMAC_RX_IO_DATA_REG: | ||||
|         if (!s->rx_num_packets) { | ||||
|             qemu_log_mask(LOG_GUEST_ERROR, | ||||
|                           "Read IO data register when no packet available"); | ||||
|             return 0; | ||||
|         } | ||||
| 
 | ||||
|         ret = fifo8_pop_word(fifo); | ||||
| 
 | ||||
|         switch (s->rx_packet_pos) { | ||||
|         case 0:     /* Word is magic header */ | ||||
|             s->rx_packet_pos += 4; | ||||
|             break; | ||||
|         case 4:     /* Word is rx info header */ | ||||
|             s->rx_packet_pos += 4; | ||||
|             s->rx_packet_size = QEMU_ALIGN_UP(extract32(ret, 0, 16), 4); | ||||
|             break; | ||||
|         default:    /* Word is packet data */ | ||||
|             s->rx_packet_pos += 4; | ||||
|             s->rx_packet_size -= 4; | ||||
| 
 | ||||
|             if (!s->rx_packet_size) { | ||||
|                 s->rx_packet_pos = 0; | ||||
|                 s->rx_num_packets--; | ||||
|                 nc = qemu_get_queue(s->nic); | ||||
|                 if (aw_emac_can_receive(nc)) { | ||||
|                     qemu_flush_queued_packets(nc); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return ret; | ||||
|     case EMAC_RX_FBC_REG: | ||||
|         return s->rx_num_packets; | ||||
|     case EMAC_INT_CTL_REG: | ||||
|         return s->int_ctl; | ||||
|     case EMAC_INT_STA_REG: | ||||
|         return s->int_sta; | ||||
|     case EMAC_MAC_MRDD_REG: | ||||
|         return RTL8201CP_mdio_read(s, | ||||
|                                    extract32(s->phy_target, PHY_ADDR_SHIFT, 8), | ||||
|                                    extract32(s->phy_target, PHY_REG_SHIFT, 8)); | ||||
|     default: | ||||
|         qemu_log_mask(LOG_UNIMP, | ||||
|                       "allwinner_emac: read access to unknown register 0x" | ||||
|                       TARGET_FMT_plx "\n", offset); | ||||
|         ret = 0; | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| static void aw_emac_write(void *opaque, hwaddr offset, uint64_t value, | ||||
|                           unsigned size) | ||||
| { | ||||
|     AwEmacState *s = opaque; | ||||
|     Fifo8 *fifo; | ||||
|     NetClientState *nc = qemu_get_queue(s->nic); | ||||
|     int chan; | ||||
| 
 | ||||
|     switch (offset) { | ||||
|     case EMAC_CTL_REG: | ||||
|         if (value & EMAC_CTL_RESET) { | ||||
|             aw_emac_reset(DEVICE(s)); | ||||
|             value &= ~EMAC_CTL_RESET; | ||||
|         } | ||||
|         s->ctl = value; | ||||
|         if (aw_emac_can_receive(nc)) { | ||||
|             qemu_flush_queued_packets(nc); | ||||
|         } | ||||
|         break; | ||||
|     case EMAC_TX_MODE_REG: | ||||
|         s->tx_mode = value; | ||||
|         break; | ||||
|     case EMAC_TX_CTL0_REG: | ||||
|     case EMAC_TX_CTL1_REG: | ||||
|         chan = (offset == EMAC_TX_CTL0_REG ? 0 : 1); | ||||
|         if ((value & 1) && (s->ctl & EMAC_CTL_TX_EN)) { | ||||
|             uint32_t len, ret; | ||||
|             const uint8_t *data; | ||||
| 
 | ||||
|             fifo = &s->tx_fifo[chan]; | ||||
|             len = s->tx_length[chan]; | ||||
| 
 | ||||
|             if (len > fifo8_num_used(fifo)) { | ||||
|                 len = fifo8_num_used(fifo); | ||||
|                 qemu_log_mask(LOG_GUEST_ERROR, | ||||
|                               "allwinner_emac: TX length > fifo data length\n"); | ||||
|             } | ||||
|             if (len > 0) { | ||||
|                 data = fifo8_pop_buf(fifo, len, &ret); | ||||
|                 qemu_send_packet(nc, data, ret); | ||||
|                 aw_emac_tx_reset(s, chan); | ||||
|                 /* Raise TX interrupt */ | ||||
|                 s->int_sta |= EMAC_INT_TX_CHAN(chan); | ||||
|                 aw_emac_update_irq(s); | ||||
|             } | ||||
|         } | ||||
|         break; | ||||
|     case EMAC_TX_INS_REG: | ||||
|         s->tx_channel = value < NUM_TX_FIFOS ? value : 0; | ||||
|         break; | ||||
|     case EMAC_TX_PL0_REG: | ||||
|     case EMAC_TX_PL1_REG: | ||||
|         chan = (offset == EMAC_TX_PL0_REG ? 0 : 1); | ||||
|         if (value > TX_FIFO_SIZE) { | ||||
|             qemu_log_mask(LOG_GUEST_ERROR, | ||||
|                           "allwinner_emac: invalid TX frame length %d\n", | ||||
|                           (int)value); | ||||
|             value = TX_FIFO_SIZE; | ||||
|         } | ||||
|         s->tx_length[chan] = value; | ||||
|         break; | ||||
|     case EMAC_TX_IO_DATA_REG: | ||||
|         fifo = &s->tx_fifo[s->tx_channel]; | ||||
|         if (fifo8_num_free(fifo) < 4) { | ||||
|             qemu_log_mask(LOG_GUEST_ERROR, | ||||
|                           "allwinner_emac: TX data overruns fifo\n"); | ||||
|             break; | ||||
|         } | ||||
|         fifo8_push_word(fifo, value); | ||||
|         break; | ||||
|     case EMAC_RX_CTL_REG: | ||||
|         s->rx_ctl = value; | ||||
|         break; | ||||
|     case EMAC_RX_FBC_REG: | ||||
|         if (value == 0) { | ||||
|             aw_emac_rx_reset(s); | ||||
|         } | ||||
|         break; | ||||
|     case EMAC_INT_CTL_REG: | ||||
|         s->int_ctl = value; | ||||
|         break; | ||||
|     case EMAC_INT_STA_REG: | ||||
|         s->int_sta &= ~value; | ||||
|         break; | ||||
|     case EMAC_MAC_MADR_REG: | ||||
|         s->phy_target = value; | ||||
|         break; | ||||
|     case EMAC_MAC_MWTD_REG: | ||||
|         RTL8201CP_mdio_write(s, extract32(s->phy_target, PHY_ADDR_SHIFT, 8), | ||||
|                              extract32(s->phy_target, PHY_REG_SHIFT, 8), value); | ||||
|         break; | ||||
|     default: | ||||
|         qemu_log_mask(LOG_UNIMP, | ||||
|                       "allwinner_emac: write access to unknown register 0x" | ||||
|                       TARGET_FMT_plx "\n", offset); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static void aw_emac_set_link(NetClientState *nc) | ||||
| { | ||||
|     AwEmacState *s = qemu_get_nic_opaque(nc); | ||||
| 
 | ||||
|     mii_set_link(&s->mii, !nc->link_down); | ||||
| } | ||||
| 
 | ||||
| static const MemoryRegionOps aw_emac_mem_ops = { | ||||
|     .read = aw_emac_read, | ||||
|     .write = aw_emac_write, | ||||
|     .endianness = DEVICE_NATIVE_ENDIAN, | ||||
|     .valid = { | ||||
|         .min_access_size = 4, | ||||
|         .max_access_size = 4, | ||||
|     }, | ||||
| }; | ||||
| 
 | ||||
| static NetClientInfo net_aw_emac_info = { | ||||
|     .type = NET_CLIENT_OPTIONS_KIND_NIC, | ||||
|     .size = sizeof(NICState), | ||||
|     .can_receive = aw_emac_can_receive, | ||||
|     .receive = aw_emac_receive, | ||||
|     .cleanup = aw_emac_cleanup, | ||||
|     .link_status_changed = aw_emac_set_link, | ||||
| }; | ||||
| 
 | ||||
| static void aw_emac_init(Object *obj) | ||||
| { | ||||
|     SysBusDevice *sbd = SYS_BUS_DEVICE(obj); | ||||
|     AwEmacState *s = AW_EMAC(obj); | ||||
| 
 | ||||
|     memory_region_init_io(&s->iomem, OBJECT(s), &aw_emac_mem_ops, s, | ||||
|                           "aw_emac", 0x1000); | ||||
|     sysbus_init_mmio(sbd, &s->iomem); | ||||
|     sysbus_init_irq(sbd, &s->irq); | ||||
| } | ||||
| 
 | ||||
| static void aw_emac_realize(DeviceState *dev, Error **errp) | ||||
| { | ||||
|     AwEmacState *s = AW_EMAC(dev); | ||||
| 
 | ||||
|     qemu_macaddr_default_if_unset(&s->conf.macaddr); | ||||
|     s->nic = qemu_new_nic(&net_aw_emac_info, &s->conf, | ||||
|                           object_get_typename(OBJECT(dev)), dev->id, s); | ||||
|     qemu_format_nic_info_str(qemu_get_queue(s->nic), s->conf.macaddr.a); | ||||
| 
 | ||||
|     fifo8_create(&s->rx_fifo, RX_FIFO_SIZE); | ||||
|     fifo8_create(&s->tx_fifo[0], TX_FIFO_SIZE); | ||||
|     fifo8_create(&s->tx_fifo[1], TX_FIFO_SIZE); | ||||
| } | ||||
| 
 | ||||
| static Property aw_emac_properties[] = { | ||||
|     DEFINE_NIC_PROPERTIES(AwEmacState, conf), | ||||
|     DEFINE_PROP_UINT8("phy-addr", AwEmacState, phy_addr, 0), | ||||
|     DEFINE_PROP_END_OF_LIST(), | ||||
| }; | ||||
| 
 | ||||
| static const VMStateDescription vmstate_mii = { | ||||
|     .name = "rtl8201cp", | ||||
|     .version_id = 1, | ||||
|     .minimum_version_id = 1, | ||||
|     .fields = (VMStateField[]) { | ||||
|         VMSTATE_UINT16(bmcr, RTL8201CPState), | ||||
|         VMSTATE_UINT16(bmsr, RTL8201CPState), | ||||
|         VMSTATE_UINT16(anar, RTL8201CPState), | ||||
|         VMSTATE_UINT16(anlpar, RTL8201CPState), | ||||
|         VMSTATE_END_OF_LIST() | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| static int aw_emac_post_load(void *opaque, int version_id) | ||||
| { | ||||
|     AwEmacState *s = opaque; | ||||
| 
 | ||||
|     aw_emac_set_link(qemu_get_queue(s->nic)); | ||||
| 
 | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| static const VMStateDescription vmstate_aw_emac = { | ||||
|     .name = "allwinner_emac", | ||||
|     .version_id = 1, | ||||
|     .minimum_version_id = 1, | ||||
|     .post_load = aw_emac_post_load, | ||||
|     .fields = (VMStateField[]) { | ||||
|         VMSTATE_STRUCT(mii, AwEmacState, 1, vmstate_mii, RTL8201CPState), | ||||
|         VMSTATE_UINT32(ctl, AwEmacState), | ||||
|         VMSTATE_UINT32(tx_mode, AwEmacState), | ||||
|         VMSTATE_UINT32(rx_ctl, AwEmacState), | ||||
|         VMSTATE_UINT32(int_ctl, AwEmacState), | ||||
|         VMSTATE_UINT32(int_sta, AwEmacState), | ||||
|         VMSTATE_UINT32(phy_target, AwEmacState), | ||||
|         VMSTATE_FIFO8(rx_fifo, AwEmacState), | ||||
|         VMSTATE_UINT32(rx_num_packets, AwEmacState), | ||||
|         VMSTATE_UINT32(rx_packet_size, AwEmacState), | ||||
|         VMSTATE_UINT32(rx_packet_pos, AwEmacState), | ||||
|         VMSTATE_STRUCT_ARRAY(tx_fifo, AwEmacState, NUM_TX_FIFOS, 1, | ||||
|                              vmstate_fifo8, Fifo8), | ||||
|         VMSTATE_UINT32_ARRAY(tx_length, AwEmacState, NUM_TX_FIFOS), | ||||
|         VMSTATE_UINT32(tx_channel, AwEmacState), | ||||
|         VMSTATE_END_OF_LIST() | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| static void aw_emac_class_init(ObjectClass *klass, void *data) | ||||
| { | ||||
|     DeviceClass *dc = DEVICE_CLASS(klass); | ||||
| 
 | ||||
|     dc->realize = aw_emac_realize; | ||||
|     dc->props = aw_emac_properties; | ||||
|     dc->reset = aw_emac_reset; | ||||
|     dc->vmsd = &vmstate_aw_emac; | ||||
| } | ||||
| 
 | ||||
| static const TypeInfo aw_emac_info = { | ||||
|     .name           = TYPE_AW_EMAC, | ||||
|     .parent         = TYPE_SYS_BUS_DEVICE, | ||||
|     .instance_size  = sizeof(AwEmacState), | ||||
|     .instance_init   = aw_emac_init, | ||||
|     .class_init     = aw_emac_class_init, | ||||
| }; | ||||
| 
 | ||||
| static void aw_emac_register_types(void) | ||||
| { | ||||
|     type_register_static(&aw_emac_info); | ||||
| } | ||||
| 
 | ||||
| type_init(aw_emac_register_types) | ||||
							
								
								
									
										210
									
								
								include/hw/net/allwinner_emac.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										210
									
								
								include/hw/net/allwinner_emac.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,210 @@ | ||||
| /*
 | ||||
|  * Emulation of Allwinner EMAC Fast Ethernet controller and | ||||
|  * Realtek RTL8201CP PHY | ||||
|  * | ||||
|  * Copyright (C) 2014 Beniamino Galvani <b.galvani@gmail.com> | ||||
|  * | ||||
|  * Allwinner EMAC register definitions from Linux kernel are: | ||||
|  *   Copyright 2012 Stefan Roese <sr@denx.de> | ||||
|  *   Copyright 2013 Maxime Ripard <maxime.ripard@free-electrons.com> | ||||
|  *   Copyright 1997 Sten Wang | ||||
|  * | ||||
|  * This program is free software; you can redistribute it and/or | ||||
|  * modify it under the terms of the GNU General Public License | ||||
|  * version 2 as published by the Free Software Foundation. | ||||
|  * | ||||
|  * 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. | ||||
|  * | ||||
|  */ | ||||
| #ifndef AW_EMAC_H | ||||
| #define AW_EMAC_H | ||||
| 
 | ||||
| #include "net/net.h" | ||||
| #include "qemu/fifo8.h" | ||||
| 
 | ||||
| #define TYPE_AW_EMAC "allwinner-emac" | ||||
| #define AW_EMAC(obj) OBJECT_CHECK(AwEmacState, (obj), TYPE_AW_EMAC) | ||||
| 
 | ||||
| /*
 | ||||
|  * Allwinner EMAC register list | ||||
|  */ | ||||
| #define EMAC_CTL_REG            0x00 | ||||
| 
 | ||||
| #define EMAC_TX_MODE_REG        0x04 | ||||
| #define EMAC_TX_FLOW_REG        0x08 | ||||
| #define EMAC_TX_CTL0_REG        0x0C | ||||
| #define EMAC_TX_CTL1_REG        0x10 | ||||
| #define EMAC_TX_INS_REG         0x14 | ||||
| #define EMAC_TX_PL0_REG         0x18 | ||||
| #define EMAC_TX_PL1_REG         0x1C | ||||
| #define EMAC_TX_STA_REG         0x20 | ||||
| #define EMAC_TX_IO_DATA_REG     0x24 | ||||
| #define EMAC_TX_IO_DATA1_REG    0x28 | ||||
| #define EMAC_TX_TSVL0_REG       0x2C | ||||
| #define EMAC_TX_TSVH0_REG       0x30 | ||||
| #define EMAC_TX_TSVL1_REG       0x34 | ||||
| #define EMAC_TX_TSVH1_REG       0x38 | ||||
| 
 | ||||
| #define EMAC_RX_CTL_REG         0x3C | ||||
| #define EMAC_RX_HASH0_REG       0x40 | ||||
| #define EMAC_RX_HASH1_REG       0x44 | ||||
| #define EMAC_RX_STA_REG         0x48 | ||||
| #define EMAC_RX_IO_DATA_REG     0x4C | ||||
| #define EMAC_RX_FBC_REG         0x50 | ||||
| 
 | ||||
| #define EMAC_INT_CTL_REG        0x54 | ||||
| #define EMAC_INT_STA_REG        0x58 | ||||
| 
 | ||||
| #define EMAC_MAC_CTL0_REG       0x5C | ||||
| #define EMAC_MAC_CTL1_REG       0x60 | ||||
| #define EMAC_MAC_IPGT_REG       0x64 | ||||
| #define EMAC_MAC_IPGR_REG       0x68 | ||||
| #define EMAC_MAC_CLRT_REG       0x6C | ||||
| #define EMAC_MAC_MAXF_REG       0x70 | ||||
| #define EMAC_MAC_SUPP_REG       0x74 | ||||
| #define EMAC_MAC_TEST_REG       0x78 | ||||
| #define EMAC_MAC_MCFG_REG       0x7C | ||||
| #define EMAC_MAC_MCMD_REG       0x80 | ||||
| #define EMAC_MAC_MADR_REG       0x84 | ||||
| #define EMAC_MAC_MWTD_REG       0x88 | ||||
| #define EMAC_MAC_MRDD_REG       0x8C | ||||
| #define EMAC_MAC_MIND_REG       0x90 | ||||
| #define EMAC_MAC_SSRR_REG       0x94 | ||||
| #define EMAC_MAC_A0_REG         0x98 | ||||
| #define EMAC_MAC_A1_REG         0x9C | ||||
| #define EMAC_MAC_A2_REG         0xA0 | ||||
| 
 | ||||
| #define EMAC_SAFX_L_REG0        0xA4 | ||||
| #define EMAC_SAFX_H_REG0        0xA8 | ||||
| #define EMAC_SAFX_L_REG1        0xAC | ||||
| #define EMAC_SAFX_H_REG1        0xB0 | ||||
| #define EMAC_SAFX_L_REG2        0xB4 | ||||
| #define EMAC_SAFX_H_REG2        0xB8 | ||||
| #define EMAC_SAFX_L_REG3        0xBC | ||||
| #define EMAC_SAFX_H_REG3        0xC0 | ||||
| 
 | ||||
| /* CTL register fields */ | ||||
| #define EMAC_CTL_RESET                  (1 << 0) | ||||
| #define EMAC_CTL_TX_EN                  (1 << 1) | ||||
| #define EMAC_CTL_RX_EN                  (1 << 2) | ||||
| 
 | ||||
| /* TX MODE register fields */ | ||||
| #define EMAC_TX_MODE_ABORTED_FRAME_EN   (1 << 0) | ||||
| #define EMAC_TX_MODE_DMA_EN             (1 << 1) | ||||
| 
 | ||||
| /* RX CTL register fields */ | ||||
| #define EMAC_RX_CTL_AUTO_DRQ_EN         (1 << 1) | ||||
| #define EMAC_RX_CTL_DMA_EN              (1 << 2) | ||||
| #define EMAC_RX_CTL_PASS_ALL_EN         (1 << 4) | ||||
| #define EMAC_RX_CTL_PASS_CTL_EN         (1 << 5) | ||||
| #define EMAC_RX_CTL_PASS_CRC_ERR_EN     (1 << 6) | ||||
| #define EMAC_RX_CTL_PASS_LEN_ERR_EN     (1 << 7) | ||||
| #define EMAC_RX_CTL_PASS_LEN_OOR_EN     (1 << 8) | ||||
| #define EMAC_RX_CTL_ACCEPT_UNICAST_EN   (1 << 16) | ||||
| #define EMAC_RX_CTL_DA_FILTER_EN        (1 << 17) | ||||
| #define EMAC_RX_CTL_ACCEPT_MULTICAST_EN (1 << 20) | ||||
| #define EMAC_RX_CTL_HASH_FILTER_EN      (1 << 21) | ||||
| #define EMAC_RX_CTL_ACCEPT_BROADCAST_EN (1 << 22) | ||||
| #define EMAC_RX_CTL_SA_FILTER_EN        (1 << 24) | ||||
| #define EMAC_RX_CTL_SA_FILTER_INVERT_EN (1 << 25) | ||||
| 
 | ||||
| /* RX IO DATA register fields */ | ||||
| #define EMAC_RX_HEADER(len, status)     (((len) & 0xffff) | ((status) << 16)) | ||||
| #define EMAC_RX_IO_DATA_STATUS_CRC_ERR  (1 << 4) | ||||
| #define EMAC_RX_IO_DATA_STATUS_LEN_ERR  (3 << 5) | ||||
| #define EMAC_RX_IO_DATA_STATUS_OK       (1 << 7) | ||||
| #define EMAC_UNDOCUMENTED_MAGIC         0x0143414d  /* header for RX frames */ | ||||
| 
 | ||||
| /* PHY registers */ | ||||
| #define MII_BMCR            0 | ||||
| #define MII_BMSR            1 | ||||
| #define MII_PHYID1          2 | ||||
| #define MII_PHYID2          3 | ||||
| #define MII_ANAR            4 | ||||
| #define MII_ANLPAR          5 | ||||
| #define MII_ANER            6 | ||||
| #define MII_NSR             16 | ||||
| #define MII_LBREMR          17 | ||||
| #define MII_REC             18 | ||||
| #define MII_SNRDR           19 | ||||
| #define MII_TEST            25 | ||||
| 
 | ||||
| /* PHY registers fields */ | ||||
| #define MII_BMCR_RESET      (1 << 15) | ||||
| #define MII_BMCR_LOOPBACK   (1 << 14) | ||||
| #define MII_BMCR_SPEED      (1 << 13) | ||||
| #define MII_BMCR_AUTOEN     (1 << 12) | ||||
| #define MII_BMCR_FD         (1 << 8) | ||||
| 
 | ||||
| #define MII_BMSR_100TX_FD   (1 << 14) | ||||
| #define MII_BMSR_100TX_HD   (1 << 13) | ||||
| #define MII_BMSR_10T_FD     (1 << 12) | ||||
| #define MII_BMSR_10T_HD     (1 << 11) | ||||
| #define MII_BMSR_MFPS       (1 << 6) | ||||
| #define MII_BMSR_AUTONEG    (1 << 3) | ||||
| #define MII_BMSR_LINK_ST    (1 << 2) | ||||
| 
 | ||||
| #define MII_ANAR_TXFD       (1 << 8) | ||||
| #define MII_ANAR_TX         (1 << 7) | ||||
| #define MII_ANAR_10FD       (1 << 6) | ||||
| #define MII_ANAR_10         (1 << 5) | ||||
| #define MII_ANAR_CSMACD     (1 << 0) | ||||
| 
 | ||||
| #define RTL8201CP_PHYID1    0x0000 | ||||
| #define RTL8201CP_PHYID2    0x8201 | ||||
| 
 | ||||
| /* INT CTL and INT STA registers fields */ | ||||
| #define EMAC_INT_TX_CHAN(x) (1 << (x)) | ||||
| #define EMAC_INT_RX         (1 << 8) | ||||
| 
 | ||||
| /* Due to lack of specifications, size of fifos is chosen arbitrarily */ | ||||
| #define TX_FIFO_SIZE        (4 * 1024) | ||||
| #define RX_FIFO_SIZE        (32 * 1024) | ||||
| 
 | ||||
| #define NUM_TX_FIFOS        2 | ||||
| #define RX_HDR_SIZE         8 | ||||
| #define CRC_SIZE            4 | ||||
| 
 | ||||
| #define PHY_REG_SHIFT       0 | ||||
| #define PHY_ADDR_SHIFT      8 | ||||
| 
 | ||||
| typedef struct RTL8201CPState { | ||||
|     uint16_t bmcr; | ||||
|     uint16_t bmsr; | ||||
|     uint16_t anar; | ||||
|     uint16_t anlpar; | ||||
| } RTL8201CPState; | ||||
| 
 | ||||
| typedef struct AwEmacState { | ||||
|     /*< private >*/ | ||||
|     SysBusDevice  parent_obj; | ||||
|     /*< public >*/ | ||||
| 
 | ||||
|     MemoryRegion   iomem; | ||||
|     qemu_irq       irq; | ||||
|     NICState       *nic; | ||||
|     NICConf        conf; | ||||
|     RTL8201CPState mii; | ||||
|     uint8_t        phy_addr; | ||||
| 
 | ||||
|     uint32_t       ctl; | ||||
|     uint32_t       tx_mode; | ||||
|     uint32_t       rx_ctl; | ||||
|     uint32_t       int_ctl; | ||||
|     uint32_t       int_sta; | ||||
|     uint32_t       phy_target; | ||||
| 
 | ||||
|     Fifo8          rx_fifo; | ||||
|     uint32_t       rx_num_packets; | ||||
|     uint32_t       rx_packet_size; | ||||
|     uint32_t       rx_packet_pos; | ||||
| 
 | ||||
|     Fifo8          tx_fifo[NUM_TX_FIFOS]; | ||||
|     uint32_t       tx_length[NUM_TX_FIFOS]; | ||||
|     uint32_t       tx_channel; | ||||
| } AwEmacState; | ||||
| 
 | ||||
| #endif | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Beniamino Galvani
						Beniamino Galvani