freebsd-dev/sys/dev/mgb/if_mgb.c

1638 lines
42 KiB
C
Raw Normal View History

/*-
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2019 The FreeBSD Foundation, Inc.
*
* This driver was written by Gerald ND Aryeetey <gndaryee@uwaterloo.ca>
* under sponsorship from the FreeBSD Foundation.
*
* 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 AUTHOR 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 AUTHOR 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.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
/*
* Microchip LAN7430/LAN7431 PCIe to Gigabit Ethernet Controller driver.
*
* Product information:
* LAN7430 https://www.microchip.com/wwwproducts/en/LAN7430
* - Integrated IEEE 802.3 compliant PHY
* LAN7431 https://www.microchip.com/wwwproducts/en/LAN7431
* - RGMII Interface
*
* This driver uses the iflib interface and the default 'ukphy' PHY driver.
*
* UNIMPLEMENTED FEATURES
* ----------------------
* A number of features supported by LAN743X device are not yet implemented in
* this driver:
*
* - Multiple (up to 4) RX queues support
* - Just needs to remove asserts and malloc multiple `rx_ring_data`
* structs based on ncpus.
* - RX/TX Checksum Offloading support
* - VLAN support
* - Receive Packet Filtering (Multicast Perfect/Hash Address) support
* - Wake on LAN (WoL) support
* - TX LSO support
* - Receive Side Scaling (RSS) support
* - Debugging Capabilities:
* - Could include MAC statistics and
* error status registers in sysctl.
*/
#include <sys/param.h>
#include <sys/bus.h>
#include <sys/endian.h>
#include <sys/kdb.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/rman.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <machine/bus.h>
#include <machine/resource.h>
#include <net/ethernet.h>
#include <net/if.h>
#include <net/if_var.h>
#include <net/if_types.h>
#include <net/if_media.h>
#include <net/iflib.h>
#include <dev/mgb/if_mgb.h>
#include <dev/mii/mii.h>
#include <dev/mii/miivar.h>
#include <dev/pci/pcireg.h>
#include <dev/pci/pcivar.h>
#include "ifdi_if.h"
#include "miibus_if.h"
static pci_vendor_info_t mgb_vendor_info_array[] = {
PVID(MGB_MICROCHIP_VENDOR_ID, MGB_LAN7430_DEVICE_ID,
"Microchip LAN7430 PCIe Gigabit Ethernet Controller"),
PVID(MGB_MICROCHIP_VENDOR_ID, MGB_LAN7431_DEVICE_ID,
"Microchip LAN7431 PCIe Gigabit Ethernet Controller"),
PVID_END
};
/* Device methods */
static device_register_t mgb_register;
/* IFLIB methods */
static ifdi_attach_pre_t mgb_attach_pre;
static ifdi_attach_post_t mgb_attach_post;
static ifdi_detach_t mgb_detach;
static ifdi_tx_queues_alloc_t mgb_tx_queues_alloc;
static ifdi_rx_queues_alloc_t mgb_rx_queues_alloc;
static ifdi_queues_free_t mgb_queues_free;
static ifdi_init_t mgb_init;
static ifdi_stop_t mgb_stop;
static ifdi_msix_intr_assign_t mgb_msix_intr_assign;
static ifdi_tx_queue_intr_enable_t mgb_tx_queue_intr_enable;
static ifdi_rx_queue_intr_enable_t mgb_rx_queue_intr_enable;
static ifdi_intr_enable_t mgb_intr_enable_all;
static ifdi_intr_disable_t mgb_intr_disable_all;
/* IFLIB_TXRX methods */
static int mgb_isc_txd_encap(void *,
if_pkt_info_t);
static void mgb_isc_txd_flush(void *,
uint16_t, qidx_t);
static int mgb_isc_txd_credits_update(void *,
uint16_t, bool);
static int mgb_isc_rxd_available(void *,
uint16_t, qidx_t, qidx_t);
static int mgb_isc_rxd_pkt_get(void *,
if_rxd_info_t);
static void mgb_isc_rxd_refill(void *,
if_rxd_update_t);
static void mgb_isc_rxd_flush(void *,
uint16_t, uint8_t, qidx_t);
/* Interrupts */
static driver_filter_t mgb_legacy_intr;
static driver_filter_t mgb_admin_intr;
static driver_filter_t mgb_rxq_intr;
static bool mgb_intr_test(struct mgb_softc *);
/* MII methods */
static miibus_readreg_t mgb_miibus_readreg;
static miibus_writereg_t mgb_miibus_writereg;
static miibus_linkchg_t mgb_miibus_linkchg;
static miibus_statchg_t mgb_miibus_statchg;
static int mgb_media_change(if_t);
static void mgb_media_status(if_t,
struct ifmediareq *);
/* Helper/Test functions */
static int mgb_test_bar(struct mgb_softc *);
static int mgb_alloc_regs(struct mgb_softc *);
static int mgb_release_regs(struct mgb_softc *);
static void mgb_get_ethaddr(struct mgb_softc *,
struct ether_addr *);
static int mgb_wait_for_bits(struct mgb_softc *,
int, int, int);
/* H/W init, reset and teardown helpers */
static int mgb_hw_init(struct mgb_softc *);
static int mgb_hw_teardown(struct mgb_softc *);
static int mgb_hw_reset(struct mgb_softc *);
static int mgb_mac_init(struct mgb_softc *);
static int mgb_dmac_reset(struct mgb_softc *);
static int mgb_phy_reset(struct mgb_softc *);
static int mgb_dma_init(struct mgb_softc *);
static int mgb_dma_tx_ring_init(struct mgb_softc *,
int);
static int mgb_dma_rx_ring_init(struct mgb_softc *,
int);
static int mgb_dmac_control(struct mgb_softc *,
int, int, enum mgb_dmac_cmd);
static int mgb_fct_control(struct mgb_softc *,
int, int, enum mgb_fct_cmd);
/*********************************************************************
* FreeBSD Device Interface Entry Points
*********************************************************************/
static device_method_t mgb_methods[] = {
/* Device interface */
DEVMETHOD(device_register, mgb_register),
DEVMETHOD(device_probe, iflib_device_probe),
DEVMETHOD(device_attach, iflib_device_attach),
DEVMETHOD(device_detach, iflib_device_detach),
DEVMETHOD(device_shutdown, iflib_device_shutdown),
DEVMETHOD(device_suspend, iflib_device_suspend),
DEVMETHOD(device_resume, iflib_device_resume),
/* MII Interface */
DEVMETHOD(miibus_readreg, mgb_miibus_readreg),
DEVMETHOD(miibus_writereg, mgb_miibus_writereg),
DEVMETHOD(miibus_linkchg, mgb_miibus_linkchg),
DEVMETHOD(miibus_statchg, mgb_miibus_statchg),
DEVMETHOD_END
};
static driver_t mgb_driver = {
"mgb", mgb_methods, sizeof(struct mgb_softc)
};
devclass_t mgb_devclass;
DRIVER_MODULE(mgb, pci, mgb_driver, mgb_devclass, NULL, NULL);
IFLIB_PNP_INFO(pci, mgb, mgb_vendor_info_array);
MODULE_VERSION(mgb, 1);
#if 0 /* MIIBUS_DEBUG */
/* If MIIBUS debug stuff is in attach then order matters. Use below instead. */
DRIVER_MODULE_ORDERED(miibus, mgb, miibus_driver, miibus_devclass, NULL, NULL,
SI_ORDER_ANY);
#endif /* MIIBUS_DEBUG */
DRIVER_MODULE(miibus, mgb, miibus_driver, miibus_devclass, NULL, NULL);
MODULE_DEPEND(mgb, pci, 1, 1, 1);
MODULE_DEPEND(mgb, ether, 1, 1, 1);
MODULE_DEPEND(mgb, miibus, 1, 1, 1);
MODULE_DEPEND(mgb, iflib, 1, 1, 1);
static device_method_t mgb_iflib_methods[] = {
DEVMETHOD(ifdi_attach_pre, mgb_attach_pre),
DEVMETHOD(ifdi_attach_post, mgb_attach_post),
DEVMETHOD(ifdi_detach, mgb_detach),
DEVMETHOD(ifdi_init, mgb_init),
DEVMETHOD(ifdi_stop, mgb_stop),
DEVMETHOD(ifdi_tx_queues_alloc, mgb_tx_queues_alloc),
DEVMETHOD(ifdi_rx_queues_alloc, mgb_rx_queues_alloc),
DEVMETHOD(ifdi_queues_free, mgb_queues_free),
DEVMETHOD(ifdi_msix_intr_assign, mgb_msix_intr_assign),
DEVMETHOD(ifdi_tx_queue_intr_enable, mgb_tx_queue_intr_enable),
DEVMETHOD(ifdi_rx_queue_intr_enable, mgb_rx_queue_intr_enable),
DEVMETHOD(ifdi_intr_enable, mgb_intr_enable_all),
DEVMETHOD(ifdi_intr_disable, mgb_intr_disable_all),
#if 0 /* Not yet implemented IFLIB methods */
/*
* Set multicast addresses, mtu and promiscuous mode
*/
DEVMETHOD(ifdi_multi_set, mgb_multi_set),
DEVMETHOD(ifdi_mtu_set, mgb_mtu_set),
DEVMETHOD(ifdi_promisc_set, mgb_promisc_set),
/*
* Needed for VLAN support
*/
DEVMETHOD(ifdi_vlan_register, mgb_vlan_register),
DEVMETHOD(ifdi_vlan_unregister, mgb_vlan_unregister),
/*
* Needed for WOL support
* at the very least.
*/
DEVMETHOD(ifdi_shutdown, mgb_shutdown),
DEVMETHOD(ifdi_suspend, mgb_suspend),
DEVMETHOD(ifdi_resume, mgb_resume),
#endif /* UNUSED_IFLIB_METHODS */
DEVMETHOD_END
};
static driver_t mgb_iflib_driver = {
"mgb", mgb_iflib_methods, sizeof(struct mgb_softc)
};
struct if_txrx mgb_txrx = {
.ift_txd_encap = mgb_isc_txd_encap,
.ift_txd_flush = mgb_isc_txd_flush,
.ift_txd_credits_update = mgb_isc_txd_credits_update,
.ift_rxd_available = mgb_isc_rxd_available,
.ift_rxd_pkt_get = mgb_isc_rxd_pkt_get,
.ift_rxd_refill = mgb_isc_rxd_refill,
.ift_rxd_flush = mgb_isc_rxd_flush,
.ift_legacy_intr = mgb_legacy_intr
};
struct if_shared_ctx mgb_sctx_init = {
.isc_magic = IFLIB_MAGIC,
.isc_q_align = PAGE_SIZE,
.isc_admin_intrcnt = 1,
.isc_flags = IFLIB_DRIVER_MEDIA /* | IFLIB_HAS_RXCQ | IFLIB_HAS_TXCQ*/,
.isc_vendor_info = mgb_vendor_info_array,
.isc_driver_version = "1",
.isc_driver = &mgb_iflib_driver,
/* 2 queues per set for TX and RX (ring queue, head writeback queue) */
.isc_ntxqs = 2,
.isc_tx_maxsize = MGB_DMA_MAXSEGS * MCLBYTES,
/* .isc_tx_nsegments = MGB_DMA_MAXSEGS, */
.isc_tx_maxsegsize = MCLBYTES,
.isc_ntxd_min = {1, 1}, /* Will want to make this bigger */
.isc_ntxd_max = {MGB_DMA_RING_SIZE, 1},
.isc_ntxd_default = {MGB_DMA_RING_SIZE, 1},
.isc_nrxqs = 2,
.isc_rx_maxsize = MCLBYTES,
.isc_rx_nsegments = 1,
.isc_rx_maxsegsize = MCLBYTES,
.isc_nrxd_min = {1, 1}, /* Will want to make this bigger */
.isc_nrxd_max = {MGB_DMA_RING_SIZE, 1},
.isc_nrxd_default = {MGB_DMA_RING_SIZE, 1},
.isc_nfl = 1, /*one free list since there is only one queue */
#if 0 /* UNUSED_CTX */
.isc_tso_maxsize = MGB_TSO_MAXSIZE + sizeof(struct ether_vlan_header),
.isc_tso_maxsegsize = MGB_TX_MAXSEGSIZE,
#endif /* UNUSED_CTX */
};
/*********************************************************************/
static void *
mgb_register(device_t dev)
{
return (&mgb_sctx_init);
}
static int
mgb_attach_pre(if_ctx_t ctx)
{
struct mgb_softc *sc;
if_softc_ctx_t scctx;
int error, phyaddr, rid;
struct ether_addr hwaddr;
struct mii_data *miid;
sc = iflib_get_softc(ctx);
sc->ctx = ctx;
sc->dev = iflib_get_dev(ctx);
scctx = iflib_get_softc_ctx(ctx);
/* IFLIB required setup */
scctx->isc_txrx = &mgb_txrx;
scctx->isc_tx_nsegments = MGB_DMA_MAXSEGS;
/* Ring desc queues */
scctx->isc_txqsizes[0] = sizeof(struct mgb_ring_desc) *
scctx->isc_ntxd[0];
scctx->isc_rxqsizes[0] = sizeof(struct mgb_ring_desc) *
scctx->isc_nrxd[0];
/* Head WB queues */
scctx->isc_txqsizes[1] = sizeof(uint32_t) * scctx->isc_ntxd[1];
scctx->isc_rxqsizes[1] = sizeof(uint32_t) * scctx->isc_nrxd[1];
/* XXX: Must have 1 txqset, but can have up to 4 rxqsets */
scctx->isc_nrxqsets = 1;
scctx->isc_ntxqsets = 1;
/* scctx->isc_tx_csum_flags = (CSUM_TCP | CSUM_UDP) |
(CSUM_TCP_IPV6 | CSUM_UDP_IPV6) | CSUM_TSO */
scctx->isc_tx_csum_flags = 0;
scctx->isc_capabilities = scctx->isc_capenable = 0;
#if 0
/*
* CSUM, TSO and VLAN support are TBD
*/
IFCAP_TXCSUM | IFCAP_TXCSUM_IPV6 |
IFCAP_TSO4 | IFCAP_TSO6 |
IFCAP_RXCSUM | IFCAP_RXCSUM_IPV6 |
IFCAP_VLAN_MTU | IFCAP_VLAN_HWTAGGING |
IFCAP_VLAN_HWCSUM | IFCAP_VLAN_HWTSO |
IFCAP_JUMBO_MTU;
scctx->isc_capabilities |= IFCAP_LRO | IFCAP_VLAN_HWFILTER;
#endif
/* get the BAR */
error = mgb_alloc_regs(sc);
if (error != 0) {
device_printf(sc->dev,
"Unable to allocate bus resource: registers.\n");
goto fail;
}
error = mgb_test_bar(sc);
if (error != 0)
goto fail;
error = mgb_hw_init(sc);
if (error != 0) {
device_printf(sc->dev,
"MGB device init failed. (err: %d)\n", error);
goto fail;
}
switch (pci_get_device(sc->dev))
{
case MGB_LAN7430_DEVICE_ID:
phyaddr = 1;
break;
case MGB_LAN7431_DEVICE_ID:
default:
phyaddr = MII_PHY_ANY;
break;
}
/* XXX: Would be nice(r) if locked methods were here */
error = mii_attach(sc->dev, &sc->miibus, iflib_get_ifp(ctx),
mgb_media_change, mgb_media_status,
BMSR_DEFCAPMASK, phyaddr, MII_OFFSET_ANY, MIIF_DOPAUSE);
if (error != 0) {
device_printf(sc->dev, "Failed to attach MII interface\n");
goto fail;
}
miid = device_get_softc(sc->miibus);
scctx->isc_media = &miid->mii_media;
scctx->isc_msix_bar = pci_msix_table_bar(sc->dev);
/** Setup PBA BAR **/
rid = pci_msix_pba_bar(sc->dev);
if (rid != scctx->isc_msix_bar) {
sc->pba = bus_alloc_resource_any(sc->dev, SYS_RES_MEMORY,
&rid, RF_ACTIVE);
if (sc->pba == NULL) {
error = ENXIO;
device_printf(sc->dev, "Failed to setup PBA BAR\n");
goto fail;
}
}
mgb_get_ethaddr(sc, &hwaddr);
if (ETHER_IS_BROADCAST(hwaddr.octet) ||
ETHER_IS_MULTICAST(hwaddr.octet) ||
ETHER_IS_ZERO(hwaddr.octet))
ether_gen_addr(iflib_get_ifp(ctx), &hwaddr);
/*
* XXX: if the MAC address was generated the linux driver
* writes it back to the device.
*/
iflib_set_mac(ctx, hwaddr.octet);
/* Map all vectors to vector 0 (admin interrupts) by default. */
CSR_WRITE_REG(sc, MGB_INTR_VEC_RX_MAP, 0);
CSR_WRITE_REG(sc, MGB_INTR_VEC_TX_MAP, 0);
CSR_WRITE_REG(sc, MGB_INTR_VEC_OTHER_MAP, 0);
return (0);
fail:
mgb_detach(ctx);
return (error);
}
static int
mgb_attach_post(if_ctx_t ctx)
{
struct mgb_softc *sc;
sc = iflib_get_softc(ctx);
device_printf(sc->dev, "Interrupt test: %s\n",
(mgb_intr_test(sc) ? "PASS" : "FAIL"));
return (0);
}
static int
mgb_detach(if_ctx_t ctx)
{
struct mgb_softc *sc;
int error;
sc = iflib_get_softc(ctx);
/* XXX: Should report errors but still detach everything. */
error = mgb_hw_teardown(sc);
/* Release IRQs */
iflib_irq_free(ctx, &sc->rx_irq);
iflib_irq_free(ctx, &sc->admin_irq);
if (sc->miibus != NULL)
device_delete_child(sc->dev, sc->miibus);
if (sc->pba != NULL)
error = bus_release_resource(sc->dev, SYS_RES_MEMORY,
rman_get_rid(sc->pba), sc->pba);
sc->pba = NULL;
error = mgb_release_regs(sc);
return (error);
}
static int
mgb_media_change(if_t ifp)
{
struct mii_data *miid;
struct mii_softc *miisc;
struct mgb_softc *sc;
if_ctx_t ctx;
int needs_reset;
ctx = if_getsoftc(ifp);
sc = iflib_get_softc(ctx);
miid = device_get_softc(sc->miibus);
LIST_FOREACH(miisc, &miid->mii_phys, mii_list)
PHY_RESET(miisc);
needs_reset = mii_mediachg(miid);
if (needs_reset != 0)
ifp->if_init(ctx);
return (needs_reset);
}
static void
mgb_media_status(if_t ifp, struct ifmediareq *ifmr)
{
struct mgb_softc *sc;
struct mii_data *miid;
sc = iflib_get_softc(if_getsoftc(ifp));
miid = device_get_softc(sc->miibus);
if ((if_getflags(ifp) & IFF_UP) == 0)
return;
mii_pollstat(miid);
ifmr->ifm_active = miid->mii_media_active;
ifmr->ifm_status = miid->mii_media_status;
}
static int
mgb_tx_queues_alloc(if_ctx_t ctx, caddr_t *vaddrs, uint64_t *paddrs, int ntxqs,
int ntxqsets)
{
struct mgb_softc *sc;
struct mgb_ring_data *rdata;
int q;
sc = iflib_get_softc(ctx);
KASSERT(ntxqsets == 1, ("ntxqsets = %d", ntxqsets));
rdata = &sc->tx_ring_data;
for (q = 0; q < ntxqsets; q++) {
KASSERT(ntxqs == 2, ("ntxqs = %d", ntxqs));
/* Ring */
rdata->ring = (struct mgb_ring_desc *) vaddrs[q * ntxqs + 0];
rdata->ring_bus_addr = paddrs[q * ntxqs + 0];
/* Head WB */
rdata->head_wb = (uint32_t *) vaddrs[q * ntxqs + 1];
rdata->head_wb_bus_addr = paddrs[q * ntxqs + 1];
}
return 0;
}
static int
mgb_rx_queues_alloc(if_ctx_t ctx, caddr_t *vaddrs, uint64_t *paddrs, int nrxqs,
int nrxqsets)
{
struct mgb_softc *sc;
struct mgb_ring_data *rdata;
int q;
sc = iflib_get_softc(ctx);
KASSERT(nrxqsets == 1, ("nrxqsets = %d", nrxqsets));
rdata = &sc->rx_ring_data;
for (q = 0; q < nrxqsets; q++) {
KASSERT(nrxqs == 2, ("nrxqs = %d", nrxqs));
/* Ring */
rdata->ring = (struct mgb_ring_desc *) vaddrs[q * nrxqs + 0];
rdata->ring_bus_addr = paddrs[q * nrxqs + 0];
/* Head WB */
rdata->head_wb = (uint32_t *) vaddrs[q * nrxqs + 1];
rdata->head_wb_bus_addr = paddrs[q * nrxqs + 1];
}
return 0;
}
static void
mgb_queues_free(if_ctx_t ctx)
{
struct mgb_softc *sc;
sc = iflib_get_softc(ctx);
memset(&sc->rx_ring_data, 0, sizeof(struct mgb_ring_data));
memset(&sc->tx_ring_data, 0, sizeof(struct mgb_ring_data));
}
static void
mgb_init(if_ctx_t ctx)
{
struct mgb_softc *sc;
struct mii_data *miid;
int error;
sc = iflib_get_softc(ctx);
miid = device_get_softc(sc->miibus);
device_printf(sc->dev, "running init ...\n");
mgb_dma_init(sc);
/* XXX: Turn off perfect filtering, turn on (broad|multi|uni)cast rx */
CSR_CLEAR_REG(sc, MGB_RFE_CTL, MGB_RFE_ALLOW_PERFECT_FILTER);
CSR_UPDATE_REG(sc, MGB_RFE_CTL,
MGB_RFE_ALLOW_BROADCAST |
MGB_RFE_ALLOW_UNICAST |
MGB_RFE_ALLOW_UNICAST);
error = mii_mediachg(miid);
KASSERT(!error, ("mii_mediachg returned: %d", error));
}
#ifdef DEBUG
static void
mgb_dump_some_stats(struct mgb_softc *sc)
{
int i;
int first_stat = 0x1200;
int last_stat = 0x12FC;
for (i = first_stat; i <= last_stat; i += 4)
if (CSR_READ_REG(sc, i) != 0)
device_printf(sc->dev, "0x%04x: 0x%08x\n", i,
CSR_READ_REG(sc, i));
char *stat_names[] = {
"MAC_ERR_STS ",
"FCT_INT_STS ",
"DMAC_CFG ",
"DMAC_CMD ",
"DMAC_INT_STS ",
"DMAC_INT_EN ",
"DMAC_RX_ERR_STS0 ",
"DMAC_RX_ERR_STS1 ",
"DMAC_RX_ERR_STS2 ",
"DMAC_RX_ERR_STS3 ",
"INT_STS ",
"INT_EN ",
"INT_VEC_EN ",
"INT_VEC_MAP0 ",
"INT_VEC_MAP1 ",
"INT_VEC_MAP2 ",
"TX_HEAD0",
"TX_TAIL0",
"DMAC_TX_ERR_STS0 ",
NULL
};
int stats[] = {
0x114,
0xA0,
0xC00,
0xC0C,
0xC10,
0xC14,
0xC60,
0xCA0,
0xCE0,
0xD20,
0x780,
0x788,
0x794,
0x7A0,
0x7A4,
0x780,
0xD58,
0xD5C,
0xD60,
0x0
};
i = 0;
printf("==============================\n");
while (stats[i++])
device_printf(sc->dev, "%s at offset 0x%04x = 0x%08x\n",
stat_names[i - 1], stats[i - 1],
CSR_READ_REG(sc, stats[i - 1]));
printf("==== TX RING DESCS ====\n");
for (i = 0; i < MGB_DMA_RING_SIZE; i++)
device_printf(sc->dev, "ring[%d].data0=0x%08x\n"
"ring[%d].data1=0x%08x\n"
"ring[%d].data2=0x%08x\n"
"ring[%d].data3=0x%08x\n",
i, sc->tx_ring_data.ring[i].ctl,
i, sc->tx_ring_data.ring[i].addr.low,
i, sc->tx_ring_data.ring[i].addr.high,
i, sc->tx_ring_data.ring[i].sts);
device_printf(sc->dev, "==== DUMP_TX_DMA_RAM ====\n");
int i;
CSR_WRITE_REG(sc, 0x24, 0xF); // DP_SEL & TX_RAM_0
for (i = 0; i < 128; i++) {
CSR_WRITE_REG(sc, 0x2C, i); // DP_ADDR
CSR_WRITE_REG(sc, 0x28, 0); // DP_CMD
while ((CSR_READ_REG(sc, 0x24) & 0x80000000) == 0) // DP_SEL & READY
DELAY(1000);
device_printf(sc->dev, "DMAC_TX_RAM_0[%u]=%08x\n", i,
CSR_READ_REG(sc, 0x30)); // DP_DATA
}
}
#endif
static void
mgb_stop(if_ctx_t ctx)
{
struct mgb_softc *sc ;
if_softc_ctx_t scctx;
int i;
sc = iflib_get_softc(ctx);
scctx = iflib_get_softc_ctx(ctx);
/* XXX: Could potentially timeout */
for (i = 0; i < scctx->isc_nrxqsets; i++) {
mgb_dmac_control(sc, MGB_DMAC_RX_START, 0, DMAC_STOP);
mgb_fct_control(sc, MGB_FCT_RX_CTL, 0, FCT_DISABLE);
}
for (i = 0; i < scctx->isc_ntxqsets; i++) {
mgb_dmac_control(sc, MGB_DMAC_TX_START, 0, DMAC_STOP);
mgb_fct_control(sc, MGB_FCT_TX_CTL, 0, FCT_DISABLE);
}
}
static int
mgb_legacy_intr(void *xsc)
{
struct mgb_softc *sc;
sc = xsc;
iflib_admin_intr_deferred(sc->ctx);
return (FILTER_HANDLED);
}
static int
mgb_rxq_intr(void *xsc)
{
struct mgb_softc *sc;
if_softc_ctx_t scctx;
uint32_t intr_sts, intr_en;
int qidx;
sc = xsc;
scctx = iflib_get_softc_ctx(sc->ctx);
intr_sts = CSR_READ_REG(sc, MGB_INTR_STS);
intr_en = CSR_READ_REG(sc, MGB_INTR_ENBL_SET);
intr_sts &= intr_en;
for (qidx = 0; qidx < scctx->isc_nrxqsets; qidx++) {
if ((intr_sts & MGB_INTR_STS_RX(qidx))){
CSR_WRITE_REG(sc, MGB_INTR_ENBL_CLR,
MGB_INTR_STS_RX(qidx));
CSR_WRITE_REG(sc, MGB_INTR_STS, MGB_INTR_STS_RX(qidx));
}
}
return (FILTER_SCHEDULE_THREAD);
}
static int
mgb_admin_intr(void *xsc)
{
struct mgb_softc *sc;
if_softc_ctx_t scctx;
uint32_t intr_sts, intr_en;
int qidx;
sc = xsc;
scctx = iflib_get_softc_ctx(sc->ctx);
intr_sts = CSR_READ_REG(sc, MGB_INTR_STS);
intr_en = CSR_READ_REG(sc, MGB_INTR_ENBL_SET);
intr_sts &= intr_en;
/*
* NOTE: Debugging printfs here
* will likely cause interrupt test failure.
*/
/* TODO: shouldn't continue if suspended */
if ((intr_sts & MGB_INTR_STS_ANY) == 0)
{
device_printf(sc->dev, "non-mgb interrupt triggered.\n");
return (FILTER_SCHEDULE_THREAD);
}
if ((intr_sts & MGB_INTR_STS_TEST) != 0)
{
sc->isr_test_flag = true;
CSR_WRITE_REG(sc, MGB_INTR_STS, MGB_INTR_STS_TEST);
return (FILTER_HANDLED);
}
if ((intr_sts & MGB_INTR_STS_RX_ANY) != 0)
{
for (qidx = 0; qidx < scctx->isc_nrxqsets; qidx++) {
if ((intr_sts & MGB_INTR_STS_RX(qidx))){
iflib_rx_intr_deferred(sc->ctx, qidx);
}
}
return (FILTER_HANDLED);
}
/* XXX: TX interrupts should not occur */
if ((intr_sts & MGB_INTR_STS_TX_ANY) != 0)
{
for (qidx = 0; qidx < scctx->isc_ntxqsets; qidx++) {
if ((intr_sts & MGB_INTR_STS_RX(qidx))) {
/* clear the interrupt sts and run handler */
CSR_WRITE_REG(sc, MGB_INTR_ENBL_CLR,
MGB_INTR_STS_TX(qidx));
CSR_WRITE_REG(sc, MGB_INTR_STS,
MGB_INTR_STS_TX(qidx));
iflib_tx_intr_deferred(sc->ctx, qidx);
}
}
return (FILTER_HANDLED);
}
return (FILTER_SCHEDULE_THREAD);
}
static int
mgb_msix_intr_assign(if_ctx_t ctx, int msix)
{
struct mgb_softc *sc;
if_softc_ctx_t scctx;
int error, i, vectorid;
char irq_name[16];
sc = iflib_get_softc(ctx);
scctx = iflib_get_softc_ctx(ctx);
KASSERT(scctx->isc_nrxqsets == 1 && scctx->isc_ntxqsets == 1,
("num rxqsets/txqsets != 1 "));
/*
* First vector should be admin interrupts, others vectors are TX/RX
*
* RIDs start at 1, and vector ids start at 0.
*/
vectorid = 0;
error = iflib_irq_alloc_generic(ctx, &sc->admin_irq, vectorid + 1,
IFLIB_INTR_ADMIN, mgb_admin_intr, sc, 0, "admin");
if (error) {
device_printf(sc->dev,
"Failed to register admin interrupt handler\n");
return (error);
}
for (i = 0; i < scctx->isc_nrxqsets; i++) {
vectorid++;
snprintf(irq_name, sizeof(irq_name), "rxq%d", i);
error = iflib_irq_alloc_generic(ctx, &sc->rx_irq, vectorid + 1,
IFLIB_INTR_RX, mgb_rxq_intr, sc, i, irq_name);
if (error) {
device_printf(sc->dev,
"Failed to register rxq %d interrupt handler\n", i);
return (error);
}
CSR_UPDATE_REG(sc, MGB_INTR_VEC_RX_MAP,
MGB_INTR_VEC_MAP(vectorid, i));
}
/* Not actually mapping hw TX interrupts ... */
for (i = 0; i < scctx->isc_ntxqsets; i++) {
snprintf(irq_name, sizeof(irq_name), "txq%d", i);
iflib_softirq_alloc_generic(ctx, NULL, IFLIB_INTR_TX, NULL, i,
irq_name);
}
return (0);
}
static void
mgb_intr_enable_all(if_ctx_t ctx)
{
struct mgb_softc *sc;
if_softc_ctx_t scctx;
int i, dmac_enable = 0, intr_sts = 0, vec_en = 0;
sc = iflib_get_softc(ctx);
scctx = iflib_get_softc_ctx(ctx);
intr_sts |= MGB_INTR_STS_ANY;
vec_en |= MGB_INTR_STS_ANY;
for (i = 0; i < scctx->isc_nrxqsets; i++) {
intr_sts |= MGB_INTR_STS_RX(i);
dmac_enable |= MGB_DMAC_RX_INTR_ENBL(i);
vec_en |= MGB_INTR_RX_VEC_STS(i);
}
/* TX interrupts aren't needed ... */
CSR_WRITE_REG(sc, MGB_INTR_ENBL_SET, intr_sts);
CSR_WRITE_REG(sc, MGB_INTR_VEC_ENBL_SET, vec_en);
CSR_WRITE_REG(sc, MGB_DMAC_INTR_STS, dmac_enable);
CSR_WRITE_REG(sc, MGB_DMAC_INTR_ENBL_SET, dmac_enable);
}
static void
mgb_intr_disable_all(if_ctx_t ctx)
{
struct mgb_softc *sc;
sc = iflib_get_softc(ctx);
CSR_WRITE_REG(sc, MGB_INTR_ENBL_CLR, UINT32_MAX);
CSR_WRITE_REG(sc, MGB_INTR_VEC_ENBL_CLR, UINT32_MAX);
CSR_WRITE_REG(sc, MGB_INTR_STS, UINT32_MAX);
CSR_WRITE_REG(sc, MGB_DMAC_INTR_ENBL_CLR, UINT32_MAX);
CSR_WRITE_REG(sc, MGB_DMAC_INTR_STS, UINT32_MAX);
}
static int
mgb_rx_queue_intr_enable(if_ctx_t ctx, uint16_t qid)
{
/* called after successful rx isr */
struct mgb_softc *sc;
sc = iflib_get_softc(ctx);
CSR_WRITE_REG(sc, MGB_INTR_VEC_ENBL_SET, MGB_INTR_RX_VEC_STS(qid));
CSR_WRITE_REG(sc, MGB_INTR_ENBL_SET, MGB_INTR_STS_RX(qid));
CSR_WRITE_REG(sc, MGB_DMAC_INTR_STS, MGB_DMAC_RX_INTR_ENBL(qid));
CSR_WRITE_REG(sc, MGB_DMAC_INTR_ENBL_SET, MGB_DMAC_RX_INTR_ENBL(qid));
return (0);
}
static int
mgb_tx_queue_intr_enable(if_ctx_t ctx, uint16_t qid)
{
/* XXX: not called (since tx interrupts not used) */
struct mgb_softc *sc;
sc = iflib_get_softc(ctx);
CSR_WRITE_REG(sc, MGB_INTR_ENBL_SET, MGB_INTR_STS_TX(qid));
CSR_WRITE_REG(sc, MGB_DMAC_INTR_STS, MGB_DMAC_TX_INTR_ENBL(qid));
CSR_WRITE_REG(sc, MGB_DMAC_INTR_ENBL_SET, MGB_DMAC_TX_INTR_ENBL(qid));
return (0);
}
static bool
mgb_intr_test(struct mgb_softc *sc)
{
int i;
sc->isr_test_flag = false;
CSR_WRITE_REG(sc, MGB_INTR_STS, MGB_INTR_STS_TEST);
CSR_WRITE_REG(sc, MGB_INTR_VEC_ENBL_SET, MGB_INTR_STS_ANY);
CSR_WRITE_REG(sc, MGB_INTR_ENBL_SET,
MGB_INTR_STS_ANY | MGB_INTR_STS_TEST);
CSR_WRITE_REG(sc, MGB_INTR_SET, MGB_INTR_STS_TEST);
if (sc->isr_test_flag)
return true;
for (i = 0; i < MGB_TIMEOUT; i++) {
DELAY(10);
if (sc->isr_test_flag)
break;
}
CSR_WRITE_REG(sc, MGB_INTR_ENBL_CLR, MGB_INTR_STS_TEST);
CSR_WRITE_REG(sc, MGB_INTR_STS, MGB_INTR_STS_TEST);
return sc->isr_test_flag;
}
static int
mgb_isc_txd_encap(void *xsc , if_pkt_info_t ipi)
{
struct mgb_softc *sc;
if_softc_ctx_t scctx;
struct mgb_ring_data *rdata;
struct mgb_ring_desc *txd;
bus_dma_segment_t *segs;
qidx_t pidx, nsegs;
int i;
KASSERT(ipi->ipi_qsidx == 0,
("tried to refill TX Channel %d.\n", ipi->ipi_qsidx));
sc = xsc;
scctx = iflib_get_softc_ctx(sc->ctx);
rdata = &sc->tx_ring_data;
pidx = ipi->ipi_pidx;
segs = ipi->ipi_segs;
nsegs = ipi->ipi_nsegs;
/* For each seg, create a descriptor */
for (i = 0; i < nsegs; ++i) {
KASSERT(nsegs == 1, ("Multisegment packet !!!!!\n"));
txd = &rdata->ring[pidx];
txd->ctl = htole32(
(segs[i].ds_len & MGB_DESC_CTL_BUFLEN_MASK ) |
/*
* XXX: This will be wrong in the multipacket case
* I suspect FS should be for the first packet and
* LS should be for the last packet
*/
MGB_TX_DESC_CTL_FS | MGB_TX_DESC_CTL_LS |
MGB_DESC_CTL_FCS);
txd->addr.low = htole32(CSR_TRANSLATE_ADDR_LOW32(
segs[i].ds_addr));
txd->addr.high = htole32(CSR_TRANSLATE_ADDR_HIGH32(
segs[i].ds_addr));
txd->sts = htole32(
(segs[i].ds_len << 16) & MGB_DESC_FRAME_LEN_MASK);
pidx = MGB_NEXT_RING_IDX(pidx);
}
ipi->ipi_new_pidx = pidx;
return (0);
}
static void
mgb_isc_txd_flush(void *xsc, uint16_t txqid, qidx_t pidx)
{
struct mgb_softc *sc;
struct mgb_ring_data *rdata;
KASSERT(txqid == 0, ("tried to flush TX Channel %d.\n", txqid));
sc = xsc;
rdata = &sc->tx_ring_data;
if (rdata->last_tail != pidx) {
rdata->last_tail = pidx;
CSR_WRITE_REG(sc, MGB_DMA_TX_TAIL(txqid), rdata->last_tail);
}
}
static int
mgb_isc_txd_credits_update(void *xsc, uint16_t txqid, bool clear)
{
struct mgb_softc *sc;
struct mgb_ring_desc *txd;
struct mgb_ring_data *rdata;
int processed = 0;
/*
* > If clear is true, we need to report the number of TX command ring
* > descriptors that have been processed by the device. If clear is
* > false, we just need to report whether or not at least one TX
* > command ring descriptor has been processed by the device.
* - vmx driver
*/
KASSERT(txqid == 0, ("tried to credits_update TX Channel %d.\n",
txqid));
sc = xsc;
rdata = &sc->tx_ring_data;
while (*(rdata->head_wb) != rdata->last_head) {
if (!clear)
return 1;
txd = &rdata->ring[rdata->last_head];
memset(txd, 0, sizeof(struct mgb_ring_desc));
rdata->last_head = MGB_NEXT_RING_IDX(rdata->last_head);
processed++;
}
return (processed);
}
static int
mgb_isc_rxd_available(void *xsc, uint16_t rxqid, qidx_t idx, qidx_t budget)
{
struct mgb_softc *sc;
if_softc_ctx_t scctx;
struct mgb_ring_data *rdata;
int avail = 0;
sc = xsc;
KASSERT(rxqid == 0, ("tried to check availability in RX Channel %d.\n",
rxqid));
rdata = &sc->rx_ring_data;
scctx = iflib_get_softc_ctx(sc->ctx);
for (; idx != *(rdata->head_wb);
idx = MGB_NEXT_RING_IDX(idx)) {
avail++;
/* XXX: Could verify desc is device owned here */
if (avail == budget)
break;
}
return (avail);
}
static int
mgb_isc_rxd_pkt_get(void *xsc, if_rxd_info_t ri)
{
struct mgb_softc *sc;
struct mgb_ring_data *rdata;
struct mgb_ring_desc rxd;
int total_len;
KASSERT(ri->iri_qsidx == 0,
("tried to check availability in RX Channel %d\n", ri->iri_qsidx));
sc = xsc;
total_len = 0;
rdata = &sc->rx_ring_data;
while (*(rdata->head_wb) != rdata->last_head) {
/* copy ring desc and do swapping */
rxd = rdata->ring[rdata->last_head];
rxd.ctl = le32toh(rxd.ctl);
rxd.addr.low = le32toh(rxd.ctl);
rxd.addr.high = le32toh(rxd.ctl);
rxd.sts = le32toh(rxd.ctl);
if ((rxd.ctl & MGB_DESC_CTL_OWN) != 0) {
device_printf(sc->dev,
"Tried to read descriptor ... "
"found that it's owned by the driver\n");
return EINVAL;
}
if ((rxd.ctl & MGB_RX_DESC_CTL_FS) == 0) {
device_printf(sc->dev,
"Tried to read descriptor ... "
"found that FS is not set.\n");
device_printf(sc->dev, "Tried to read descriptor ... that it FS is not set.\n");
return EINVAL;
}
/* XXX: Multi-packet support */
if ((rxd.ctl & MGB_RX_DESC_CTL_LS) == 0) {
device_printf(sc->dev,
"Tried to read descriptor ... "
"found that LS is not set. (Multi-buffer packets not yet supported)\n");
return EINVAL;
}
ri->iri_frags[0].irf_flid = 0;
ri->iri_frags[0].irf_idx = rdata->last_head;
ri->iri_frags[0].irf_len = MGB_DESC_GET_FRAME_LEN(&rxd);
total_len += ri->iri_frags[0].irf_len;
rdata->last_head = MGB_NEXT_RING_IDX(rdata->last_head);
break;
}
ri->iri_nfrags = 1;
ri->iri_len = total_len;
return (0);
}
static void
mgb_isc_rxd_refill(void *xsc, if_rxd_update_t iru)
{
if_softc_ctx_t scctx;
struct mgb_softc *sc;
struct mgb_ring_data *rdata;
struct mgb_ring_desc *rxd;
uint64_t *paddrs;
qidx_t *idxs;
qidx_t idx;
int count, len;
count = iru->iru_count;
len = iru->iru_buf_size;
idxs = iru->iru_idxs;
paddrs = iru->iru_paddrs;
KASSERT(iru->iru_qsidx == 0,
("tried to refill RX Channel %d.\n", iru->iru_qsidx));
sc = xsc;
scctx = iflib_get_softc_ctx(sc->ctx);
rdata = &sc->rx_ring_data;
while (count > 0) {
idx = idxs[--count];
rxd = &rdata->ring[idx];
rxd->sts = 0;
rxd->addr.low =
htole32(CSR_TRANSLATE_ADDR_LOW32(paddrs[count]));
rxd->addr.high =
htole32(CSR_TRANSLATE_ADDR_HIGH32(paddrs[count]));
rxd->ctl = htole32(MGB_DESC_CTL_OWN |
(len & MGB_DESC_CTL_BUFLEN_MASK));
}
return;
}
static void
mgb_isc_rxd_flush(void *xsc, uint16_t rxqid, uint8_t flid, qidx_t pidx)
{
struct mgb_softc *sc;
sc = xsc;
KASSERT(rxqid == 0, ("tried to flush RX Channel %d.\n", rxqid));
iflib: leave only 1 receive descriptor unused The pidx argument of isc_rxd_flush() indicates which is the last valid receive descriptor to be used by the NIC. However, current code has multiple issues: - Intel drivers write pidx to their RDT register, which means that NICs will only use the descriptors up to pidx-1 (modulo ring size N), and won't actually use the one pointed by pidx. This does not break reception, but it is anyway confusing and suboptimal (the NIC will actually see only N-2 descriptors as available, rather than N-1). Other drivers (if_vmx, if_bnxt, if_mgb) adhere to this semantic). - The semantic used by Intel (RDT is one descriptor past the last valid one) is used by most (if not all) NICs, and it is also used on the TX side (also in iflib). Since iflib is not currently using this semantic for RX, it must decrement fl->ifl_pidx (modulo N) before calling isc_rxd_flush(), and then the per-driver callback implementation must increment the index again (to match the real semantic). This is confusing and suboptimal. - The iflib refill function is also called at initialization. However, in case the ring size is smaller than 128 (e.g. if_mgb), the refill function will actually prepare all the receive descriptors (N), without leaving one unused, as most of NICs assume (e.g. to avoid RDT to overrun RDH). I can speculate that the code looks like this right now because this issue showed up during testing (e.g. with if_mgb), and it was easy to workaround by decrementing pidx before isc_rxd_flush(). The goal of this change is to simplify the code (removing a bunch of instructions from the RX fast path), and to make the semantic of isc_rxd_flush() consistent across drivers. To achieve this, we: - change the semantics of the pidx argument to the usual one (that is the index one past the last valid one), so that both iflib and drivers avoid the decrement/increment dance. - fix the initialization code to prepare at most N-1 descriptors. Reviewed by: markj MFC after: 2 weeks Differential Revision: https://reviews.freebsd.org/D26191
2020-09-01 20:41:47 +00:00
/*
* According to the programming guide, last_tail must be set to
* the last valid RX descriptor, rather than to the one past that.
* Note that this is not true for the TX ring!
*/
sc->rx_ring_data.last_tail = MGB_PREV_RING_IDX(pidx);
CSR_WRITE_REG(sc, MGB_DMA_RX_TAIL(rxqid), sc->rx_ring_data.last_tail);
return;
}
static int
mgb_test_bar(struct mgb_softc *sc)
{
uint32_t id_rev, dev_id, rev;
id_rev = CSR_READ_REG(sc, 0);
dev_id = id_rev >> 16;
rev = id_rev & 0xFFFF;
if (dev_id == MGB_LAN7430_DEVICE_ID ||
dev_id == MGB_LAN7431_DEVICE_ID) {
return 0;
} else {
device_printf(sc->dev, "ID check failed.\n");
return ENXIO;
}
}
static int
mgb_alloc_regs(struct mgb_softc *sc)
{
int rid;
rid = PCIR_BAR(MGB_BAR);
pci_enable_busmaster(sc->dev);
sc->regs = bus_alloc_resource_any(sc->dev, SYS_RES_MEMORY,
&rid, RF_ACTIVE);
if (sc->regs == NULL)
return ENXIO;
return (0);
}
static int
mgb_release_regs(struct mgb_softc *sc)
{
int error = 0;
if (sc->regs != NULL)
error = bus_release_resource(sc->dev, SYS_RES_MEMORY,
rman_get_rid(sc->regs), sc->regs);
sc->regs = NULL;
pci_disable_busmaster(sc->dev);
return error;
}
static int
mgb_dma_init(struct mgb_softc *sc)
{
if_softc_ctx_t scctx;
int ch, error = 0;
scctx = iflib_get_softc_ctx(sc->ctx);
for (ch = 0; ch < scctx->isc_nrxqsets; ch++)
if ((error = mgb_dma_rx_ring_init(sc, ch)))
goto fail;
for (ch = 0; ch < scctx->isc_nrxqsets; ch++)
if ((error = mgb_dma_tx_ring_init(sc, ch)))
goto fail;
fail:
return error;
}
static int
mgb_dma_rx_ring_init(struct mgb_softc *sc, int channel)
{
struct mgb_ring_data *rdata;
int ring_config, error = 0;
rdata = &sc->rx_ring_data;
mgb_dmac_control(sc, MGB_DMAC_RX_START, 0, DMAC_RESET);
KASSERT(MGB_DMAC_STATE_IS_INITIAL(sc, MGB_DMAC_RX_START, channel),
("Trying to init channels when not in init state\n"));
/* write ring address */
if (rdata->ring_bus_addr == 0) {
device_printf(sc->dev, "Invalid ring bus addr.\n");
goto fail;
}
CSR_WRITE_REG(sc, MGB_DMA_RX_BASE_H(channel),
CSR_TRANSLATE_ADDR_HIGH32(rdata->ring_bus_addr));
CSR_WRITE_REG(sc, MGB_DMA_RX_BASE_L(channel),
CSR_TRANSLATE_ADDR_LOW32(rdata->ring_bus_addr));
/* write head pointer writeback address */
if (rdata->head_wb_bus_addr == 0) {
device_printf(sc->dev, "Invalid head wb bus addr.\n");
goto fail;
}
CSR_WRITE_REG(sc, MGB_DMA_RX_HEAD_WB_H(channel),
CSR_TRANSLATE_ADDR_HIGH32(rdata->head_wb_bus_addr));
CSR_WRITE_REG(sc, MGB_DMA_RX_HEAD_WB_L(channel),
CSR_TRANSLATE_ADDR_LOW32(rdata->head_wb_bus_addr));
/* Enable head pointer writeback */
CSR_WRITE_REG(sc, MGB_DMA_RX_CONFIG0(channel), MGB_DMA_HEAD_WB_ENBL);
ring_config = CSR_READ_REG(sc, MGB_DMA_RX_CONFIG1(channel));
/* ring size */
ring_config &= ~MGB_DMA_RING_LEN_MASK;
ring_config |= (MGB_DMA_RING_SIZE & MGB_DMA_RING_LEN_MASK);
/* packet padding (PAD_2 is better for IP header alignment ...) */
ring_config &= ~MGB_DMA_RING_PAD_MASK;
ring_config |= (MGB_DMA_RING_PAD_0 & MGB_DMA_RING_PAD_MASK);
CSR_WRITE_REG(sc, MGB_DMA_RX_CONFIG1(channel), ring_config);
rdata->last_head = CSR_READ_REG(sc, MGB_DMA_RX_HEAD(channel));
mgb_fct_control(sc, MGB_FCT_RX_CTL, channel, FCT_RESET);
if (error != 0) {
device_printf(sc->dev, "Failed to reset RX FCT.\n");
goto fail;
}
mgb_fct_control(sc, MGB_FCT_RX_CTL, channel, FCT_ENABLE);
if (error != 0) {
device_printf(sc->dev, "Failed to enable RX FCT.\n");
goto fail;
}
mgb_dmac_control(sc, MGB_DMAC_RX_START, channel, DMAC_START);
if (error != 0)
device_printf(sc->dev, "Failed to start RX DMAC.\n");
fail:
return (error);
}
static int
mgb_dma_tx_ring_init(struct mgb_softc *sc, int channel)
{
struct mgb_ring_data *rdata;
int ring_config, error = 0;
rdata = &sc->tx_ring_data;
if ((error = mgb_fct_control(sc, MGB_FCT_TX_CTL, channel, FCT_RESET))) {
device_printf(sc->dev, "Failed to reset TX FCT.\n");
goto fail;
}
if ((error = mgb_fct_control(sc, MGB_FCT_TX_CTL, channel,
FCT_ENABLE))) {
device_printf(sc->dev, "Failed to enable TX FCT.\n");
goto fail;
}
if ((error = mgb_dmac_control(sc, MGB_DMAC_TX_START, channel,
DMAC_RESET))) {
device_printf(sc->dev, "Failed to reset TX DMAC.\n");
goto fail;
}
KASSERT(MGB_DMAC_STATE_IS_INITIAL(sc, MGB_DMAC_TX_START, channel),
("Trying to init channels in not init state\n"));
/* write ring address */
if (rdata->ring_bus_addr == 0) {
device_printf(sc->dev, "Invalid ring bus addr.\n");
goto fail;
}
CSR_WRITE_REG(sc, MGB_DMA_TX_BASE_H(channel),
CSR_TRANSLATE_ADDR_HIGH32(rdata->ring_bus_addr));
CSR_WRITE_REG(sc, MGB_DMA_TX_BASE_L(channel),
CSR_TRANSLATE_ADDR_LOW32(rdata->ring_bus_addr));
/* write ring size */
ring_config = CSR_READ_REG(sc, MGB_DMA_TX_CONFIG1(channel));
ring_config &= ~MGB_DMA_RING_LEN_MASK;
ring_config |= (MGB_DMA_RING_SIZE & MGB_DMA_RING_LEN_MASK);
CSR_WRITE_REG(sc, MGB_DMA_TX_CONFIG1(channel), ring_config);
/* Enable interrupt on completion and head pointer writeback */
ring_config = (MGB_DMA_HEAD_WB_LS_ENBL | MGB_DMA_HEAD_WB_ENBL);
CSR_WRITE_REG(sc, MGB_DMA_TX_CONFIG0(channel), ring_config);
/* write head pointer writeback address */
if (rdata->head_wb_bus_addr == 0) {
device_printf(sc->dev, "Invalid head wb bus addr.\n");
goto fail;
}
CSR_WRITE_REG(sc, MGB_DMA_TX_HEAD_WB_H(channel),
CSR_TRANSLATE_ADDR_HIGH32(rdata->head_wb_bus_addr));
CSR_WRITE_REG(sc, MGB_DMA_TX_HEAD_WB_L(channel),
CSR_TRANSLATE_ADDR_LOW32(rdata->head_wb_bus_addr));
rdata->last_head = CSR_READ_REG(sc, MGB_DMA_TX_HEAD(channel));
KASSERT(rdata->last_head == 0, ("MGB_DMA_TX_HEAD was not reset.\n"));
rdata->last_tail = 0;
CSR_WRITE_REG(sc, MGB_DMA_TX_TAIL(channel), rdata->last_tail);
if ((error = mgb_dmac_control(sc, MGB_DMAC_TX_START, channel,
DMAC_START)))
device_printf(sc->dev, "Failed to start TX DMAC.\n");
fail:
return error;
}
static int
mgb_dmac_control(struct mgb_softc *sc, int start, int channel,
enum mgb_dmac_cmd cmd)
{
int error = 0;
switch (cmd) {
case DMAC_RESET:
CSR_WRITE_REG(sc, MGB_DMAC_CMD,
MGB_DMAC_CMD_RESET(start, channel));
error = mgb_wait_for_bits(sc, MGB_DMAC_CMD, 0,
MGB_DMAC_CMD_RESET(start, channel));
break;
case DMAC_START:
/*
* NOTE: this simplifies the logic, since it will never
* try to start in STOP_PENDING, but it also increases work.
*/
error = mgb_dmac_control(sc, start, channel, DMAC_STOP);
if (error != 0)
return error;
CSR_WRITE_REG(sc, MGB_DMAC_CMD,
MGB_DMAC_CMD_START(start, channel));
break;
case DMAC_STOP:
CSR_WRITE_REG(sc, MGB_DMAC_CMD,
MGB_DMAC_CMD_STOP(start, channel));
error = mgb_wait_for_bits(sc, MGB_DMAC_CMD,
MGB_DMAC_CMD_STOP(start, channel),
MGB_DMAC_CMD_START(start, channel));
break;
}
return error;
}
static int
mgb_fct_control(struct mgb_softc *sc, int reg, int channel,
enum mgb_fct_cmd cmd)
{
switch (cmd) {
case FCT_RESET:
CSR_WRITE_REG(sc, reg, MGB_FCT_RESET(channel));
return mgb_wait_for_bits(sc, reg, 0, MGB_FCT_RESET(channel));
case FCT_ENABLE:
CSR_WRITE_REG(sc, reg, MGB_FCT_ENBL(channel));
return (0);
case FCT_DISABLE:
CSR_WRITE_REG(sc, reg, MGB_FCT_DSBL(channel));
return mgb_wait_for_bits(sc, reg, 0, MGB_FCT_ENBL(channel));
}
}
static int
mgb_hw_teardown(struct mgb_softc *sc)
{
int err = 0;
/* Stop MAC */
CSR_CLEAR_REG(sc, MGB_MAC_RX, MGB_MAC_ENBL);
CSR_WRITE_REG(sc, MGB_MAC_TX, MGB_MAC_ENBL);
if ((err = mgb_wait_for_bits(sc, MGB_MAC_RX, MGB_MAC_DSBL, 0)))
return (err);
if ((err = mgb_wait_for_bits(sc, MGB_MAC_TX, MGB_MAC_DSBL, 0)))
return (err);
return (err);
}
static int
mgb_hw_init(struct mgb_softc *sc)
{
int error = 0;
error = mgb_hw_reset(sc);
if (error != 0)
goto fail;
mgb_mac_init(sc);
error = mgb_phy_reset(sc);
if (error != 0)
goto fail;
error = mgb_dmac_reset(sc);
if (error != 0)
goto fail;
fail:
return error;
}
static int
mgb_hw_reset(struct mgb_softc *sc)
{
CSR_UPDATE_REG(sc, MGB_HW_CFG, MGB_LITE_RESET);
return (mgb_wait_for_bits(sc, MGB_HW_CFG, 0, MGB_LITE_RESET));
}
static int
mgb_mac_init(struct mgb_softc *sc)
{
/**
* enable automatic duplex detection and
* automatic speed detection
*/
CSR_UPDATE_REG(sc, MGB_MAC_CR, MGB_MAC_ADD_ENBL | MGB_MAC_ASD_ENBL);
CSR_UPDATE_REG(sc, MGB_MAC_TX, MGB_MAC_ENBL);
CSR_UPDATE_REG(sc, MGB_MAC_RX, MGB_MAC_ENBL);
return MGB_STS_OK;
}
static int
mgb_phy_reset(struct mgb_softc *sc)
{
CSR_UPDATE_BYTE(sc, MGB_PMT_CTL, MGB_PHY_RESET);
if (mgb_wait_for_bits(sc, MGB_PMT_CTL, 0, MGB_PHY_RESET) ==
MGB_STS_TIMEOUT)
return MGB_STS_TIMEOUT;
return (mgb_wait_for_bits(sc, MGB_PMT_CTL, MGB_PHY_READY, 0));
}
static int
mgb_dmac_reset(struct mgb_softc *sc)
{
CSR_WRITE_REG(sc, MGB_DMAC_CMD, MGB_DMAC_RESET);
return (mgb_wait_for_bits(sc, MGB_DMAC_CMD, 0, MGB_DMAC_RESET));
}
static int
mgb_wait_for_bits(struct mgb_softc *sc, int reg, int set_bits, int clear_bits)
{
int i, val;
i = 0;
do {
/*
* XXX: Datasheets states delay should be > 5 microseconds
* for device reset.
*/
DELAY(100);
val = CSR_READ_REG(sc, reg);
if ((val & set_bits) == set_bits &&
(val & clear_bits) == 0)
return MGB_STS_OK;
} while (i++ < MGB_TIMEOUT);
return MGB_STS_TIMEOUT;
}
static void
mgb_get_ethaddr(struct mgb_softc *sc, struct ether_addr *dest)
{
CSR_READ_REG_BYTES(sc, MGB_MAC_ADDR_BASE_L, &dest->octet[0], 4);
CSR_READ_REG_BYTES(sc, MGB_MAC_ADDR_BASE_H, &dest->octet[4], 2);
}
static int
mgb_miibus_readreg(device_t dev, int phy, int reg)
{
struct mgb_softc *sc;
int mii_access;
sc = iflib_get_softc(device_get_softc(dev));
if (mgb_wait_for_bits(sc, MGB_MII_ACCESS, 0, MGB_MII_BUSY) ==
MGB_STS_TIMEOUT)
return EIO;
mii_access = (phy & MGB_MII_PHY_ADDR_MASK) << MGB_MII_PHY_ADDR_SHIFT;
mii_access |= (reg & MGB_MII_REG_ADDR_MASK) << MGB_MII_REG_ADDR_SHIFT;
mii_access |= MGB_MII_BUSY | MGB_MII_READ;
CSR_WRITE_REG(sc, MGB_MII_ACCESS, mii_access);
if (mgb_wait_for_bits(sc, MGB_MII_ACCESS, 0, MGB_MII_BUSY) ==
MGB_STS_TIMEOUT)
return EIO;
return (CSR_READ_2_BYTES(sc, MGB_MII_DATA));
}
static int
mgb_miibus_writereg(device_t dev, int phy, int reg, int data)
{
struct mgb_softc *sc;
int mii_access;
sc = iflib_get_softc(device_get_softc(dev));
if (mgb_wait_for_bits(sc, MGB_MII_ACCESS,
0, MGB_MII_BUSY) == MGB_STS_TIMEOUT)
return EIO;
mii_access = (phy & MGB_MII_PHY_ADDR_MASK) << MGB_MII_PHY_ADDR_SHIFT;
mii_access |= (reg & MGB_MII_REG_ADDR_MASK) << MGB_MII_REG_ADDR_SHIFT;
mii_access |= MGB_MII_BUSY | MGB_MII_WRITE;
CSR_WRITE_REG(sc, MGB_MII_DATA, data);
CSR_WRITE_REG(sc, MGB_MII_ACCESS, mii_access);
if (mgb_wait_for_bits(sc, MGB_MII_ACCESS, 0, MGB_MII_BUSY) ==
MGB_STS_TIMEOUT)
return EIO;
return 0;
}
/* XXX: May need to lock these up */
static void
mgb_miibus_statchg(device_t dev)
{
struct mgb_softc *sc;
struct mii_data *miid;
sc = iflib_get_softc(device_get_softc(dev));
miid = device_get_softc(sc->miibus);
/* Update baudrate in iflib */
sc->baudrate = ifmedia_baudrate(miid->mii_media_active);
iflib_link_state_change(sc->ctx, sc->link_state, sc->baudrate);
}
static void
mgb_miibus_linkchg(device_t dev)
{
struct mgb_softc *sc;
struct mii_data *miid;
int link_state;
sc = iflib_get_softc(device_get_softc(dev));
miid = device_get_softc(sc->miibus);
/* XXX: copied from miibus_linkchg **/
if (miid->mii_media_status & IFM_AVALID) {
if (miid->mii_media_status & IFM_ACTIVE)
link_state = LINK_STATE_UP;
else
link_state = LINK_STATE_DOWN;
} else
link_state = LINK_STATE_UNKNOWN;
sc->link_state = link_state;
iflib_link_state_change(sc->ctx, sc->link_state, sc->baudrate);
}