/* $NetBSD: dwc_gmac.c,v 1.64.2.1 2020/08/11 17:14:21 martin Exp $ */
/*-
* Copyright (c) 2013, 2014 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Matt Thomas of 3am Software Foundry and Martin Husemann.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* This driver supports the Synopsis Designware GMAC core, as found
* on Allwinner A20 cores and others.
*
* Real documentation seems to not be available, the marketing product
* documents could be found here:
*
* http://www.synopsys.com/dw/ipdir.php?ds=dwc_ether_mac10_100_1000_unive
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(1, "$NetBSD: dwc_gmac.c,v 1.64.2.1 2020/08/11 17:14:21 martin Exp $");
/* #define DWC_GMAC_DEBUG 1 */
#ifdef _KERNEL_OPT
#include "opt_inet.h"
#include "opt_net_mpsafe.h"
#endif
#include <sys/param.h>
#include <sys/bus.h>
#include <sys/device.h>
#include <sys/intr.h>
#include <sys/systm.h>
#include <sys/sockio.h>
#include <sys/cprng.h>
#include <sys/rndsource.h>
#include <net/if.h>
#include <net/if_ether.h>
#include <net/if_media.h>
#include <net/bpf.h>
#ifdef INET
#include <netinet/if_inarp.h>
#endif
#include <dev/mii/miivar.h>
#include <dev/ic/dwc_gmac_reg.h>
#include <dev/ic/dwc_gmac_var.h>
static int dwc_gmac_miibus_read_reg(device_t, int, int, uint16_t *);
static int dwc_gmac_miibus_write_reg(device_t, int, int, uint16_t);
static void dwc_gmac_miibus_statchg(struct ifnet *);
static int dwc_gmac_reset(struct dwc_gmac_softc *);
static void dwc_gmac_write_hwaddr(struct dwc_gmac_softc *, uint8_t *);
static int dwc_gmac_alloc_dma_rings(struct dwc_gmac_softc *);
static void dwc_gmac_free_dma_rings(struct dwc_gmac_softc *);
static int dwc_gmac_alloc_rx_ring(struct dwc_gmac_softc *, struct dwc_gmac_rx_ring *);
static void dwc_gmac_reset_rx_ring(struct dwc_gmac_softc *, struct dwc_gmac_rx_ring *);
static void dwc_gmac_free_rx_ring(struct dwc_gmac_softc *, struct dwc_gmac_rx_ring *);
static int dwc_gmac_alloc_tx_ring(struct dwc_gmac_softc *, struct dwc_gmac_tx_ring *);
static void dwc_gmac_reset_tx_ring(struct dwc_gmac_softc *, struct dwc_gmac_tx_ring *);
static void dwc_gmac_free_tx_ring(struct dwc_gmac_softc *, struct dwc_gmac_tx_ring *);
static void dwc_gmac_txdesc_sync(struct dwc_gmac_softc *, int, int, int);
static int dwc_gmac_init(struct ifnet *);
static int dwc_gmac_init_locked(struct ifnet *);
static void dwc_gmac_stop(struct ifnet *, int);
static void dwc_gmac_stop_locked(struct ifnet *, int);
static void dwc_gmac_start(struct ifnet *);
static void dwc_gmac_start_locked(struct ifnet *);
static int dwc_gmac_queue(struct dwc_gmac_softc *, struct mbuf *);
static int dwc_gmac_ioctl(struct ifnet *, u_long, void *);
static void dwc_gmac_tx_intr(struct dwc_gmac_softc *);
static void dwc_gmac_rx_intr(struct dwc_gmac_softc *);
static void dwc_gmac_setmulti(struct dwc_gmac_softc *);
static int dwc_gmac_ifflags_cb(struct ethercom *);
static uint32_t bitrev32(uint32_t);
static void dwc_gmac_desc_set_owned_by_dev(struct dwc_gmac_dev_dmadesc *);
static int dwc_gmac_desc_is_owned_by_dev(struct dwc_gmac_dev_dmadesc *);
static void dwc_gmac_desc_std_set_len(struct dwc_gmac_dev_dmadesc *, int);
static uint32_t dwc_gmac_desc_std_get_len(struct dwc_gmac_dev_dmadesc *);
static void dwc_gmac_desc_std_tx_init_flags(struct dwc_gmac_dev_dmadesc *);
static void dwc_gmac_desc_std_tx_set_first_frag(struct dwc_gmac_dev_dmadesc *);
static void dwc_gmac_desc_std_tx_set_last_frag(struct dwc_gmac_dev_dmadesc *);
static void dwc_gmac_desc_std_rx_init_flags(struct dwc_gmac_dev_dmadesc *);
static int dwc_gmac_desc_std_rx_has_error(struct dwc_gmac_dev_dmadesc *);
static void dwc_gmac_desc_enh_set_len(struct dwc_gmac_dev_dmadesc *, int);
static uint32_t dwc_gmac_desc_enh_get_len(struct dwc_gmac_dev_dmadesc *);
static void dwc_gmac_desc_enh_tx_init_flags(struct dwc_gmac_dev_dmadesc *);
static void dwc_gmac_desc_enh_tx_set_first_frag(struct dwc_gmac_dev_dmadesc *);
static void dwc_gmac_desc_enh_tx_set_last_frag(struct dwc_gmac_dev_dmadesc *);
static void dwc_gmac_desc_enh_rx_init_flags(struct dwc_gmac_dev_dmadesc *);
static int dwc_gmac_desc_enh_rx_has_error(struct dwc_gmac_dev_dmadesc *);
static const struct dwc_gmac_desc_methods desc_methods_standard = {
.tx_init_flags = dwc_gmac_desc_std_tx_init_flags,
.tx_set_owned_by_dev = dwc_gmac_desc_set_owned_by_dev,
.tx_is_owned_by_dev = dwc_gmac_desc_is_owned_by_dev,
.tx_set_len = dwc_gmac_desc_std_set_len,
.tx_set_first_frag = dwc_gmac_desc_std_tx_set_first_frag,
.tx_set_last_frag = dwc_gmac_desc_std_tx_set_last_frag,
.rx_init_flags = dwc_gmac_desc_std_rx_init_flags,
.rx_set_owned_by_dev = dwc_gmac_desc_set_owned_by_dev,
.rx_is_owned_by_dev = dwc_gmac_desc_is_owned_by_dev,
.rx_set_len = dwc_gmac_desc_std_set_len,
.rx_get_len = dwc_gmac_desc_std_get_len,
.rx_has_error = dwc_gmac_desc_std_rx_has_error
};
static const struct dwc_gmac_desc_methods desc_methods_enhanced = {
.tx_init_flags = dwc_gmac_desc_enh_tx_init_flags,
.tx_set_owned_by_dev = dwc_gmac_desc_set_owned_by_dev,
.tx_is_owned_by_dev = dwc_gmac_desc_is_owned_by_dev,
.tx_set_len = dwc_gmac_desc_enh_set_len,
.tx_set_first_frag = dwc_gmac_desc_enh_tx_set_first_frag,
.tx_set_last_frag = dwc_gmac_desc_enh_tx_set_last_frag,
.rx_init_flags = dwc_gmac_desc_enh_rx_init_flags,
.rx_set_owned_by_dev = dwc_gmac_desc_set_owned_by_dev,
.rx_is_owned_by_dev = dwc_gmac_desc_is_owned_by_dev,
.rx_set_len = dwc_gmac_desc_enh_set_len,
.rx_get_len = dwc_gmac_desc_enh_get_len,
.rx_has_error = dwc_gmac_desc_enh_rx_has_error
};
#define TX_DESC_OFFSET(N) ((AWGE_RX_RING_COUNT+(N)) \
*sizeof(struct dwc_gmac_dev_dmadesc))
#define TX_NEXT(N) (((N)+1) & (AWGE_TX_RING_COUNT-1))
#define RX_DESC_OFFSET(N) ((N)*sizeof(struct dwc_gmac_dev_dmadesc))
#define RX_NEXT(N) (((N)+1) & (AWGE_RX_RING_COUNT-1))
#define GMAC_DEF_DMA_INT_MASK (GMAC_DMA_INT_TIE | GMAC_DMA_INT_RIE | \
GMAC_DMA_INT_NIE | GMAC_DMA_INT_AIE | \
GMAC_DMA_INT_FBE | GMAC_DMA_INT_UNE)
#define GMAC_DMA_INT_ERRORS (GMAC_DMA_INT_AIE | GMAC_DMA_INT_ERE | \
GMAC_DMA_INT_FBE | \
GMAC_DMA_INT_RWE | GMAC_DMA_INT_RUE | \
GMAC_DMA_INT_UNE | GMAC_DMA_INT_OVE | \
GMAC_DMA_INT_TJE)
#define AWIN_DEF_MAC_INTRMASK \
(AWIN_GMAC_MAC_INT_TSI | AWIN_GMAC_MAC_INT_ANEG | \
AWIN_GMAC_MAC_INT_LINKCHG)
#ifdef DWC_GMAC_DEBUG
static void dwc_gmac_dump_dma(struct dwc_gmac_softc *);
static void dwc_gmac_dump_tx_desc(struct dwc_gmac_softc *);
static void dwc_gmac_dump_rx_desc(struct dwc_gmac_softc *);
static void dwc_dump_and_abort(struct dwc_gmac_softc *, const char *);
static void dwc_dump_status(struct dwc_gmac_softc *);
static void dwc_gmac_dump_ffilt(struct dwc_gmac_softc *, uint32_t);
#endif
int
dwc_gmac_attach(struct dwc_gmac_softc *sc, int phy_id, uint32_t mii_clk)
{
uint8_t enaddr[ETHER_ADDR_LEN];
uint32_t maclo, machi, ver, hwft;
struct mii_data * const mii = &sc->sc_mii;
struct ifnet * const ifp = &sc->sc_ec.ec_if;
prop_dictionary_t dict;
int rv;
mutex_init(&sc->sc_mdio_lock, MUTEX_DEFAULT, IPL_NET);
sc->sc_mii_clk = mii_clk & 7;
dict = device_properties(sc->sc_dev);
prop_data_t ea = dict ? prop_dictionary_get(dict, "mac-address") : NULL;
if (ea != NULL) {
/*
* If the MAC address is overriden by a device property,
* use that.
*/
KASSERT(prop_object_type(ea) == PROP_TYPE_DATA);
KASSERT(prop_data_size(ea) == ETHER_ADDR_LEN);
memcpy(enaddr, prop_data_data_nocopy(ea), ETHER_ADDR_LEN);
} else {
/*
* If we did not get an externaly configure address,
* try to read one from the current filter setup,
* before resetting the chip.
*/
maclo = bus_space_read_4(sc->sc_bst, sc->sc_bsh,
AWIN_GMAC_MAC_ADDR0LO);
machi = bus_space_read_4(sc->sc_bst, sc->sc_bsh,
AWIN_GMAC_MAC_ADDR0HI);
if (maclo == 0xffffffff && (machi & 0xffff) == 0xffff) {
/* fake MAC address */
maclo = 0x00f2 | (cprng_strong32() << 16);
machi = cprng_strong32();
}
enaddr[0] = maclo & 0x0ff;
enaddr[1] = (maclo >> 8) & 0x0ff;
enaddr[2] = (maclo >> 16) & 0x0ff;
enaddr[3] = (maclo >> 24) & 0x0ff;
enaddr[4] = machi & 0x0ff;
enaddr[5] = (machi >> 8) & 0x0ff;
}
ver = bus_space_read_4(sc->sc_bst, sc->sc_bsh, AWIN_GMAC_MAC_VERSION);
aprint_normal_dev(sc->sc_dev, "Core version: %08x\n", ver);
/*
* Init chip and do initial setup
*/
if (dwc_gmac_reset(sc) != 0)
return ENXIO; /* not much to cleanup, haven't attached yet */
dwc_gmac_write_hwaddr(sc, enaddr);
aprint_normal_dev(sc->sc_dev, "Ethernet address %s\n",
ether_sprintf(enaddr));
hwft = 0;
if (ver >= 0x35) {
hwft = bus_space_read_4(sc->sc_bst, sc->sc_bsh,
AWIN_GMAC_DMA_HWFEATURES);
aprint_normal_dev(sc->sc_dev,
"HW feature mask: %x\n", hwft);
}
if (hwft & GMAC_DMA_FEAT_ENHANCED_DESC) {
aprint_normal_dev(sc->sc_dev,
"Using enhanced descriptor format\n");
sc->sc_descm = &desc_methods_enhanced;
} else {
sc->sc_descm = &desc_methods_standard;
}
if (hwft & GMAC_DMA_FEAT_RMON) {
uint32_t val;
/* Mask all MMC interrupts */
val = 0xffffffff;
bus_space_write_4(sc->sc_bst, sc->sc_bsh,
GMAC_MMC_RX_INT_MSK, val);
bus_space_write_4(sc->sc_bst, sc->sc_bsh,
GMAC_MMC_TX_INT_MSK, val);
}
/*
* Allocate Tx and Rx rings
*/
if (dwc_gmac_alloc_dma_rings(sc) != 0) {
aprint_error_dev(sc->sc_dev, "could not allocate DMA rings\n");
goto fail;
}
if (dwc_gmac_alloc_tx_ring(sc, &sc->sc_txq) != 0) {
aprint_error_dev(sc->sc_dev, "could not allocate Tx ring\n");
goto fail;
}
if (dwc_gmac_alloc_rx_ring(sc, &sc->sc_rxq) != 0) {
aprint_error_dev(sc->sc_dev, "could not allocate Rx ring\n");
goto fail;
}
sc->sc_lock = mutex_obj_alloc(MUTEX_DEFAULT, IPL_NET);
mutex_init(&sc->sc_txq.t_mtx, MUTEX_DEFAULT, IPL_NET);
mutex_init(&sc->sc_rxq.r_mtx, MUTEX_DEFAULT, IPL_NET);
/*
* Prepare interface data
*/
ifp->if_softc = sc;
strlcpy(ifp->if_xname, device_xname(sc->sc_dev), IFNAMSIZ);
ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
#ifdef DWCGMAC_MPSAFE
ifp->if_extflags = IFEF_MPSAFE;
#endif
ifp->if_ioctl = dwc_gmac_ioctl;
ifp->if_start = dwc_gmac_start;
ifp->if_init = dwc_gmac_init;
ifp->if_stop = dwc_gmac_stop;
IFQ_SET_MAXLEN(&ifp->if_snd, IFQ_MAXLEN);
IFQ_SET_READY(&ifp->if_snd);
/*
* Attach MII subdevices
*/
sc->sc_ec.ec_mii = &sc->sc_mii;
ifmedia_init(&mii->mii_media, 0, ether_mediachange, ether_mediastatus);
mii->mii_ifp = ifp;
mii->mii_readreg = dwc_gmac_miibus_read_reg;
mii->mii_writereg = dwc_gmac_miibus_write_reg;
mii->mii_statchg = dwc_gmac_miibus_statchg;
mii_attach(sc->sc_dev, mii, 0xffffffff, phy_id, MII_OFFSET_ANY,
MIIF_DOPAUSE);
if (LIST_EMPTY(&mii->mii_phys)) {
aprint_error_dev(sc->sc_dev, "no PHY found!\n");
ifmedia_add(&mii->mii_media, IFM_ETHER | IFM_MANUAL, 0, NULL);
ifmedia_set(&mii->mii_media, IFM_ETHER | IFM_MANUAL);
} else {
ifmedia_set(&mii->mii_media, IFM_ETHER | IFM_AUTO);
}
/*
* We can support 802.1Q VLAN-sized frames.
*/
sc->sc_ec.ec_capabilities |= ETHERCAP_VLAN_MTU;
/*
* Ready, attach interface
*/
/* Attach the interface. */
rv = if_initialize(ifp);
if (rv != 0)
goto fail_2;
sc->sc_ipq = if_percpuq_create(&sc->sc_ec.ec_if);
if_deferred_start_init(ifp, NULL);
ether_ifattach(ifp, enaddr);
ether_set_ifflags_cb(&sc->sc_ec, dwc_gmac_ifflags_cb);
if_register(ifp);
rnd_attach_source(&sc->rnd_source, device_xname(sc->sc_dev),
RND_TYPE_NET, RND_FLAG_DEFAULT);
/*
* Enable interrupts
*/
mutex_enter(sc->sc_lock);
bus_space_write_4(sc->sc_bst, sc->sc_bsh, AWIN_GMAC_MAC_INTMASK,
AWIN_DEF_MAC_INTRMASK);
bus_space_write_4(sc->sc_bst, sc->sc_bsh, AWIN_GMAC_DMA_INTENABLE,
GMAC_DEF_DMA_INT_MASK);
mutex_exit(sc->sc_lock);
return 0;
fail_2:
ifmedia_removeall(&mii->mii_media);
mii_detach(mii, MII_PHY_ANY, MII_OFFSET_ANY);
mutex_destroy(&sc->sc_txq.t_mtx);
mutex_destroy(&sc->sc_rxq.r_mtx);
mutex_obj_free(sc->sc_lock);
fail:
dwc_gmac_free_rx_ring(sc, &sc->sc_rxq);
dwc_gmac_free_tx_ring(sc, &sc->sc_txq);
dwc_gmac_free_dma_rings(sc);
mutex_destroy(&sc->sc_mdio_lock);
return ENXIO;
}
static int
dwc_gmac_reset(struct dwc_gmac_softc *sc)
{
size_t cnt;
bus_space_write_4(sc->sc_bst, sc->sc_bsh, AWIN_GMAC_DMA_BUSMODE,
bus_space_read_4(sc->sc_bst, sc->sc_bsh, AWIN_GMAC_DMA_BUSMODE)
| GMAC_BUSMODE_RESET);
for (cnt = 0; cnt < 3000; cnt++) {
if ((bus_space_read_4(sc->sc_bst, sc->sc_bsh, AWIN_GMAC_DMA_BUSMODE)
& GMAC_BUSMODE_RESET) == 0)
return 0;
delay(10);
}
aprint_error_dev(sc->sc_dev, "reset timed out\n");
return EIO;
}
static void
dwc_gmac_write_hwaddr(struct dwc_gmac_softc *sc,
uint8_t enaddr[ETHER_ADDR_LEN])
{
uint32_t hi, lo;
hi = enaddr[4] | (enaddr[5] << 8);
lo = enaddr[0] | (enaddr[1] << 8) | (enaddr[2] << 16)
| (enaddr[3] << 24);
bus_space_write_4(sc->sc_bst, sc->sc_bsh, AWIN_GMAC_MAC_ADDR0HI, hi);
bus_space_write_4(sc->sc_bst, sc->sc_bsh, AWIN_GMAC_MAC_ADDR0LO, lo);
}
static int
dwc_gmac_miibus_read_reg(device_t self, int phy, int reg, uint16_t *val)
{
struct dwc_gmac_softc * const sc = device_private(self);
uint16_t mii;
size_t cnt;
mii = __SHIFTIN(phy, GMAC_MII_PHY_MASK)
| __SHIFTIN(reg, GMAC_MII_REG_MASK)
| __SHIFTIN(sc->sc_mii_clk, GMAC_MII_CLKMASK)
| GMAC_MII_BUSY;
mutex_enter(&sc->sc_mdio_lock);
bus_space_write_4(sc->sc_bst, sc->sc_bsh, AWIN_GMAC_MAC_MIIADDR, mii);
for (cnt = 0; cnt < 1000; cnt++) {
if (!(bus_space_read_4(sc->sc_bst, sc->sc_bsh,
AWIN_GMAC_MAC_MIIADDR) & GMAC_MII_BUSY)) {
*val = bus_space_read_4(sc->sc_bst, sc->sc_bsh,
AWIN_GMAC_MAC_MIIDATA);
break;
}
delay(10);
}
mutex_exit(&sc->sc_mdio_lock);
if (cnt >= 1000)
return ETIMEDOUT;
return 0;
}
static int
dwc_gmac_miibus_write_reg(device_t self, int phy, int reg, uint16_t val)
{
struct dwc_gmac_softc * const sc = device_private(self);
uint16_t mii;
size_t cnt;
mii = __SHIFTIN(phy, GMAC_MII_PHY_MASK)
| __SHIFTIN(reg, GMAC_MII_REG_MASK)
| __SHIFTIN(sc->sc_mii_clk, GMAC_MII_CLKMASK)
| GMAC_MII_BUSY | GMAC_MII_WRITE;
mutex_enter(&sc->sc_mdio_lock);
bus_space_write_4(sc->sc_bst, sc->sc_bsh, AWIN_GMAC_MAC_MIIDATA, val);
bus_space_write_4(sc->sc_bst, sc->sc_bsh, AWIN_GMAC_MAC_MIIADDR, mii);
for (cnt = 0; cnt < 1000; cnt++) {
if (!(bus_space_read_4(sc->sc_bst, sc->sc_bsh,
AWIN_GMAC_MAC_MIIADDR) & GMAC_MII_BUSY))
break;
delay(10);
}
mutex_exit(&sc->sc_mdio_lock);
if (cnt >= 1000)
return ETIMEDOUT;
return 0;
}
static int
dwc_gmac_alloc_rx_ring(struct dwc_gmac_softc *sc,
struct dwc_gmac_rx_ring *ring)
{
struct dwc_gmac_rx_data *data;
bus_addr_t physaddr;
const size_t descsize = AWGE_RX_RING_COUNT * sizeof(*ring->r_desc);
int error, i, next;
ring->r_cur = ring->r_next = 0;
memset(ring->r_desc, 0, descsize);
/*
* Pre-allocate Rx buffers and populate Rx ring.
*/
for (i = 0; i < AWGE_RX_RING_COUNT; i++) {
struct dwc_gmac_dev_dmadesc *desc;
data = &sc->sc_rxq.r_data[i];
MGETHDR(data->rd_m, M_DONTWAIT, MT_DATA);
if (data->rd_m == NULL) {
aprint_error_dev(sc->sc_dev,
"could not allocate rx mbuf #%d\n", i);
error = ENOMEM;
goto fail;
}
error = bus_dmamap_create(sc->sc_dmat, MCLBYTES, 1,
MCLBYTES, 0, BUS_DMA_NOWAIT, &data->rd_map);
if (error != 0) {
aprint_error_dev(sc->sc_dev,
"could not create DMA map\n");
data->rd_map = NULL;
goto fail;
}
MCLGET(data->rd_m, M_DONTWAIT);
if (!(data->rd_m->m_flags & M_EXT)) {
aprint_error_dev(sc->sc_dev,
"could not allocate mbuf cluster #%d\n", i);
error = ENOMEM;
goto fail;
}
data->rd_m->m_len = data->rd_m->m_pkthdr.len
= data->rd_m->m_ext.ext_size;
if (data->rd_m->m_len > AWGE_MAX_PACKET) {
data->rd_m->m_len = data->rd_m->m_pkthdr.len
= AWGE_MAX_PACKET;
}
error = bus_dmamap_load_mbuf(sc->sc_dmat, data->rd_map,
data->rd_m, BUS_DMA_READ | BUS_DMA_NOWAIT);
if (error != 0) {
aprint_error_dev(sc->sc_dev,
"could not load rx buf DMA map #%d", i);
goto fail;
}
bus_dmamap_sync(sc->sc_dmat, data->rd_map, 0,
data->rd_map->dm_mapsize, BUS_DMASYNC_PREREAD);
physaddr = data->rd_map->dm_segs[0].ds_addr;
desc = &sc->sc_rxq.r_desc[i];
desc->ddesc_data = htole32(physaddr);
next = RX_NEXT(i);
desc->ddesc_next = htole32(ring->r_physaddr
+ next * sizeof(*desc));
sc->sc_descm->rx_init_flags(desc);
sc->sc_descm->rx_set_len(desc, data->rd_m->m_len);
sc->sc_descm->rx_set_owned_by_dev(desc);
}
bus_dmamap_sync(sc->sc_dmat, sc->sc_dma_ring_map, 0,
AWGE_RX_RING_COUNT*sizeof(struct dwc_gmac_dev_dmadesc),
BUS_DMASYNC_PREWRITE | BUS_DMASYNC_PREREAD);
bus_space_write_4(sc->sc_bst, sc->sc_bsh, AWIN_GMAC_DMA_RX_ADDR,
ring->r_physaddr);
return 0;
fail:
dwc_gmac_free_rx_ring(sc, ring);
return error;
}
static void
dwc_gmac_reset_rx_ring(struct dwc_gmac_softc *sc,
struct dwc_gmac_rx_ring *ring)
{
struct dwc_gmac_dev_dmadesc *desc;
struct dwc_gmac_rx_data *data;
int i;
mutex_enter(&ring->r_mtx);
for (i = 0; i < AWGE_RX_RING_COUNT; i++) {
desc = &sc->sc_rxq.r_desc[i];
data = &sc->sc_rxq.r_data[i];
sc->sc_descm->rx_init_flags(desc);
sc->sc_descm->rx_set_len(desc, data->rd_m->m_len);
sc->sc_descm->rx_set_owned_by_dev(desc);
}
bus_dmamap_sync(sc->sc_dmat, sc->sc_dma_ring_map, 0,
AWGE_RX_RING_COUNT*sizeof(struct dwc_gmac_dev_dmadesc),
BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
ring->r_cur = ring->r_next = 0;
/* reset DMA address to start of ring */
bus_space_write_4(sc->sc_bst, sc->sc_bsh, AWIN_GMAC_DMA_RX_ADDR,
sc->sc_rxq.r_physaddr);
mutex_exit(&ring->r_mtx);
}
static int
dwc_gmac_alloc_dma_rings(struct dwc_gmac_softc *sc)
{
const size_t descsize = AWGE_TOTAL_RING_COUNT *
sizeof(struct dwc_gmac_dev_dmadesc);
int error, nsegs;
void *rings;
error = bus_dmamap_create(sc->sc_dmat, descsize, 1, descsize, 0,
BUS_DMA_NOWAIT, &sc->sc_dma_ring_map);
if (error != 0) {
aprint_error_dev(sc->sc_dev,
"could not create desc DMA map\n");
sc->sc_dma_ring_map = NULL;
goto fail;
}
error = bus_dmamem_alloc(sc->sc_dmat, descsize, PAGE_SIZE, 0,
&sc->sc_dma_ring_seg, 1, &nsegs, BUS_DMA_NOWAIT |BUS_DMA_COHERENT);
if (error != 0) {
aprint_error_dev(sc->sc_dev,
"could not map DMA memory\n");
goto fail;
}
error = bus_dmamem_map(sc->sc_dmat, &sc->sc_dma_ring_seg, nsegs,
descsize, &rings, BUS_DMA_NOWAIT | BUS_DMA_COHERENT);
if (error != 0) {
aprint_error_dev(sc->sc_dev,
"could not allocate DMA memory\n");
goto fail;
}
error = bus_dmamap_load(sc->sc_dmat, sc->sc_dma_ring_map, rings,
descsize, NULL, BUS_DMA_NOWAIT | BUS_DMA_COHERENT);
if (error != 0) {
aprint_error_dev(sc->sc_dev,
"could not load desc DMA map\n");
goto fail;
}
/* give first AWGE_RX_RING_COUNT to the RX side */
sc->sc_rxq.r_desc = rings;
sc->sc_rxq.r_physaddr = sc->sc_dma_ring_map->dm_segs[0].ds_addr;
/* and next rings to the TX side */
sc->sc_txq.t_desc = sc->sc_rxq.r_desc + AWGE_RX_RING_COUNT;
sc->sc_txq.t_physaddr = sc->sc_rxq.r_physaddr +
AWGE_RX_RING_COUNT*sizeof(struct dwc_gmac_dev_dmadesc);
return 0;
fail:
dwc_gmac_free_dma_rings(sc);
return error;
}
static void
dwc_gmac_free_dma_rings(struct dwc_gmac_softc *sc)
{
bus_dmamap_sync(sc->sc_dmat, sc->sc_dma_ring_map, 0,
sc->sc_dma_ring_map->dm_mapsize, BUS_DMASYNC_POSTWRITE);
bus_dmamap_unload(sc->sc_dmat, sc->sc_dma_ring_map);
bus_dmamem_unmap(sc->sc_dmat, sc->sc_rxq.r_desc,
AWGE_TOTAL_RING_COUNT * sizeof(struct dwc_gmac_dev_dmadesc));
bus_dmamem_free(sc->sc_dmat, &sc->sc_dma_ring_seg, 1);
}
static void
dwc_gmac_free_rx_ring(struct dwc_gmac_softc *sc, struct dwc_gmac_rx_ring *ring)
{
struct dwc_gmac_rx_data *data;
int i;
if (ring->r_desc == NULL)
return;
for (i = 0; i < AWGE_RX_RING_COUNT; i++) {
data = &ring->r_data[i];
if (data->rd_map != NULL) {
bus_dmamap_sync(sc->sc_dmat, data->rd_map, 0,
AWGE_RX_RING_COUNT
*sizeof(struct dwc_gmac_dev_dmadesc),
BUS_DMASYNC_POSTREAD);
bus_dmamap_unload(sc->sc_dmat, data->rd_map);
bus_dmamap_destroy(sc->sc_dmat, data->rd_map);
}
if (data->rd_m != NULL)
m_freem(data->rd_m);
}
}
static int
dwc_gmac_alloc_tx_ring(struct dwc_gmac_softc *sc,
struct dwc_gmac_tx_ring *ring)
{
int i, error = 0;
ring->t_queued = 0;
ring->t_cur = ring->t_next = 0;
memset(ring->t_desc, 0, AWGE_TX_RING_COUNT*sizeof(*ring->t_desc));
bus_dmamap_sync(sc->sc_dmat, sc->sc_dma_ring_map,
TX_DESC_OFFSET(0),
AWGE_TX_RING_COUNT*sizeof(struct dwc_gmac_dev_dmadesc),
BUS_DMASYNC_POSTWRITE);
for (i = 0; i < AWGE_TX_RING_COUNT; i++) {
error = bus_dmamap_create(sc->sc_dmat, MCLBYTES,
AWGE_TX_RING_COUNT, MCLBYTES, 0,
BUS_DMA_NOWAIT | BUS_DMA_COHERENT,
&ring->t_data[i].td_map);
if (error != 0) {
aprint_error_dev(sc->sc_dev,
"could not create TX DMA map #%d\n", i);
ring->t_data[i].td_map = NULL;
goto fail;
}
ring->t_desc[i].ddesc_next = htole32(
ring->t_physaddr + sizeof(struct dwc_gmac_dev_dmadesc)
*TX_NEXT(i));
}
return 0;
fail:
dwc_gmac_free_tx_ring(sc, ring);
return error;
}
static void
dwc_gmac_txdesc_sync(struct dwc_gmac_softc *sc, int start, int end, int ops)
{
/* 'end' is pointing one descriptor beyond the last we want to sync */
if (end > start) {
bus_dmamap_sync(sc->sc_dmat, sc->sc_dma_ring_map,
TX_DESC_OFFSET(start),
TX_DESC_OFFSET(end)-TX_DESC_OFFSET(start),
ops);
return;
}
/* sync from 'start' to end of ring */
bus_dmamap_sync(sc->sc_dmat, sc->sc_dma_ring_map,
TX_DESC_OFFSET(start),
TX_DESC_OFFSET(AWGE_TX_RING_COUNT)-TX_DESC_OFFSET(start),
ops);
if (TX_DESC_OFFSET(end) - TX_DESC_OFFSET(0) > 0) {
/* sync from start of ring to 'end' */
bus_dmamap_sync(sc->sc_dmat, sc->sc_dma_ring_map,
TX_DESC_OFFSET(0),
TX_DESC_OFFSET(end)-TX_DESC_OFFSET(0),
ops);
}
}
static void
dwc_gmac_reset_tx_ring(struct dwc_gmac_softc *sc,
struct dwc_gmac_tx_ring *ring)
{
int i;
mutex_enter(&ring->t_mtx);
for (i = 0; i < AWGE_TX_RING_COUNT; i++) {
struct dwc_gmac_tx_data *data = &ring->t_data[i];
if (data->td_m != NULL) {
bus_dmamap_sync(sc->sc_dmat, data->td_active,
0, data->td_active->dm_mapsize,
BUS_DMASYNC_POSTWRITE);
bus_dmamap_unload(sc->sc_dmat, data->td_active);
m_freem(data->td_m);
data->td_m = NULL;
}
}
bus_dmamap_sync(sc->sc_dmat, sc->sc_dma_ring_map,
TX_DESC_OFFSET(0),
AWGE_TX_RING_COUNT*sizeof(struct dwc_gmac_dev_dmadesc),
BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
bus_space_write_4(sc->sc_bst, sc->sc_bsh, AWIN_GMAC_DMA_TX_ADDR,
sc->sc_txq.t_physaddr);
ring->t_queued = 0;
ring->t_cur = ring->t_next = 0;
mutex_exit(&ring->t_mtx);
}
static void
dwc_gmac_free_tx_ring(struct dwc_gmac_softc *sc,
struct dwc_gmac_tx_ring *ring)
{
int i;
/* unload the maps */
for (i = 0; i < AWGE_TX_RING_COUNT; i++) {
struct dwc_gmac_tx_data *data = &ring->t_data[i];
if (data->td_m != NULL) {
bus_dmamap_sync(sc->sc_dmat, data->td_active,
0, data->td_map->dm_mapsize,
BUS_DMASYNC_POSTWRITE);
bus_dmamap_unload(sc->sc_dmat, data->td_active);
m_freem(data->td_m);
data->td_m = NULL;
}
}
/* and actually free them */
for (i = 0; i < AWGE_TX_RING_COUNT; i++) {
struct dwc_gmac_tx_data *data = &ring->t_data[i];
bus_dmamap_destroy(sc->sc_dmat, data->td_map);
}
}
static void
dwc_gmac_miibus_statchg(struct ifnet *ifp)
{
struct dwc_gmac_softc * const sc = ifp->if_softc;
struct mii_data * const mii = &sc->sc_mii;
uint32_t conf, flow;
/*
* Set MII or GMII interface based on the speed
* negotiated by the PHY.
*/
conf = bus_space_read_4(sc->sc_bst, sc->sc_bsh, AWIN_GMAC_MAC_CONF);
conf &= ~(AWIN_GMAC_MAC_CONF_FES100 | AWIN_GMAC_MAC_CONF_MIISEL
| AWIN_GMAC_MAC_CONF_FULLDPLX);
conf |= AWIN_GMAC_MAC_CONF_FRAMEBURST
| AWIN_GMAC_MAC_CONF_DISABLERXOWN
| AWIN_GMAC_MAC_CONF_DISABLEJABBER
| AWIN_GMAC_MAC_CONF_ACS
| AWIN_GMAC_MAC_CONF_RXENABLE
| AWIN_GMAC_MAC_CONF_TXENABLE;
switch (IFM_SUBTYPE(mii->mii_media_active)) {
case IFM_10_T:
conf |= AWIN_GMAC_MAC_CONF_MIISEL;
break;
case IFM_100_TX:
conf |= AWIN_GMAC_MAC_CONF_FES100 |
AWIN_GMAC_MAC_CONF_MIISEL;
break;
case IFM_1000_T:
break;
}
if (sc->sc_set_speed)
sc->sc_set_speed(sc, IFM_SUBTYPE(mii->mii_media_active));
flow = 0;
if (IFM_OPTIONS(mii->mii_media_active) & IFM_FDX) {
conf |= AWIN_GMAC_MAC_CONF_FULLDPLX;
flow |= __SHIFTIN(0x200, AWIN_GMAC_MAC_FLOWCTRL_PAUSE);
}
if (mii->mii_media_active & IFM_ETH_TXPAUSE) {
flow |= AWIN_GMAC_MAC_FLOWCTRL_TFE;
}
if (mii->mii_media_active & IFM_ETH_RXPAUSE) {
flow |= AWIN_GMAC_MAC_FLOWCTRL_RFE;
}
bus_space_write_4(sc->sc_bst, sc->sc_bsh,
AWIN_GMAC_MAC_FLOWCTRL, flow);
#ifdef DWC_GMAC_DEBUG
aprint_normal_dev(sc->sc_dev,
"setting MAC conf register: %08x\n", conf);
#endif
bus_space_write_4(sc->sc_bst, sc->sc_bsh,
AWIN_GMAC_MAC_CONF, conf);
}
static int
dwc_gmac_init(struct ifnet *ifp)
{
struct dwc_gmac_softc *sc = ifp->if_softc;
mutex_enter(sc->sc_lock);
int ret = dwc_gmac_init_locked(ifp);
mutex_exit(sc->sc_lock);
return ret;
}
static int
dwc_gmac_init_locked(struct ifnet *ifp)
{
struct dwc_gmac_softc *sc = ifp->if_softc;
uint32_t ffilt;
if (ifp->if_flags & IFF_RUNNING)
return 0;
dwc_gmac_stop_locked(ifp, 0);
/*
* Configure DMA burst/transfer mode and RX/TX priorities.
* XXX - the GMAC_BUSMODE_PRIORXTX bits are undocumented.
*/
bus_space_write_4(sc->sc_bst, sc->sc_bsh, AWIN_GMAC_DMA_BUSMODE,
GMAC_BUSMODE_FIXEDBURST | GMAC_BUSMODE_4PBL |
__SHIFTIN(2, GMAC_BUSMODE_RPBL) |
__SHIFTIN(2, GMAC_BUSMODE_PBL));
/*
* Set up address filter
*/
ffilt = bus_space_read_4(sc->sc_bst, sc->sc_bsh, AWIN_GMAC_MAC_FFILT);
if (ifp->if_flags & IFF_PROMISC) {
ffilt |= AWIN_GMAC_MAC_FFILT_PR;
} else {
ffilt &= ~AWIN_GMAC_MAC_FFILT_PR;
}
if (ifp->if_flags & IFF_BROADCAST) {
ffilt &= ~AWIN_GMAC_MAC_FFILT_DBF;
} else {
ffilt |= AWIN_GMAC_MAC_FFILT_DBF;
}
bus_space_write_4(sc->sc_bst, sc->sc_bsh, AWIN_GMAC_MAC_FFILT, ffilt);
/*
* Set up multicast filter
*/
dwc_gmac_setmulti(sc);
/*
* Set up dma pointer for RX and TX ring
*/
bus_space_write_4(sc->sc_bst, sc->sc_bsh, AWIN_GMAC_DMA_RX_ADDR,
sc->sc_rxq.r_physaddr);
bus_space_write_4(sc->sc_bst, sc->sc_bsh, AWIN_GMAC_DMA_TX_ADDR,
sc->sc_txq.t_physaddr);
/*
* Start RX/TX part
*/
uint32_t opmode = GMAC_DMA_OP_RXSTART | GMAC_DMA_OP_TXSTART;
if ((sc->sc_flags & DWC_GMAC_FORCE_THRESH_DMA_MODE) == 0) {
opmode |= GMAC_DMA_OP_RXSTOREFORWARD | GMAC_DMA_OP_TXSTOREFORWARD;
}
bus_space_write_4(sc->sc_bst, sc->sc_bsh, AWIN_GMAC_DMA_OPMODE, opmode);
sc->sc_stopping = false;
ifp->if_flags |= IFF_RUNNING;
ifp->if_flags &= ~IFF_OACTIVE;
return 0;
}
static void
dwc_gmac_start(struct ifnet *ifp)
{
struct dwc_gmac_softc *sc = ifp->if_softc;
#ifdef DWCGMAC_MPSAFE
KASSERT(if_is_mpsafe(ifp));
#endif
mutex_enter(sc->sc_lock);
if (!sc->sc_stopping) {
mutex_enter(&sc->sc_txq.t_mtx);
dwc_gmac_start_locked(ifp);
mutex_exit(&sc->sc_txq.t_mtx);
}
mutex_exit(sc->sc_lock);
}
static void
dwc_gmac_start_locked(struct ifnet *ifp)
{
struct dwc_gmac_softc *sc = ifp->if_softc;
int old = sc->sc_txq.t_queued;
int start = sc->sc_txq.t_cur;
struct mbuf *m0;
if ((ifp->if_flags & (IFF_RUNNING | IFF_OACTIVE)) != IFF_RUNNING)
return;
for (;;) {
IFQ_POLL(&ifp->if_snd, m0);
if (m0 == NULL)
break;
if (dwc_gmac_queue(sc, m0) != 0) {
ifp->if_flags |= IFF_OACTIVE;
break;
}
IFQ_DEQUEUE(&ifp->if_snd, m0);
bpf_mtap(ifp, m0, BPF_D_OUT);
if (sc->sc_txq.t_queued == AWGE_TX_RING_COUNT) {
ifp->if_flags |= IFF_OACTIVE;
break;
}
}
if (sc->sc_txq.t_queued != old) {
/* packets have been queued, kick it off */
dwc_gmac_txdesc_sync(sc, start, sc->sc_txq.t_cur,
BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
#ifdef DWC_GMAC_DEBUG
dwc_dump_status(sc);
#endif
bus_space_write_4(sc->sc_bst, sc->sc_bsh,
AWIN_GMAC_DMA_TXPOLL, ~0U);
}
}
static void
dwc_gmac_stop(struct ifnet *ifp, int disable)
{
struct dwc_gmac_softc *sc = ifp->if_softc;
mutex_enter(sc->sc_lock);
dwc_gmac_stop_locked(ifp, disable);
mutex_exit(sc->sc_lock);
}
static void
dwc_gmac_stop_locked(struct ifnet *ifp, int disable)
{
struct dwc_gmac_softc *sc = ifp->if_softc;
sc->sc_stopping = true;
bus_space_write_4(sc->sc_bst, sc->sc_bsh,
AWIN_GMAC_DMA_OPMODE,
bus_space_read_4(sc->sc_bst, sc->sc_bsh,
AWIN_GMAC_DMA_OPMODE)
& ~(GMAC_DMA_OP_TXSTART | GMAC_DMA_OP_RXSTART));
bus_space_write_4(sc->sc_bst, sc->sc_bsh,
AWIN_GMAC_DMA_OPMODE,
bus_space_read_4(sc->sc_bst, sc->sc_bsh,
AWIN_GMAC_DMA_OPMODE) | GMAC_DMA_OP_FLUSHTX);
mii_down(&sc->sc_mii);
dwc_gmac_reset_tx_ring(sc, &sc->sc_txq);
dwc_gmac_reset_rx_ring(sc, &sc->sc_rxq);
ifp->if_flags &= ~(IFF_RUNNING | IFF_OACTIVE);
}
/*
* Add m0 to the TX ring
*/
static int
dwc_gmac_queue(struct dwc_gmac_softc *sc, struct mbuf *m0)
{
struct dwc_gmac_dev_dmadesc *desc = NULL;
struct dwc_gmac_tx_data *data = NULL;
bus_dmamap_t map;
int error, i, first;
#ifdef DWC_GMAC_DEBUG
aprint_normal_dev(sc->sc_dev,
"dwc_gmac_queue: adding mbuf chain %p\n", m0);
#endif
first = sc->sc_txq.t_cur;
map = sc->sc_txq.t_data[first].td_map;
error = bus_dmamap_load_mbuf(sc->sc_dmat, map, m0,
BUS_DMA_WRITE | BUS_DMA_NOWAIT);
if (error != 0) {
aprint_error_dev(sc->sc_dev, "could not map mbuf "
"(len: %d, error %d)\n", m0->m_pkthdr.len, error);
return error;
}
if (sc->sc_txq.t_queued + map->dm_nsegs > AWGE_TX_RING_COUNT) {
bus_dmamap_unload(sc->sc_dmat, map);
return ENOBUFS;
}
for (i = 0; i < map->dm_nsegs; i++) {
data = &sc->sc_txq.t_data[sc->sc_txq.t_cur];
desc = &sc->sc_txq.t_desc[sc->sc_txq.t_cur];
desc->ddesc_data = htole32(map->dm_segs[i].ds_addr);
#ifdef DWC_GMAC_DEBUG
aprint_normal_dev(sc->sc_dev, "enqueing desc #%d data %08lx "
"len %lu\n", sc->sc_txq.t_cur,
(unsigned long)map->dm_segs[i].ds_addr,
(unsigned long)map->dm_segs[i].ds_len);
#endif
sc->sc_descm->tx_init_flags(desc);
sc->sc_descm->tx_set_len(desc, map->dm_segs[i].ds_len);
if (i == 0)
sc->sc_descm->tx_set_first_frag(desc);
/*
* Defer passing ownership of the first descriptor
* until we are done.
*/
if (i != 0)
sc->sc_descm->tx_set_owned_by_dev(desc);
sc->sc_txq.t_queued++;
sc->sc_txq.t_cur = TX_NEXT(sc->sc_txq.t_cur);
}
sc->sc_descm->tx_set_last_frag(desc);
data->td_m = m0;
data->td_active = map;
bus_dmamap_sync(sc->sc_dmat, map, 0, map->dm_mapsize,
BUS_DMASYNC_PREWRITE);
/* Pass first to device */
sc->sc_descm->tx_set_owned_by_dev(&sc->sc_txq.t_desc[first]);
bus_dmamap_sync(sc->sc_dmat, map, 0, map->dm_mapsize,
BUS_DMASYNC_PREWRITE);
return 0;
}
/*
* If the interface is up and running, only modify the receive
* filter when setting promiscuous or debug mode. Otherwise fall
* through to ether_ioctl, which will reset the chip.
*/
static int
dwc_gmac_ifflags_cb(struct ethercom *ec)
{
struct ifnet *ifp = &ec->ec_if;
struct dwc_gmac_softc *sc = ifp->if_softc;
int ret = 0;
mutex_enter(sc->sc_lock);
int change = ifp->if_flags ^ sc->sc_if_flags;
sc->sc_if_flags = ifp->if_flags;
if ((change & ~(IFF_CANTCHANGE | IFF_DEBUG)) != 0) {
ret = ENETRESET;
goto out;
}
if ((change & IFF_PROMISC) != 0) {
dwc_gmac_setmulti(sc);
}
out:
mutex_exit(sc->sc_lock);
return ret;
}
static int
dwc_gmac_ioctl(struct ifnet *ifp, u_long cmd, void *data)
{
struct dwc_gmac_softc *sc = ifp->if_softc;
int error = 0;
int s = splnet();
error = ether_ioctl(ifp, cmd, data);
#ifdef DWCGMAC_MPSAFE
splx(s);
#endif
if (error == ENETRESET) {
error = 0;
if (cmd != SIOCADDMULTI && cmd != SIOCDELMULTI)
;
else if (ifp->if_flags & IFF_RUNNING) {
/*
* Multicast list has changed; set the hardware filter
* accordingly.
*/
mutex_enter(sc->sc_lock);
dwc_gmac_setmulti(sc);
mutex_exit(sc->sc_lock);
}
}
/* Try to get things going again */
if (ifp->if_flags & IFF_UP)
dwc_gmac_start(ifp);
sc->sc_if_flags = sc->sc_ec.ec_if.if_flags;
#ifndef DWCGMAC_MPSAFE
splx(s);
#endif
return error;
}
static void
dwc_gmac_tx_intr(struct dwc_gmac_softc *sc)
{
struct ifnet *ifp = &sc->sc_ec.ec_if;
struct dwc_gmac_tx_data *data;
struct dwc_gmac_dev_dmadesc *desc;
int i, nsegs;
mutex_enter(&sc->sc_txq.t_mtx);
for (i = sc->sc_txq.t_next; sc->sc_txq.t_queued > 0; i = TX_NEXT(i)) {
#ifdef DWC_GMAC_DEBUG
aprint_normal_dev(sc->sc_dev,
"dwc_gmac_tx_intr: checking desc #%d (t_queued: %d)\n",
i, sc->sc_txq.t_queued);
#endif
/*
* i+1 does not need to be a valid descriptor,
* this is just a special notion to just sync
* a single tx descriptor (i)
*/
dwc_gmac_txdesc_sync(sc, i, i+1,
BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
desc = &sc->sc_txq.t_desc[i];
if (sc->sc_descm->tx_is_owned_by_dev(desc))
break;
data = &sc->sc_txq.t_data[i];
if (data->td_m == NULL)
continue;
ifp->if_opackets++;
nsegs = data->td_active->dm_nsegs;
bus_dmamap_sync(sc->sc_dmat, data->td_active, 0,
data->td_active->dm_mapsize, BUS_DMASYNC_POSTWRITE);
bus_dmamap_unload(sc->sc_dmat, data->td_active);
#ifdef DWC_GMAC_DEBUG
aprint_normal_dev(sc->sc_dev,
"dwc_gmac_tx_intr: done with packet at desc #%d, "
"freeing mbuf %p\n", i, data->td_m);
#endif
m_freem(data->td_m);
data->td_m = NULL;
sc->sc_txq.t_queued -= nsegs;
}
sc->sc_txq.t_next = i;
if (sc->sc_txq.t_queued < AWGE_TX_RING_COUNT) {
ifp->if_flags &= ~IFF_OACTIVE;
}
mutex_exit(&sc->sc_txq.t_mtx);
}
static void
dwc_gmac_rx_intr(struct dwc_gmac_softc *sc)
{
struct ifnet *ifp = &sc->sc_ec.ec_if;
struct dwc_gmac_dev_dmadesc *desc;
struct dwc_gmac_rx_data *data;
bus_addr_t physaddr;
struct mbuf *m, *mnew;
int i, len, error;
mutex_enter(&sc->sc_rxq.r_mtx);
for (i = sc->sc_rxq.r_cur; ; i = RX_NEXT(i)) {
bus_dmamap_sync(sc->sc_dmat, sc->sc_dma_ring_map,
RX_DESC_OFFSET(i), sizeof(*desc),
BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
desc = &sc->sc_rxq.r_desc[i];
data = &sc->sc_rxq.r_data[i];
if (sc->sc_descm->rx_is_owned_by_dev(desc))
break;
if (sc->sc_descm->rx_has_error(desc)) {
#ifdef DWC_GMAC_DEBUG
aprint_normal_dev(sc->sc_dev,
"RX error: descriptor status %08x, skipping\n",
le32toh(desc->ddesc_status0));
#endif
ifp->if_ierrors++;
goto skip;
}
len = sc->sc_descm->rx_get_len(desc);
#ifdef DWC_GMAC_DEBUG
aprint_normal_dev(sc->sc_dev,
"rx int: device is done with descriptor #%d, len: %d\n",
i, len);
#endif
/*
* Try to get a new mbuf before passing this one
* up, if that fails, drop the packet and reuse
* the existing one.
*/
MGETHDR(mnew, M_DONTWAIT, MT_DATA);
if (mnew == NULL) {
ifp->if_ierrors++;
goto skip;
}
MCLGET(mnew, M_DONTWAIT);
if ((mnew->m_flags & M_EXT) == 0) {
m_freem(mnew);
ifp->if_ierrors++;
goto skip;
}
mnew->m_len = mnew->m_pkthdr.len = mnew->m_ext.ext_size;
if (mnew->m_len > AWGE_MAX_PACKET) {
mnew->m_len = mnew->m_pkthdr.len = AWGE_MAX_PACKET;
}
/* unload old DMA map */
bus_dmamap_sync(sc->sc_dmat, data->rd_map, 0,
data->rd_map->dm_mapsize, BUS_DMASYNC_POSTREAD);
bus_dmamap_unload(sc->sc_dmat, data->rd_map);
/* and reload with new mbuf */
error = bus_dmamap_load_mbuf(sc->sc_dmat, data->rd_map,
mnew, BUS_DMA_READ | BUS_DMA_NOWAIT);
if (error != 0) {
m_freem(mnew);
/* try to reload old mbuf */
error = bus_dmamap_load_mbuf(sc->sc_dmat, data->rd_map,
data->rd_m, BUS_DMA_READ | BUS_DMA_NOWAIT);
if (error != 0) {
panic("%s: could not load old rx mbuf",
device_xname(sc->sc_dev));
}
ifp->if_ierrors++;
goto skip;
}
physaddr = data->rd_map->dm_segs[0].ds_addr;
/*
* New mbuf loaded, update RX ring and continue
*/
m = data->rd_m;
data->rd_m = mnew;
desc->ddesc_data = htole32(physaddr);
/* finalize mbuf */
m->m_pkthdr.len = m->m_len = len;
m_set_rcvif(m, ifp);
m->m_flags |= M_HASFCS;
if_percpuq_enqueue(sc->sc_ipq, m);
skip:
bus_dmamap_sync(sc->sc_dmat, data->rd_map, 0,
data->rd_map->dm_mapsize, BUS_DMASYNC_PREREAD);
sc->sc_descm->rx_init_flags(desc);
sc->sc_descm->rx_set_len(desc, data->rd_m->m_len);
sc->sc_descm->rx_set_owned_by_dev(desc);
bus_dmamap_sync(sc->sc_dmat, sc->sc_dma_ring_map,
RX_DESC_OFFSET(i), sizeof(*desc),
BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
}
/* update RX pointer */
sc->sc_rxq.r_cur = i;
mutex_exit(&sc->sc_rxq.r_mtx);
}
/*
* Reverse order of bits - http://aggregate.org/MAGIC/#Bit%20Reversal
*/
static uint32_t
bitrev32(uint32_t x)
{
x = (((x & 0xaaaaaaaa) >> 1) | ((x & 0x55555555) << 1));
x = (((x & 0xcccccccc) >> 2) | ((x & 0x33333333) << 2));
x = (((x & 0xf0f0f0f0) >> 4) | ((x & 0x0f0f0f0f) << 4));
x = (((x & 0xff00ff00) >> 8) | ((x & 0x00ff00ff) << 8));
return (x >> 16) | (x << 16);
}
static void
dwc_gmac_setmulti(struct dwc_gmac_softc *sc)
{
struct ifnet * const ifp = &sc->sc_ec.ec_if;
struct ether_multi *enm;
struct ether_multistep step;
struct ethercom *ec = &sc->sc_ec;
uint32_t hashes[2] = { 0, 0 };
uint32_t ffilt, h;
int mcnt;
KASSERT(mutex_owned(sc->sc_lock));
ffilt = bus_space_read_4(sc->sc_bst, sc->sc_bsh, AWIN_GMAC_MAC_FFILT);
if (ifp->if_flags & IFF_PROMISC) {
ffilt |= AWIN_GMAC_MAC_FFILT_PR;
goto special_filter;
}
ffilt &= ~(AWIN_GMAC_MAC_FFILT_PM | AWIN_GMAC_MAC_FFILT_PR);
bus_space_write_4(sc->sc_bst, sc->sc_bsh, AWIN_GMAC_MAC_HTLOW, 0);
bus_space_write_4(sc->sc_bst, sc->sc_bsh, AWIN_GMAC_MAC_HTHIGH, 0);
ETHER_LOCK(ec);
ec->ec_flags &= ~ETHER_F_ALLMULTI;
ETHER_FIRST_MULTI(step, ec, enm);
mcnt = 0;
while (enm != NULL) {
if (memcmp(enm->enm_addrlo, enm->enm_addrhi,
ETHER_ADDR_LEN) != 0) {
ffilt |= AWIN_GMAC_MAC_FFILT_PM;
ec->ec_flags |= ETHER_F_ALLMULTI;
ETHER_UNLOCK(ec);
goto special_filter;
}
h = bitrev32(
~ether_crc32_le(enm->enm_addrlo, ETHER_ADDR_LEN)
) >> 26;
hashes[h >> 5] |= (1 << (h & 0x1f));
mcnt++;
ETHER_NEXT_MULTI(step, enm);
}
ETHER_UNLOCK(ec);
if (mcnt)
ffilt |= AWIN_GMAC_MAC_FFILT_HMC;
else
ffilt &= ~AWIN_GMAC_MAC_FFILT_HMC;
bus_space_write_4(sc->sc_bst, sc->sc_bsh, AWIN_GMAC_MAC_FFILT, ffilt);
bus_space_write_4(sc->sc_bst, sc->sc_bsh, AWIN_GMAC_MAC_HTLOW,
hashes[0]);
bus_space_write_4(sc->sc_bst, sc->sc_bsh, AWIN_GMAC_MAC_HTHIGH,
hashes[1]);
sc->sc_if_flags = ifp->if_flags;
#ifdef DWC_GMAC_DEBUG
dwc_gmac_dump_ffilt(sc, ffilt);
#endif
return;
special_filter:
#ifdef DWC_GMAC_DEBUG
dwc_gmac_dump_ffilt(sc, ffilt);
#endif
/* no MAC hashes, ALLMULTI or PROMISC */
bus_space_write_4(sc->sc_bst, sc->sc_bsh, AWIN_GMAC_MAC_FFILT,
ffilt);
bus_space_write_4(sc->sc_bst, sc->sc_bsh, AWIN_GMAC_MAC_HTLOW,
0xffffffff);
bus_space_write_4(sc->sc_bst, sc->sc_bsh, AWIN_GMAC_MAC_HTHIGH,
0xffffffff);
sc->sc_if_flags = sc->sc_ec.ec_if.if_flags;
}
int
dwc_gmac_intr(struct dwc_gmac_softc *sc)
{
uint32_t status, dma_status;
int rv = 0;
if (sc->sc_stopping)
return 0;
status = bus_space_read_4(sc->sc_bst, sc->sc_bsh, AWIN_GMAC_MAC_INTR);
if (status & AWIN_GMAC_MII_IRQ) {
(void)bus_space_read_4(sc->sc_bst, sc->sc_bsh,
AWIN_GMAC_MII_STATUS);
rv = 1;
mii_pollstat(&sc->sc_mii);
}
dma_status = bus_space_read_4(sc->sc_bst, sc->sc_bsh,
AWIN_GMAC_DMA_STATUS);
if (dma_status & (GMAC_DMA_INT_NIE | GMAC_DMA_INT_AIE))
rv = 1;
if (dma_status & GMAC_DMA_INT_TIE)
dwc_gmac_tx_intr(sc);
if (dma_status & GMAC_DMA_INT_RIE)
dwc_gmac_rx_intr(sc);
/*
* Check error conditions
*/
if (dma_status & GMAC_DMA_INT_ERRORS) {
sc->sc_ec.ec_if.if_oerrors++;
#ifdef DWC_GMAC_DEBUG
dwc_dump_and_abort(sc, "interrupt error condition");
#endif
}
rnd_add_uint32(&sc->rnd_source, dma_status);
/* ack interrupt */
if (dma_status)
bus_space_write_4(sc->sc_bst, sc->sc_bsh,
AWIN_GMAC_DMA_STATUS, dma_status & GMAC_DMA_INT_MASK);
/*
* Get more packets
*/
if (rv)
if_schedule_deferred_start(&sc->sc_ec.ec_if);
return rv;
}
static void
dwc_gmac_desc_set_owned_by_dev(struct dwc_gmac_dev_dmadesc *desc)
{
desc->ddesc_status0 |= htole32(DDESC_STATUS_OWNEDBYDEV);
}
static int
dwc_gmac_desc_is_owned_by_dev(struct dwc_gmac_dev_dmadesc *desc)
{
return !!(le32toh(desc->ddesc_status0) & DDESC_STATUS_OWNEDBYDEV);
}
static void
dwc_gmac_desc_std_set_len(struct dwc_gmac_dev_dmadesc *desc, int len)
{
uint32_t cntl = le32toh(desc->ddesc_cntl1);
desc->ddesc_cntl1 = htole32((cntl & ~DDESC_CNTL_SIZE1MASK) |
__SHIFTIN(len, DDESC_CNTL_SIZE1MASK));
}
static uint32_t
dwc_gmac_desc_std_get_len(struct dwc_gmac_dev_dmadesc *desc)
{
return __SHIFTOUT(le32toh(desc->ddesc_status0), DDESC_STATUS_FRMLENMSK);
}
static void
dwc_gmac_desc_std_tx_init_flags(struct dwc_gmac_dev_dmadesc *desc)
{
desc->ddesc_status0 = 0;
desc->ddesc_cntl1 = htole32(DDESC_CNTL_TXCHAIN);
}
static void
dwc_gmac_desc_std_tx_set_first_frag(struct dwc_gmac_dev_dmadesc *desc)
{
uint32_t cntl = le32toh(desc->ddesc_cntl1);
desc->ddesc_cntl1 = htole32(cntl | DDESC_CNTL_TXFIRST);
}
static void
dwc_gmac_desc_std_tx_set_last_frag(struct dwc_gmac_dev_dmadesc *desc)
{
uint32_t cntl = le32toh(desc->ddesc_cntl1);
desc->ddesc_cntl1 = htole32(cntl |
DDESC_CNTL_TXLAST | DDESC_CNTL_TXINT);
}
static void
dwc_gmac_desc_std_rx_init_flags(struct dwc_gmac_dev_dmadesc *desc)
{
desc->ddesc_status0 = 0;
desc->ddesc_cntl1 = htole32(DDESC_CNTL_TXCHAIN);
}
static int
dwc_gmac_desc_std_rx_has_error(struct dwc_gmac_dev_dmadesc *desc) {
return !!(le32toh(desc->ddesc_status0) &
(DDESC_STATUS_RXERROR | DDESC_STATUS_RXTRUNCATED));
}
static void
dwc_gmac_desc_enh_set_len(struct dwc_gmac_dev_dmadesc *desc, int len)
{
uint32_t tdes1 = le32toh(desc->ddesc_cntl1);
desc->ddesc_cntl1 = htole32((tdes1 & ~DDESC_DES1_SIZE1MASK) |
__SHIFTIN(len, DDESC_DES1_SIZE1MASK));
}
static uint32_t
dwc_gmac_desc_enh_get_len(struct dwc_gmac_dev_dmadesc *desc)
{
return __SHIFTOUT(le32toh(desc->ddesc_status0), DDESC_RDES0_FL);
}
static void
dwc_gmac_desc_enh_tx_init_flags(struct dwc_gmac_dev_dmadesc *desc)
{
desc->ddesc_status0 = htole32(DDESC_TDES0_TCH);
desc->ddesc_cntl1 = 0;
}
static void
dwc_gmac_desc_enh_tx_set_first_frag(struct dwc_gmac_dev_dmadesc *desc)
{
uint32_t tdes0 = le32toh(desc->ddesc_status0);
desc->ddesc_status0 = htole32(tdes0 | DDESC_TDES0_FS);
}
static void
dwc_gmac_desc_enh_tx_set_last_frag(struct dwc_gmac_dev_dmadesc *desc)
{
uint32_t tdes0 = le32toh(desc->ddesc_status0);
desc->ddesc_status0 = htole32(tdes0 | DDESC_TDES0_LS | DDESC_TDES0_IC);
}
static void
dwc_gmac_desc_enh_rx_init_flags(struct dwc_gmac_dev_dmadesc *desc)
{
desc->ddesc_status0 = 0;
desc->ddesc_cntl1 = htole32(DDESC_RDES1_RCH);
}
static int
dwc_gmac_desc_enh_rx_has_error(struct dwc_gmac_dev_dmadesc *desc)
{
return !!(le32toh(desc->ddesc_status0) &
(DDESC_RDES0_ES | DDESC_RDES0_LE));
}
#ifdef DWC_GMAC_DEBUG
static void
dwc_gmac_dump_dma(struct dwc_gmac_softc *sc)
{
aprint_normal_dev(sc->sc_dev, "busmode: %08x\n",
bus_space_read_4(sc->sc_bst, sc->sc_bsh, AWIN_GMAC_DMA_BUSMODE));
aprint_normal_dev(sc->sc_dev, "tx poll: %08x\n",
bus_space_read_4(sc->sc_bst, sc->sc_bsh, AWIN_GMAC_DMA_TXPOLL));
aprint_normal_dev(sc->sc_dev, "rx poll: %08x\n",
bus_space_read_4(sc->sc_bst, sc->sc_bsh, AWIN_GMAC_DMA_RXPOLL));
aprint_normal_dev(sc->sc_dev, "rx descriptors: %08x\n",
bus_space_read_4(sc->sc_bst, sc->sc_bsh, AWIN_GMAC_DMA_RX_ADDR));
aprint_normal_dev(sc->sc_dev, "tx descriptors: %08x\n",
bus_space_read_4(sc->sc_bst, sc->sc_bsh, AWIN_GMAC_DMA_TX_ADDR));
aprint_normal_dev(sc->sc_dev, "status: %08x\n",
bus_space_read_4(sc->sc_bst, sc->sc_bsh, AWIN_GMAC_DMA_STATUS));
aprint_normal_dev(sc->sc_dev, "op mode: %08x\n",
bus_space_read_4(sc->sc_bst, sc->sc_bsh, AWIN_GMAC_DMA_OPMODE));
aprint_normal_dev(sc->sc_dev, "int enable: %08x\n",
bus_space_read_4(sc->sc_bst, sc->sc_bsh, AWIN_GMAC_DMA_INTENABLE));
aprint_normal_dev(sc->sc_dev, "cur tx: %08x\n",
bus_space_read_4(sc->sc_bst, sc->sc_bsh, AWIN_GMAC_DMA_CUR_TX_DESC));
aprint_normal_dev(sc->sc_dev, "cur rx: %08x\n",
bus_space_read_4(sc->sc_bst, sc->sc_bsh, AWIN_GMAC_DMA_CUR_RX_DESC));
aprint_normal_dev(sc->sc_dev, "cur tx buffer: %08x\n",
bus_space_read_4(sc->sc_bst, sc->sc_bsh, AWIN_GMAC_DMA_CUR_TX_BUFADDR));
aprint_normal_dev(sc->sc_dev, "cur rx buffer: %08x\n",
bus_space_read_4(sc->sc_bst, sc->sc_bsh, AWIN_GMAC_DMA_CUR_RX_BUFADDR));
}
static void
dwc_gmac_dump_tx_desc(struct dwc_gmac_softc *sc)
{
int i;
aprint_normal_dev(sc->sc_dev, "TX queue: cur=%d, next=%d, queued=%d\n",
sc->sc_txq.t_cur, sc->sc_txq.t_next, sc->sc_txq.t_queued);
aprint_normal_dev(sc->sc_dev, "TX DMA descriptors:\n");
for (i = 0; i < AWGE_TX_RING_COUNT; i++) {
struct dwc_gmac_dev_dmadesc *desc = &sc->sc_txq.t_desc[i];
aprint_normal("#%d (%08lx): status: %08x cntl: %08x "
"data: %08x next: %08x\n",
i, sc->sc_txq.t_physaddr +
i*sizeof(struct dwc_gmac_dev_dmadesc),
le32toh(desc->ddesc_status0), le32toh(desc->ddesc_cntl1),
le32toh(desc->ddesc_data), le32toh(desc->ddesc_next));
}
}
static void
dwc_gmac_dump_rx_desc(struct dwc_gmac_softc *sc)
{
int i;
aprint_normal_dev(sc->sc_dev, "RX queue: cur=%d, next=%d\n",
sc->sc_rxq.r_cur, sc->sc_rxq.r_next);
aprint_normal_dev(sc->sc_dev, "RX DMA descriptors:\n");
for (i = 0; i < AWGE_RX_RING_COUNT; i++) {
struct dwc_gmac_dev_dmadesc *desc = &sc->sc_rxq.r_desc[i];
aprint_normal("#%d (%08lx): status: %08x cntl: %08x "
"data: %08x next: %08x\n",
i, sc->sc_rxq.r_physaddr +
i*sizeof(struct dwc_gmac_dev_dmadesc),
le32toh(desc->ddesc_status0), le32toh(desc->ddesc_cntl1),
le32toh(desc->ddesc_data), le32toh(desc->ddesc_next));
}
}
static void
dwc_dump_status(struct dwc_gmac_softc *sc)
{
uint32_t status = bus_space_read_4(sc->sc_bst, sc->sc_bsh,
AWIN_GMAC_MAC_INTR);
uint32_t dma_status = bus_space_read_4(sc->sc_bst, sc->sc_bsh,
AWIN_GMAC_DMA_STATUS);
char buf[200];
/* print interrupt state */
snprintb(buf, sizeof(buf), "\177\20"
"b\x10""NI\0"
"b\x0f""AI\0"
"b\x0e""ER\0"
"b\x0d""FB\0"
"b\x0a""ET\0"
"b\x09""RW\0"
"b\x08""RS\0"
"b\x07""RU\0"
"b\x06""RI\0"
"b\x05""UN\0"
"b\x04""OV\0"
"b\x03""TJ\0"
"b\x02""TU\0"
"b\x01""TS\0"
"b\x00""TI\0"
"\0", dma_status);
aprint_normal_dev(sc->sc_dev, "INTR status: %08x, DMA status: %s\n",
status, buf);
}
static void
dwc_dump_and_abort(struct dwc_gmac_softc *sc, const char *msg)
{
dwc_dump_status(sc);
dwc_gmac_dump_ffilt(sc,
bus_space_read_4(sc->sc_bst, sc->sc_bsh, AWIN_GMAC_MAC_FFILT));
dwc_gmac_dump_dma(sc);
dwc_gmac_dump_tx_desc(sc);
dwc_gmac_dump_rx_desc(sc);
panic("%s", msg);
}
static void dwc_gmac_dump_ffilt(struct dwc_gmac_softc *sc, uint32_t ffilt)
{
char buf[200];
/* print filter setup */
snprintb(buf, sizeof(buf), "\177\20"
"b\x1f""RA\0"
"b\x0a""HPF\0"
"b\x09""SAF\0"
"b\x08""SAIF\0"
"b\x05""DBF\0"
"b\x04""PM\0"
"b\x03""DAIF\0"
"b\x02""HMC\0"
"b\x01""HUC\0"
"b\x00""PR\0"
"\0", ffilt);
aprint_normal_dev(sc->sc_dev, "FFILT: %s\n", buf);
}
#endif