eb956cd041
IF_ADDR_UNLOCK() across network device drivers when accessing the per-interface multicast address list, if_multiaddrs. This will allow us to change the locking strategy without affecting our driver programming interface or binary interface. For two wireless drivers, remove unnecessary locking, since they don't actually access the multicast address list. Approved by: re (kib) MFC after: 6 weeks
1934 lines
47 KiB
C
1934 lines
47 KiB
C
/*-
|
|
* Copyright (C) 2007-2008 Semihalf, Rafal Jaworowski <raj@semihalf.com>
|
|
* Copyright (C) 2006-2007 Semihalf, Piotr Kruszynski <ppk@semihalf.com>
|
|
* All rights reserved.
|
|
*
|
|
* 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 ``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 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.
|
|
*/
|
|
|
|
/*
|
|
* Freescale integrated Three-Speed Ethernet Controller (TSEC) driver.
|
|
*/
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
#ifdef HAVE_KERNEL_OPTION_HEADERS
|
|
#include "opt_device_polling.h"
|
|
#endif
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/bus.h>
|
|
#include <sys/endian.h>
|
|
#include <sys/mbuf.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/module.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/sockio.h>
|
|
#include <sys/sysctl.h>
|
|
|
|
#include <net/bpf.h>
|
|
#include <net/ethernet.h>
|
|
#include <net/if.h>
|
|
#include <net/if_arp.h>
|
|
#include <net/if_dl.h>
|
|
#include <net/if_media.h>
|
|
#include <net/if_types.h>
|
|
#include <net/if_vlan_var.h>
|
|
|
|
#include <netinet/in_systm.h>
|
|
#include <netinet/in.h>
|
|
#include <netinet/ip.h>
|
|
|
|
#include <machine/bus.h>
|
|
|
|
#include <dev/mii/mii.h>
|
|
#include <dev/mii/miivar.h>
|
|
|
|
#include <dev/tsec/if_tsec.h>
|
|
#include <dev/tsec/if_tsecreg.h>
|
|
|
|
static int tsec_alloc_dma_desc(device_t dev, bus_dma_tag_t *dtag,
|
|
bus_dmamap_t *dmap, bus_size_t dsize, void **vaddr, void *raddr,
|
|
const char *dname);
|
|
static void tsec_dma_ctl(struct tsec_softc *sc, int state);
|
|
static int tsec_encap(struct tsec_softc *sc, struct mbuf *m_head,
|
|
int fcb_inserted);
|
|
static void tsec_free_dma(struct tsec_softc *sc);
|
|
static void tsec_free_dma_desc(bus_dma_tag_t dtag, bus_dmamap_t dmap, void *vaddr);
|
|
static int tsec_ifmedia_upd(struct ifnet *ifp);
|
|
static void tsec_ifmedia_sts(struct ifnet *ifp, struct ifmediareq *ifmr);
|
|
static int tsec_new_rxbuf(bus_dma_tag_t tag, bus_dmamap_t map,
|
|
struct mbuf **mbufp, uint32_t *paddr);
|
|
static void tsec_map_dma_addr(void *arg, bus_dma_segment_t *segs,
|
|
int nseg, int error);
|
|
static void tsec_intrs_ctl(struct tsec_softc *sc, int state);
|
|
static void tsec_init(void *xsc);
|
|
static void tsec_init_locked(struct tsec_softc *sc);
|
|
static int tsec_ioctl(struct ifnet *ifp, u_long command, caddr_t data);
|
|
static void tsec_reset_mac(struct tsec_softc *sc);
|
|
static void tsec_setfilter(struct tsec_softc *sc);
|
|
static void tsec_set_mac_address(struct tsec_softc *sc);
|
|
static void tsec_start(struct ifnet *ifp);
|
|
static void tsec_start_locked(struct ifnet *ifp);
|
|
static void tsec_stop(struct tsec_softc *sc);
|
|
static void tsec_tick(void *arg);
|
|
static void tsec_watchdog(struct tsec_softc *sc);
|
|
static void tsec_add_sysctls(struct tsec_softc *sc);
|
|
static int tsec_sysctl_ic_time(SYSCTL_HANDLER_ARGS);
|
|
static int tsec_sysctl_ic_count(SYSCTL_HANDLER_ARGS);
|
|
static void tsec_set_rxic(struct tsec_softc *sc);
|
|
static void tsec_set_txic(struct tsec_softc *sc);
|
|
static int tsec_receive_intr_locked(struct tsec_softc *sc, int count);
|
|
static void tsec_transmit_intr_locked(struct tsec_softc *sc);
|
|
static void tsec_error_intr_locked(struct tsec_softc *sc, int count);
|
|
static void tsec_offload_setup(struct tsec_softc *sc);
|
|
static void tsec_offload_process_frame(struct tsec_softc *sc,
|
|
struct mbuf *m);
|
|
static void tsec_setup_multicast(struct tsec_softc *sc);
|
|
static int tsec_set_mtu(struct tsec_softc *sc, unsigned int mtu);
|
|
|
|
struct tsec_softc *tsec0_sc = NULL; /* XXX ugly hack! */
|
|
|
|
devclass_t tsec_devclass;
|
|
DRIVER_MODULE(miibus, tsec, miibus_driver, miibus_devclass, 0, 0);
|
|
MODULE_DEPEND(tsec, ether, 1, 1, 1);
|
|
MODULE_DEPEND(tsec, miibus, 1, 1, 1);
|
|
|
|
int
|
|
tsec_attach(struct tsec_softc *sc)
|
|
{
|
|
uint8_t hwaddr[ETHER_ADDR_LEN];
|
|
struct ifnet *ifp;
|
|
bus_dmamap_t *map_ptr;
|
|
bus_dmamap_t **map_pptr;
|
|
int error = 0;
|
|
int i;
|
|
|
|
/* Reset all TSEC counters */
|
|
TSEC_TX_RX_COUNTERS_INIT(sc);
|
|
|
|
/* Stop DMA engine if enabled by firmware */
|
|
tsec_dma_ctl(sc, 0);
|
|
|
|
/* Reset MAC */
|
|
tsec_reset_mac(sc);
|
|
|
|
/* Disable interrupts for now */
|
|
tsec_intrs_ctl(sc, 0);
|
|
|
|
/* Configure defaults for interrupts coalescing */
|
|
sc->rx_ic_time = 768;
|
|
sc->rx_ic_count = 16;
|
|
sc->tx_ic_time = 768;
|
|
sc->tx_ic_count = 16;
|
|
tsec_set_rxic(sc);
|
|
tsec_set_txic(sc);
|
|
tsec_add_sysctls(sc);
|
|
|
|
/* Allocate a busdma tag and DMA safe memory for TX descriptors. */
|
|
error = tsec_alloc_dma_desc(sc->dev, &sc->tsec_tx_dtag,
|
|
&sc->tsec_tx_dmap, sizeof(*sc->tsec_tx_vaddr) * TSEC_TX_NUM_DESC,
|
|
(void **)&sc->tsec_tx_vaddr, &sc->tsec_tx_raddr, "TX");
|
|
|
|
if (error) {
|
|
tsec_detach(sc);
|
|
return (ENXIO);
|
|
}
|
|
|
|
/* Allocate a busdma tag and DMA safe memory for RX descriptors. */
|
|
error = tsec_alloc_dma_desc(sc->dev, &sc->tsec_rx_dtag,
|
|
&sc->tsec_rx_dmap, sizeof(*sc->tsec_rx_vaddr) * TSEC_RX_NUM_DESC,
|
|
(void **)&sc->tsec_rx_vaddr, &sc->tsec_rx_raddr, "RX");
|
|
if (error) {
|
|
tsec_detach(sc);
|
|
return (ENXIO);
|
|
}
|
|
|
|
/* Allocate a busdma tag for TX mbufs. */
|
|
error = bus_dma_tag_create(NULL, /* parent */
|
|
TSEC_TXBUFFER_ALIGNMENT, 0, /* alignment, boundary */
|
|
BUS_SPACE_MAXADDR_32BIT, /* lowaddr */
|
|
BUS_SPACE_MAXADDR, /* highaddr */
|
|
NULL, NULL, /* filtfunc, filtfuncarg */
|
|
MCLBYTES * (TSEC_TX_NUM_DESC - 1), /* maxsize */
|
|
TSEC_TX_NUM_DESC - 1, /* nsegments */
|
|
MCLBYTES, 0, /* maxsegsz, flags */
|
|
NULL, NULL, /* lockfunc, lockfuncarg */
|
|
&sc->tsec_tx_mtag); /* dmat */
|
|
if (error) {
|
|
device_printf(sc->dev, "failed to allocate busdma tag "
|
|
"(tx mbufs)\n");
|
|
tsec_detach(sc);
|
|
return (ENXIO);
|
|
}
|
|
|
|
/* Allocate a busdma tag for RX mbufs. */
|
|
error = bus_dma_tag_create(NULL, /* parent */
|
|
TSEC_RXBUFFER_ALIGNMENT, 0, /* alignment, boundary */
|
|
BUS_SPACE_MAXADDR_32BIT, /* lowaddr */
|
|
BUS_SPACE_MAXADDR, /* highaddr */
|
|
NULL, NULL, /* filtfunc, filtfuncarg */
|
|
MCLBYTES, /* maxsize */
|
|
1, /* nsegments */
|
|
MCLBYTES, 0, /* maxsegsz, flags */
|
|
NULL, NULL, /* lockfunc, lockfuncarg */
|
|
&sc->tsec_rx_mtag); /* dmat */
|
|
if (error) {
|
|
device_printf(sc->dev, "failed to allocate busdma tag "
|
|
"(rx mbufs)\n");
|
|
tsec_detach(sc);
|
|
return (ENXIO);
|
|
}
|
|
|
|
/* Create TX busdma maps */
|
|
map_ptr = sc->tx_map_data;
|
|
map_pptr = sc->tx_map_unused_data;
|
|
|
|
for (i = 0; i < TSEC_TX_NUM_DESC; i++) {
|
|
map_pptr[i] = &map_ptr[i];
|
|
error = bus_dmamap_create(sc->tsec_tx_mtag, 0, map_pptr[i]);
|
|
if (error) {
|
|
device_printf(sc->dev, "failed to init TX ring\n");
|
|
tsec_detach(sc);
|
|
return (ENXIO);
|
|
}
|
|
}
|
|
|
|
/* Create RX busdma maps and zero mbuf handlers */
|
|
for (i = 0; i < TSEC_RX_NUM_DESC; i++) {
|
|
error = bus_dmamap_create(sc->tsec_rx_mtag, 0,
|
|
&sc->rx_data[i].map);
|
|
if (error) {
|
|
device_printf(sc->dev, "failed to init RX ring\n");
|
|
tsec_detach(sc);
|
|
return (ENXIO);
|
|
}
|
|
sc->rx_data[i].mbuf = NULL;
|
|
}
|
|
|
|
/* Create mbufs for RX buffers */
|
|
for (i = 0; i < TSEC_RX_NUM_DESC; i++) {
|
|
error = tsec_new_rxbuf(sc->tsec_rx_mtag, sc->rx_data[i].map,
|
|
&sc->rx_data[i].mbuf, &sc->rx_data[i].paddr);
|
|
if (error) {
|
|
device_printf(sc->dev, "can't load rx DMA map %d, "
|
|
"error = %d\n", i, error);
|
|
tsec_detach(sc);
|
|
return (error);
|
|
}
|
|
}
|
|
|
|
/* Create network interface for upper layers */
|
|
ifp = sc->tsec_ifp = if_alloc(IFT_ETHER);
|
|
if (ifp == NULL) {
|
|
device_printf(sc->dev, "if_alloc() failed\n");
|
|
tsec_detach(sc);
|
|
return (ENOMEM);
|
|
}
|
|
|
|
ifp->if_softc = sc;
|
|
if_initname(ifp, device_get_name(sc->dev), device_get_unit(sc->dev));
|
|
ifp->if_mtu = ETHERMTU;
|
|
ifp->if_flags = IFF_SIMPLEX | IFF_MULTICAST | IFF_BROADCAST;
|
|
ifp->if_init = tsec_init;
|
|
ifp->if_start = tsec_start;
|
|
ifp->if_ioctl = tsec_ioctl;
|
|
|
|
IFQ_SET_MAXLEN(&ifp->if_snd, TSEC_TX_NUM_DESC - 1);
|
|
ifp->if_snd.ifq_drv_maxlen = TSEC_TX_NUM_DESC - 1;
|
|
IFQ_SET_READY(&ifp->if_snd);
|
|
|
|
ifp->if_capabilities = IFCAP_VLAN_MTU;
|
|
if (sc->is_etsec)
|
|
ifp->if_capabilities |= IFCAP_HWCSUM;
|
|
|
|
ifp->if_capenable = ifp->if_capabilities;
|
|
|
|
#ifdef DEVICE_POLLING
|
|
/* Advertise that polling is supported */
|
|
ifp->if_capabilities |= IFCAP_POLLING;
|
|
#endif
|
|
|
|
/* Probe PHY(s) */
|
|
error = mii_phy_probe(sc->dev, &sc->tsec_miibus, tsec_ifmedia_upd,
|
|
tsec_ifmedia_sts);
|
|
if (error) {
|
|
device_printf(sc->dev, "MII failed to find PHY!\n");
|
|
if_free(ifp);
|
|
sc->tsec_ifp = NULL;
|
|
tsec_detach(sc);
|
|
return (error);
|
|
}
|
|
sc->tsec_mii = device_get_softc(sc->tsec_miibus);
|
|
|
|
/* Set MAC address */
|
|
tsec_get_hwaddr(sc, hwaddr);
|
|
ether_ifattach(ifp, hwaddr);
|
|
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
tsec_detach(struct tsec_softc *sc)
|
|
{
|
|
|
|
#ifdef DEVICE_POLLING
|
|
if (sc->tsec_ifp->if_capenable & IFCAP_POLLING)
|
|
ether_poll_deregister(sc->tsec_ifp);
|
|
#endif
|
|
|
|
/* Stop TSEC controller and free TX queue */
|
|
if (sc->sc_rres && sc->tsec_ifp)
|
|
tsec_shutdown(sc->dev);
|
|
|
|
/* Detach network interface */
|
|
if (sc->tsec_ifp) {
|
|
ether_ifdetach(sc->tsec_ifp);
|
|
if_free(sc->tsec_ifp);
|
|
sc->tsec_ifp = NULL;
|
|
}
|
|
|
|
/* Free DMA resources */
|
|
tsec_free_dma(sc);
|
|
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
tsec_shutdown(device_t dev)
|
|
{
|
|
struct tsec_softc *sc;
|
|
|
|
sc = device_get_softc(dev);
|
|
|
|
TSEC_GLOBAL_LOCK(sc);
|
|
tsec_stop(sc);
|
|
TSEC_GLOBAL_UNLOCK(sc);
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
tsec_suspend(device_t dev)
|
|
{
|
|
|
|
/* TODO not implemented! */
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
tsec_resume(device_t dev)
|
|
{
|
|
|
|
/* TODO not implemented! */
|
|
return (0);
|
|
}
|
|
|
|
static void
|
|
tsec_init(void *xsc)
|
|
{
|
|
struct tsec_softc *sc = xsc;
|
|
|
|
TSEC_GLOBAL_LOCK(sc);
|
|
tsec_init_locked(sc);
|
|
TSEC_GLOBAL_UNLOCK(sc);
|
|
}
|
|
|
|
static void
|
|
tsec_init_locked(struct tsec_softc *sc)
|
|
{
|
|
struct tsec_desc *tx_desc = sc->tsec_tx_vaddr;
|
|
struct tsec_desc *rx_desc = sc->tsec_rx_vaddr;
|
|
struct ifnet *ifp = sc->tsec_ifp;
|
|
uint32_t timeout, val, i;
|
|
|
|
TSEC_GLOBAL_LOCK_ASSERT(sc);
|
|
tsec_stop(sc);
|
|
|
|
/*
|
|
* These steps are according to the MPC8555E PowerQUICCIII RM:
|
|
* 14.7 Initialization/Application Information
|
|
*/
|
|
|
|
/* Step 1: soft reset MAC */
|
|
tsec_reset_mac(sc);
|
|
|
|
/* Step 2: Initialize MACCFG2 */
|
|
TSEC_WRITE(sc, TSEC_REG_MACCFG2,
|
|
TSEC_MACCFG2_FULLDUPLEX | /* Full Duplex = 1 */
|
|
TSEC_MACCFG2_PADCRC | /* PAD/CRC append */
|
|
TSEC_MACCFG2_GMII | /* I/F Mode bit */
|
|
TSEC_MACCFG2_PRECNT /* Preamble count = 7 */
|
|
);
|
|
|
|
/* Step 3: Initialize ECNTRL
|
|
* While the documentation states that R100M is ignored if RPM is
|
|
* not set, it does seem to be needed to get the orange boxes to
|
|
* work (which have a Marvell 88E1111 PHY). Go figure.
|
|
*/
|
|
|
|
/*
|
|
* XXX kludge - use circumstancial evidence to program ECNTRL
|
|
* correctly. Ideally we need some board information to guide
|
|
* us here.
|
|
*/
|
|
i = TSEC_READ(sc, TSEC_REG_ID2);
|
|
val = (i & 0xffff)
|
|
? (TSEC_ECNTRL_TBIM | TSEC_ECNTRL_SGMIIM) /* Sumatra */
|
|
: TSEC_ECNTRL_R100M; /* Orange + CDS */
|
|
TSEC_WRITE(sc, TSEC_REG_ECNTRL, TSEC_ECNTRL_STEN | val);
|
|
|
|
/* Step 4: Initialize MAC station address */
|
|
tsec_set_mac_address(sc);
|
|
|
|
/*
|
|
* Step 5: Assign a Physical address to the TBI so as to not conflict
|
|
* with the external PHY physical address
|
|
*/
|
|
TSEC_WRITE(sc, TSEC_REG_TBIPA, 5);
|
|
|
|
/* Step 6: Reset the management interface */
|
|
TSEC_WRITE(tsec0_sc, TSEC_REG_MIIMCFG, TSEC_MIIMCFG_RESETMGMT);
|
|
|
|
/* Step 7: Setup the MII Mgmt clock speed */
|
|
TSEC_WRITE(tsec0_sc, TSEC_REG_MIIMCFG, TSEC_MIIMCFG_CLKDIV28);
|
|
|
|
/* Step 8: Read MII Mgmt indicator register and check for Busy = 0 */
|
|
timeout = TSEC_READ_RETRY;
|
|
while (--timeout && (TSEC_READ(tsec0_sc, TSEC_REG_MIIMIND) &
|
|
TSEC_MIIMIND_BUSY))
|
|
DELAY(TSEC_READ_DELAY);
|
|
if (timeout == 0) {
|
|
if_printf(ifp, "tsec_init_locked(): Mgmt busy timeout\n");
|
|
return;
|
|
}
|
|
|
|
/* Step 9: Setup the MII Mgmt */
|
|
mii_mediachg(sc->tsec_mii);
|
|
|
|
/* Step 10: Clear IEVENT register */
|
|
TSEC_WRITE(sc, TSEC_REG_IEVENT, 0xffffffff);
|
|
|
|
/* Step 11: Enable interrupts */
|
|
#ifdef DEVICE_POLLING
|
|
/*
|
|
* ...only if polling is not turned on. Disable interrupts explicitly
|
|
* if polling is enabled.
|
|
*/
|
|
if (ifp->if_capenable & IFCAP_POLLING )
|
|
tsec_intrs_ctl(sc, 0);
|
|
else
|
|
#endif /* DEVICE_POLLING */
|
|
tsec_intrs_ctl(sc, 1);
|
|
|
|
/* Step 12: Initialize IADDRn */
|
|
TSEC_WRITE(sc, TSEC_REG_IADDR0, 0);
|
|
TSEC_WRITE(sc, TSEC_REG_IADDR1, 0);
|
|
TSEC_WRITE(sc, TSEC_REG_IADDR2, 0);
|
|
TSEC_WRITE(sc, TSEC_REG_IADDR3, 0);
|
|
TSEC_WRITE(sc, TSEC_REG_IADDR4, 0);
|
|
TSEC_WRITE(sc, TSEC_REG_IADDR5, 0);
|
|
TSEC_WRITE(sc, TSEC_REG_IADDR6, 0);
|
|
TSEC_WRITE(sc, TSEC_REG_IADDR7, 0);
|
|
|
|
/* Step 13: Initialize GADDRn */
|
|
TSEC_WRITE(sc, TSEC_REG_GADDR0, 0);
|
|
TSEC_WRITE(sc, TSEC_REG_GADDR1, 0);
|
|
TSEC_WRITE(sc, TSEC_REG_GADDR2, 0);
|
|
TSEC_WRITE(sc, TSEC_REG_GADDR3, 0);
|
|
TSEC_WRITE(sc, TSEC_REG_GADDR4, 0);
|
|
TSEC_WRITE(sc, TSEC_REG_GADDR5, 0);
|
|
TSEC_WRITE(sc, TSEC_REG_GADDR6, 0);
|
|
TSEC_WRITE(sc, TSEC_REG_GADDR7, 0);
|
|
|
|
/* Step 14: Initialize RCTRL */
|
|
TSEC_WRITE(sc, TSEC_REG_RCTRL, 0);
|
|
|
|
/* Step 15: Initialize DMACTRL */
|
|
tsec_dma_ctl(sc, 1);
|
|
|
|
/* Step 16: Initialize FIFO_PAUSE_CTRL */
|
|
TSEC_WRITE(sc, TSEC_REG_FIFO_PAUSE_CTRL, TSEC_FIFO_PAUSE_CTRL_EN);
|
|
|
|
/*
|
|
* Step 17: Initialize transmit/receive descriptor rings.
|
|
* Initialize TBASE and RBASE.
|
|
*/
|
|
TSEC_WRITE(sc, TSEC_REG_TBASE, sc->tsec_tx_raddr);
|
|
TSEC_WRITE(sc, TSEC_REG_RBASE, sc->tsec_rx_raddr);
|
|
|
|
for (i = 0; i < TSEC_TX_NUM_DESC; i++) {
|
|
tx_desc[i].bufptr = 0;
|
|
tx_desc[i].length = 0;
|
|
tx_desc[i].flags = ((i == TSEC_TX_NUM_DESC - 1) ?
|
|
TSEC_TXBD_W : 0);
|
|
}
|
|
bus_dmamap_sync(sc->tsec_tx_dtag, sc->tsec_tx_dmap,
|
|
BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
|
|
|
|
for (i = 0; i < TSEC_RX_NUM_DESC; i++) {
|
|
rx_desc[i].bufptr = sc->rx_data[i].paddr;
|
|
rx_desc[i].length = 0;
|
|
rx_desc[i].flags = TSEC_RXBD_E | TSEC_RXBD_I |
|
|
((i == TSEC_RX_NUM_DESC - 1) ? TSEC_RXBD_W : 0);
|
|
}
|
|
bus_dmamap_sync(sc->tsec_rx_dtag, sc->tsec_rx_dmap,
|
|
BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
|
|
|
|
/* Step 18: Initialize the maximum receive buffer length */
|
|
TSEC_WRITE(sc, TSEC_REG_MRBLR, MCLBYTES);
|
|
|
|
/* Step 19: Configure ethernet frame sizes */
|
|
TSEC_WRITE(sc, TSEC_REG_MINFLR, TSEC_MIN_FRAME_SIZE);
|
|
tsec_set_mtu(sc, ifp->if_mtu);
|
|
|
|
/* Step 20: Enable Rx and RxBD sdata snooping */
|
|
TSEC_WRITE(sc, TSEC_REG_ATTR, TSEC_ATTR_RDSEN | TSEC_ATTR_RBDSEN);
|
|
TSEC_WRITE(sc, TSEC_REG_ATTRELI, 0);
|
|
|
|
/* Step 21: Reset collision counters in hardware */
|
|
TSEC_WRITE(sc, TSEC_REG_MON_TSCL, 0);
|
|
TSEC_WRITE(sc, TSEC_REG_MON_TMCL, 0);
|
|
TSEC_WRITE(sc, TSEC_REG_MON_TLCL, 0);
|
|
TSEC_WRITE(sc, TSEC_REG_MON_TXCL, 0);
|
|
TSEC_WRITE(sc, TSEC_REG_MON_TNCL, 0);
|
|
|
|
/* Step 22: Mask all CAM interrupts */
|
|
TSEC_WRITE(sc, TSEC_REG_MON_CAM1, 0xffffffff);
|
|
TSEC_WRITE(sc, TSEC_REG_MON_CAM2, 0xffffffff);
|
|
|
|
/* Step 23: Enable Rx and Tx */
|
|
val = TSEC_READ(sc, TSEC_REG_MACCFG1);
|
|
val |= (TSEC_MACCFG1_RX_EN | TSEC_MACCFG1_TX_EN);
|
|
TSEC_WRITE(sc, TSEC_REG_MACCFG1, val);
|
|
|
|
/* Step 24: Reset TSEC counters for Tx and Rx rings */
|
|
TSEC_TX_RX_COUNTERS_INIT(sc);
|
|
|
|
/* Step 25: Setup TCP/IP Off-Load engine */
|
|
if (sc->is_etsec)
|
|
tsec_offload_setup(sc);
|
|
|
|
/* Step 26: Setup multicast filters */
|
|
tsec_setup_multicast(sc);
|
|
|
|
/* Step 27: Activate network interface */
|
|
ifp->if_drv_flags |= IFF_DRV_RUNNING;
|
|
ifp->if_drv_flags &= ~IFF_DRV_OACTIVE;
|
|
sc->tsec_if_flags = ifp->if_flags;
|
|
sc->tsec_watchdog = 0;
|
|
|
|
/* Schedule watchdog timeout */
|
|
callout_reset(&sc->tsec_callout, hz, tsec_tick, sc);
|
|
}
|
|
|
|
static void
|
|
tsec_set_mac_address(struct tsec_softc *sc)
|
|
{
|
|
uint32_t macbuf[2] = { 0, 0 };
|
|
char *macbufp, *curmac;
|
|
int i;
|
|
|
|
TSEC_GLOBAL_LOCK_ASSERT(sc);
|
|
|
|
KASSERT((ETHER_ADDR_LEN <= sizeof(macbuf)),
|
|
("tsec_set_mac_address: (%d <= %d", ETHER_ADDR_LEN,
|
|
sizeof(macbuf)));
|
|
|
|
macbufp = (char *)macbuf;
|
|
curmac = (char *)IF_LLADDR(sc->tsec_ifp);
|
|
|
|
/* Correct order of MAC address bytes */
|
|
for (i = 1; i <= ETHER_ADDR_LEN; i++)
|
|
macbufp[ETHER_ADDR_LEN-i] = curmac[i-1];
|
|
|
|
/* Initialize MAC station address MACSTNADDR2 and MACSTNADDR1 */
|
|
TSEC_WRITE(sc, TSEC_REG_MACSTNADDR2, macbuf[1]);
|
|
TSEC_WRITE(sc, TSEC_REG_MACSTNADDR1, macbuf[0]);
|
|
}
|
|
|
|
/*
|
|
* DMA control function, if argument state is:
|
|
* 0 - DMA engine will be disabled
|
|
* 1 - DMA engine will be enabled
|
|
*/
|
|
static void
|
|
tsec_dma_ctl(struct tsec_softc *sc, int state)
|
|
{
|
|
device_t dev;
|
|
uint32_t dma_flags, timeout;
|
|
|
|
dev = sc->dev;
|
|
|
|
dma_flags = TSEC_READ(sc, TSEC_REG_DMACTRL);
|
|
|
|
switch (state) {
|
|
case 0:
|
|
/* Temporarily clear stop graceful stop bits. */
|
|
tsec_dma_ctl(sc, 1000);
|
|
|
|
/* Set it again */
|
|
dma_flags |= (TSEC_DMACTRL_GRS | TSEC_DMACTRL_GTS);
|
|
break;
|
|
case 1000:
|
|
case 1:
|
|
/* Set write with response (WWR), wait (WOP) and snoop bits */
|
|
dma_flags |= (TSEC_DMACTRL_TDSEN | TSEC_DMACTRL_TBDSEN |
|
|
DMACTRL_WWR | DMACTRL_WOP);
|
|
|
|
/* Clear graceful stop bits */
|
|
dma_flags &= ~(TSEC_DMACTRL_GRS | TSEC_DMACTRL_GTS);
|
|
break;
|
|
default:
|
|
device_printf(dev, "tsec_dma_ctl(): unknown state value: %d\n",
|
|
state);
|
|
}
|
|
|
|
TSEC_WRITE(sc, TSEC_REG_DMACTRL, dma_flags);
|
|
|
|
switch (state) {
|
|
case 0:
|
|
/* Wait for DMA stop */
|
|
timeout = TSEC_READ_RETRY;
|
|
while (--timeout && (!(TSEC_READ(sc, TSEC_REG_IEVENT) &
|
|
(TSEC_IEVENT_GRSC | TSEC_IEVENT_GTSC))))
|
|
DELAY(TSEC_READ_DELAY);
|
|
|
|
if (timeout == 0)
|
|
device_printf(dev, "tsec_dma_ctl(): timeout!\n");
|
|
break;
|
|
case 1:
|
|
/* Restart transmission function */
|
|
TSEC_WRITE(sc, TSEC_REG_TSTAT, TSEC_TSTAT_THLT);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Interrupts control function, if argument state is:
|
|
* 0 - all TSEC interrupts will be masked
|
|
* 1 - all TSEC interrupts will be unmasked
|
|
*/
|
|
static void
|
|
tsec_intrs_ctl(struct tsec_softc *sc, int state)
|
|
{
|
|
device_t dev;
|
|
|
|
dev = sc->dev;
|
|
|
|
switch (state) {
|
|
case 0:
|
|
TSEC_WRITE(sc, TSEC_REG_IMASK, 0);
|
|
break;
|
|
case 1:
|
|
TSEC_WRITE(sc, TSEC_REG_IMASK, TSEC_IMASK_BREN |
|
|
TSEC_IMASK_RXCEN | TSEC_IMASK_BSYEN | TSEC_IMASK_EBERREN |
|
|
TSEC_IMASK_BTEN | TSEC_IMASK_TXEEN | TSEC_IMASK_TXBEN |
|
|
TSEC_IMASK_TXFEN | TSEC_IMASK_XFUNEN | TSEC_IMASK_RXFEN);
|
|
break;
|
|
default:
|
|
device_printf(dev, "tsec_intrs_ctl(): unknown state value: %d\n",
|
|
state);
|
|
}
|
|
}
|
|
|
|
static void
|
|
tsec_reset_mac(struct tsec_softc *sc)
|
|
{
|
|
uint32_t maccfg1_flags;
|
|
|
|
/* Set soft reset bit */
|
|
maccfg1_flags = TSEC_READ(sc, TSEC_REG_MACCFG1);
|
|
maccfg1_flags |= TSEC_MACCFG1_SOFT_RESET;
|
|
TSEC_WRITE(sc, TSEC_REG_MACCFG1, maccfg1_flags);
|
|
|
|
/* Clear soft reset bit */
|
|
maccfg1_flags = TSEC_READ(sc, TSEC_REG_MACCFG1);
|
|
maccfg1_flags &= ~TSEC_MACCFG1_SOFT_RESET;
|
|
TSEC_WRITE(sc, TSEC_REG_MACCFG1, maccfg1_flags);
|
|
}
|
|
|
|
static void
|
|
tsec_watchdog(struct tsec_softc *sc)
|
|
{
|
|
struct ifnet *ifp;
|
|
|
|
TSEC_GLOBAL_LOCK_ASSERT(sc);
|
|
|
|
if (sc->tsec_watchdog == 0 || --sc->tsec_watchdog > 0)
|
|
return;
|
|
|
|
ifp = sc->tsec_ifp;
|
|
ifp->if_oerrors++;
|
|
if_printf(ifp, "watchdog timeout\n");
|
|
|
|
tsec_stop(sc);
|
|
tsec_init_locked(sc);
|
|
}
|
|
|
|
static void
|
|
tsec_start(struct ifnet *ifp)
|
|
{
|
|
struct tsec_softc *sc = ifp->if_softc;
|
|
|
|
TSEC_TRANSMIT_LOCK(sc);
|
|
tsec_start_locked(ifp);
|
|
TSEC_TRANSMIT_UNLOCK(sc);
|
|
}
|
|
|
|
static void
|
|
tsec_start_locked(struct ifnet *ifp)
|
|
{
|
|
struct tsec_softc *sc;
|
|
struct mbuf *m0, *mtmp;
|
|
struct tsec_tx_fcb *tx_fcb;
|
|
unsigned int queued = 0;
|
|
int csum_flags, fcb_inserted = 0;
|
|
|
|
sc = ifp->if_softc;
|
|
|
|
TSEC_TRANSMIT_LOCK_ASSERT(sc);
|
|
|
|
if ((ifp->if_drv_flags & (IFF_DRV_RUNNING | IFF_DRV_OACTIVE)) !=
|
|
IFF_DRV_RUNNING)
|
|
return;
|
|
|
|
if (sc->tsec_link == 0)
|
|
return;
|
|
|
|
bus_dmamap_sync(sc->tsec_tx_dtag, sc->tsec_tx_dmap,
|
|
BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
|
|
|
|
for (;;) {
|
|
/* Get packet from the queue */
|
|
IF_DEQUEUE(&ifp->if_snd, m0);
|
|
if (m0 == NULL)
|
|
break;
|
|
|
|
/* Insert TCP/IP Off-load frame control block */
|
|
csum_flags = m0->m_pkthdr.csum_flags;
|
|
if (csum_flags) {
|
|
|
|
M_PREPEND(m0, sizeof(struct tsec_tx_fcb), M_DONTWAIT);
|
|
if (m0 == NULL)
|
|
break;
|
|
|
|
tx_fcb = mtod(m0, struct tsec_tx_fcb *);
|
|
tx_fcb->flags = 0;
|
|
tx_fcb->l3_offset = ETHER_HDR_LEN;
|
|
tx_fcb->l4_offset = sizeof(struct ip);
|
|
|
|
if (csum_flags & CSUM_IP)
|
|
tx_fcb->flags |= TSEC_TX_FCB_IP4 |
|
|
TSEC_TX_FCB_CSUM_IP;
|
|
|
|
if (csum_flags & CSUM_TCP)
|
|
tx_fcb->flags |= TSEC_TX_FCB_TCP |
|
|
TSEC_TX_FCB_CSUM_TCP_UDP;
|
|
|
|
if (csum_flags & CSUM_UDP)
|
|
tx_fcb->flags |= TSEC_TX_FCB_UDP |
|
|
TSEC_TX_FCB_CSUM_TCP_UDP;
|
|
|
|
fcb_inserted = 1;
|
|
}
|
|
|
|
mtmp = m_defrag(m0, M_DONTWAIT);
|
|
if (mtmp)
|
|
m0 = mtmp;
|
|
|
|
if (tsec_encap(sc, m0, fcb_inserted)) {
|
|
IF_PREPEND(&ifp->if_snd, m0);
|
|
ifp->if_drv_flags |= IFF_DRV_OACTIVE;
|
|
break;
|
|
}
|
|
queued++;
|
|
BPF_MTAP(ifp, m0);
|
|
}
|
|
bus_dmamap_sync(sc->tsec_tx_dtag, sc->tsec_tx_dmap,
|
|
BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
|
|
|
|
if (queued) {
|
|
/* Enable transmitter and watchdog timer */
|
|
TSEC_WRITE(sc, TSEC_REG_TSTAT, TSEC_TSTAT_THLT);
|
|
sc->tsec_watchdog = 5;
|
|
}
|
|
}
|
|
|
|
static int
|
|
tsec_encap(struct tsec_softc *sc, struct mbuf *m0, int fcb_inserted)
|
|
{
|
|
struct tsec_desc *tx_desc = NULL;
|
|
struct ifnet *ifp;
|
|
bus_dma_segment_t segs[TSEC_TX_NUM_DESC];
|
|
bus_dmamap_t *mapp;
|
|
int csum_flag = 0, error, seg, nsegs;
|
|
|
|
TSEC_TRANSMIT_LOCK_ASSERT(sc);
|
|
|
|
ifp = sc->tsec_ifp;
|
|
|
|
if (TSEC_FREE_TX_DESC(sc) == 0) {
|
|
/* No free descriptors */
|
|
return (-1);
|
|
}
|
|
|
|
/* Fetch unused map */
|
|
mapp = TSEC_ALLOC_TX_MAP(sc);
|
|
|
|
/* Create mapping in DMA memory */
|
|
error = bus_dmamap_load_mbuf_sg(sc->tsec_tx_mtag,
|
|
*mapp, m0, segs, &nsegs, BUS_DMA_NOWAIT);
|
|
if (error != 0 || nsegs > TSEC_FREE_TX_DESC(sc) || nsegs <= 0) {
|
|
bus_dmamap_unload(sc->tsec_tx_mtag, *mapp);
|
|
TSEC_FREE_TX_MAP(sc, mapp);
|
|
return ((error != 0) ? error : -1);
|
|
}
|
|
bus_dmamap_sync(sc->tsec_tx_mtag, *mapp, BUS_DMASYNC_PREWRITE);
|
|
|
|
if ((ifp->if_flags & IFF_DEBUG) && (nsegs > 1))
|
|
if_printf(ifp, "TX buffer has %d segments\n", nsegs);
|
|
|
|
if (fcb_inserted)
|
|
csum_flag = TSEC_TXBD_TOE;
|
|
|
|
/* Everything is ok, now we can send buffers */
|
|
for (seg = 0; seg < nsegs; seg++) {
|
|
tx_desc = TSEC_GET_CUR_TX_DESC(sc);
|
|
|
|
tx_desc->length = segs[seg].ds_len;
|
|
tx_desc->bufptr = segs[seg].ds_addr;
|
|
|
|
/*
|
|
* Set flags:
|
|
* - wrap
|
|
* - checksum
|
|
* - ready to send
|
|
* - transmit the CRC sequence after the last data byte
|
|
* - interrupt after the last buffer
|
|
*/
|
|
tx_desc->flags =
|
|
(tx_desc->flags & TSEC_TXBD_W) |
|
|
((seg == 0) ? csum_flag : 0) | TSEC_TXBD_R | TSEC_TXBD_TC |
|
|
((seg == nsegs - 1) ? TSEC_TXBD_L | TSEC_TXBD_I : 0);
|
|
}
|
|
|
|
/* Save mbuf and DMA mapping for release at later stage */
|
|
TSEC_PUT_TX_MBUF(sc, m0);
|
|
TSEC_PUT_TX_MAP(sc, mapp);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static void
|
|
tsec_setfilter(struct tsec_softc *sc)
|
|
{
|
|
struct ifnet *ifp;
|
|
uint32_t flags;
|
|
|
|
ifp = sc->tsec_ifp;
|
|
flags = TSEC_READ(sc, TSEC_REG_RCTRL);
|
|
|
|
/* Promiscuous mode */
|
|
if (ifp->if_flags & IFF_PROMISC)
|
|
flags |= TSEC_RCTRL_PROM;
|
|
else
|
|
flags &= ~TSEC_RCTRL_PROM;
|
|
|
|
TSEC_WRITE(sc, TSEC_REG_RCTRL, flags);
|
|
}
|
|
|
|
#ifdef DEVICE_POLLING
|
|
static poll_handler_t tsec_poll;
|
|
|
|
static int
|
|
tsec_poll(struct ifnet *ifp, enum poll_cmd cmd, int count)
|
|
{
|
|
uint32_t ie;
|
|
struct tsec_softc *sc = ifp->if_softc;
|
|
int rx_npkts;
|
|
|
|
rx_npkts = 0;
|
|
|
|
TSEC_GLOBAL_LOCK(sc);
|
|
if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) {
|
|
TSEC_GLOBAL_UNLOCK(sc);
|
|
return (rx_npkts);
|
|
}
|
|
|
|
if (cmd == POLL_AND_CHECK_STATUS) {
|
|
tsec_error_intr_locked(sc, count);
|
|
|
|
/* Clear all events reported */
|
|
ie = TSEC_READ(sc, TSEC_REG_IEVENT);
|
|
TSEC_WRITE(sc, TSEC_REG_IEVENT, ie);
|
|
}
|
|
|
|
tsec_transmit_intr_locked(sc);
|
|
|
|
TSEC_GLOBAL_TO_RECEIVE_LOCK(sc);
|
|
|
|
rx_npkts = tsec_receive_intr_locked(sc, count);
|
|
|
|
TSEC_RECEIVE_UNLOCK(sc);
|
|
|
|
return (rx_npkts);
|
|
}
|
|
#endif /* DEVICE_POLLING */
|
|
|
|
static int
|
|
tsec_ioctl(struct ifnet *ifp, u_long command, caddr_t data)
|
|
{
|
|
struct tsec_softc *sc = ifp->if_softc;
|
|
struct ifreq *ifr = (struct ifreq *)data;
|
|
device_t dev;
|
|
int mask, error = 0;
|
|
|
|
dev = sc->dev;
|
|
|
|
switch (command) {
|
|
case SIOCSIFMTU:
|
|
TSEC_GLOBAL_LOCK(sc);
|
|
if (tsec_set_mtu(sc, ifr->ifr_mtu))
|
|
ifp->if_mtu = ifr->ifr_mtu;
|
|
else
|
|
error = EINVAL;
|
|
TSEC_GLOBAL_UNLOCK(sc);
|
|
break;
|
|
case SIOCSIFFLAGS:
|
|
TSEC_GLOBAL_LOCK(sc);
|
|
if (ifp->if_flags & IFF_UP) {
|
|
if (ifp->if_drv_flags & IFF_DRV_RUNNING) {
|
|
if ((sc->tsec_if_flags ^ ifp->if_flags) &
|
|
IFF_PROMISC)
|
|
tsec_setfilter(sc);
|
|
|
|
if ((sc->tsec_if_flags ^ ifp->if_flags) &
|
|
IFF_ALLMULTI)
|
|
tsec_setup_multicast(sc);
|
|
} else
|
|
tsec_init_locked(sc);
|
|
} else if (ifp->if_drv_flags & IFF_DRV_RUNNING)
|
|
tsec_stop(sc);
|
|
|
|
sc->tsec_if_flags = ifp->if_flags;
|
|
TSEC_GLOBAL_UNLOCK(sc);
|
|
break;
|
|
case SIOCADDMULTI:
|
|
case SIOCDELMULTI:
|
|
if (ifp->if_drv_flags & IFF_DRV_RUNNING) {
|
|
TSEC_GLOBAL_LOCK(sc);
|
|
tsec_setup_multicast(sc);
|
|
TSEC_GLOBAL_UNLOCK(sc);
|
|
}
|
|
case SIOCGIFMEDIA:
|
|
case SIOCSIFMEDIA:
|
|
error = ifmedia_ioctl(ifp, ifr, &sc->tsec_mii->mii_media,
|
|
command);
|
|
break;
|
|
case SIOCSIFCAP:
|
|
mask = ifp->if_capenable ^ ifr->ifr_reqcap;
|
|
if ((mask & IFCAP_HWCSUM) && sc->is_etsec) {
|
|
TSEC_GLOBAL_LOCK(sc);
|
|
ifp->if_capenable &= ~IFCAP_HWCSUM;
|
|
ifp->if_capenable |= IFCAP_HWCSUM & ifr->ifr_reqcap;
|
|
tsec_offload_setup(sc);
|
|
TSEC_GLOBAL_UNLOCK(sc);
|
|
}
|
|
#ifdef DEVICE_POLLING
|
|
if (mask & IFCAP_POLLING) {
|
|
if (ifr->ifr_reqcap & IFCAP_POLLING) {
|
|
error = ether_poll_register(tsec_poll, ifp);
|
|
if (error)
|
|
return (error);
|
|
|
|
TSEC_GLOBAL_LOCK(sc);
|
|
/* Disable interrupts */
|
|
tsec_intrs_ctl(sc, 0);
|
|
ifp->if_capenable |= IFCAP_POLLING;
|
|
TSEC_GLOBAL_UNLOCK(sc);
|
|
} else {
|
|
error = ether_poll_deregister(ifp);
|
|
TSEC_GLOBAL_LOCK(sc);
|
|
/* Enable interrupts */
|
|
tsec_intrs_ctl(sc, 1);
|
|
ifp->if_capenable &= ~IFCAP_POLLING;
|
|
TSEC_GLOBAL_UNLOCK(sc);
|
|
}
|
|
}
|
|
#endif
|
|
break;
|
|
|
|
default:
|
|
error = ether_ioctl(ifp, command, data);
|
|
}
|
|
|
|
/* Flush buffers if not empty */
|
|
if (ifp->if_flags & IFF_UP)
|
|
tsec_start(ifp);
|
|
return (error);
|
|
}
|
|
|
|
static int
|
|
tsec_ifmedia_upd(struct ifnet *ifp)
|
|
{
|
|
struct tsec_softc *sc = ifp->if_softc;
|
|
struct mii_data *mii;
|
|
|
|
TSEC_TRANSMIT_LOCK(sc);
|
|
|
|
mii = sc->tsec_mii;
|
|
mii_mediachg(mii);
|
|
|
|
TSEC_TRANSMIT_UNLOCK(sc);
|
|
return (0);
|
|
}
|
|
|
|
static void
|
|
tsec_ifmedia_sts(struct ifnet *ifp, struct ifmediareq *ifmr)
|
|
{
|
|
struct tsec_softc *sc = ifp->if_softc;
|
|
struct mii_data *mii;
|
|
|
|
TSEC_TRANSMIT_LOCK(sc);
|
|
|
|
mii = sc->tsec_mii;
|
|
mii_pollstat(mii);
|
|
|
|
ifmr->ifm_active = mii->mii_media_active;
|
|
ifmr->ifm_status = mii->mii_media_status;
|
|
|
|
TSEC_TRANSMIT_UNLOCK(sc);
|
|
}
|
|
|
|
static int
|
|
tsec_new_rxbuf(bus_dma_tag_t tag, bus_dmamap_t map, struct mbuf **mbufp,
|
|
uint32_t *paddr)
|
|
{
|
|
struct mbuf *new_mbuf;
|
|
bus_dma_segment_t seg[1];
|
|
int error, nsegs;
|
|
|
|
KASSERT(mbufp != NULL, ("NULL mbuf pointer!"));
|
|
|
|
new_mbuf = m_getjcl(M_DONTWAIT, MT_DATA, M_PKTHDR, MCLBYTES);
|
|
if (new_mbuf == NULL)
|
|
return (ENOBUFS);
|
|
new_mbuf->m_len = new_mbuf->m_pkthdr.len = new_mbuf->m_ext.ext_size;
|
|
|
|
if (*mbufp) {
|
|
bus_dmamap_sync(tag, map, BUS_DMASYNC_POSTREAD);
|
|
bus_dmamap_unload(tag, map);
|
|
}
|
|
|
|
error = bus_dmamap_load_mbuf_sg(tag, map, new_mbuf, seg, &nsegs,
|
|
BUS_DMA_NOWAIT);
|
|
KASSERT(nsegs == 1, ("Too many segments returned!"));
|
|
if (nsegs != 1 || error)
|
|
panic("tsec_new_rxbuf(): nsegs(%d), error(%d)", nsegs, error);
|
|
|
|
#if 0
|
|
if (error) {
|
|
printf("tsec: bus_dmamap_load_mbuf_sg() returned: %d!\n",
|
|
error);
|
|
m_freem(new_mbuf);
|
|
return (ENOBUFS);
|
|
}
|
|
#endif
|
|
|
|
#if 0
|
|
KASSERT(((seg->ds_addr) & (TSEC_RXBUFFER_ALIGNMENT-1)) == 0,
|
|
("Wrong alignment of RX buffer!"));
|
|
#endif
|
|
bus_dmamap_sync(tag, map, BUS_DMASYNC_PREREAD);
|
|
|
|
(*mbufp) = new_mbuf;
|
|
(*paddr) = seg->ds_addr;
|
|
return (0);
|
|
}
|
|
|
|
static void
|
|
tsec_map_dma_addr(void *arg, bus_dma_segment_t *segs, int nseg, int error)
|
|
{
|
|
u_int32_t *paddr;
|
|
|
|
KASSERT(nseg == 1, ("wrong number of segments, should be 1"));
|
|
paddr = arg;
|
|
*paddr = segs->ds_addr;
|
|
}
|
|
|
|
static int
|
|
tsec_alloc_dma_desc(device_t dev, bus_dma_tag_t *dtag, bus_dmamap_t *dmap,
|
|
bus_size_t dsize, void **vaddr, void *raddr, const char *dname)
|
|
{
|
|
int error;
|
|
|
|
/* Allocate a busdma tag and DMA safe memory for TX/RX descriptors. */
|
|
error = bus_dma_tag_create(NULL, /* parent */
|
|
PAGE_SIZE, 0, /* alignment, boundary */
|
|
BUS_SPACE_MAXADDR_32BIT, /* lowaddr */
|
|
BUS_SPACE_MAXADDR, /* highaddr */
|
|
NULL, NULL, /* filtfunc, filtfuncarg */
|
|
dsize, 1, /* maxsize, nsegments */
|
|
dsize, 0, /* maxsegsz, flags */
|
|
NULL, NULL, /* lockfunc, lockfuncarg */
|
|
dtag); /* dmat */
|
|
|
|
if (error) {
|
|
device_printf(dev, "failed to allocate busdma %s tag\n",
|
|
dname);
|
|
(*vaddr) = NULL;
|
|
return (ENXIO);
|
|
}
|
|
|
|
error = bus_dmamem_alloc(*dtag, vaddr, BUS_DMA_NOWAIT | BUS_DMA_ZERO,
|
|
dmap);
|
|
if (error) {
|
|
device_printf(dev, "failed to allocate %s DMA safe memory\n",
|
|
dname);
|
|
bus_dma_tag_destroy(*dtag);
|
|
(*vaddr) = NULL;
|
|
return (ENXIO);
|
|
}
|
|
|
|
error = bus_dmamap_load(*dtag, *dmap, *vaddr, dsize,
|
|
tsec_map_dma_addr, raddr, BUS_DMA_NOWAIT);
|
|
if (error) {
|
|
device_printf(dev, "cannot get address of the %s "
|
|
"descriptors\n", dname);
|
|
bus_dmamem_free(*dtag, *vaddr, *dmap);
|
|
bus_dma_tag_destroy(*dtag);
|
|
(*vaddr) = NULL;
|
|
return (ENXIO);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
static void
|
|
tsec_free_dma_desc(bus_dma_tag_t dtag, bus_dmamap_t dmap, void *vaddr)
|
|
{
|
|
|
|
if (vaddr == NULL)
|
|
return;
|
|
|
|
/* Unmap descriptors from DMA memory */
|
|
bus_dmamap_sync(dtag, dmap, BUS_DMASYNC_POSTREAD |
|
|
BUS_DMASYNC_POSTWRITE);
|
|
bus_dmamap_unload(dtag, dmap);
|
|
|
|
/* Free descriptors memory */
|
|
bus_dmamem_free(dtag, vaddr, dmap);
|
|
|
|
/* Destroy descriptors tag */
|
|
bus_dma_tag_destroy(dtag);
|
|
}
|
|
|
|
static void
|
|
tsec_free_dma(struct tsec_softc *sc)
|
|
{
|
|
int i;
|
|
|
|
/* Free TX maps */
|
|
for (i = 0; i < TSEC_TX_NUM_DESC; i++)
|
|
if (sc->tx_map_data[i] != NULL)
|
|
bus_dmamap_destroy(sc->tsec_tx_mtag,
|
|
sc->tx_map_data[i]);
|
|
/* Destroy tag for TX mbufs */
|
|
bus_dma_tag_destroy(sc->tsec_tx_mtag);
|
|
|
|
/* Free RX mbufs and maps */
|
|
for (i = 0; i < TSEC_RX_NUM_DESC; i++) {
|
|
if (sc->rx_data[i].mbuf) {
|
|
/* Unload buffer from DMA */
|
|
bus_dmamap_sync(sc->tsec_rx_mtag, sc->rx_data[i].map,
|
|
BUS_DMASYNC_POSTREAD);
|
|
bus_dmamap_unload(sc->tsec_rx_mtag,
|
|
sc->rx_data[i].map);
|
|
|
|
/* Free buffer */
|
|
m_freem(sc->rx_data[i].mbuf);
|
|
}
|
|
/* Destroy map for this buffer */
|
|
if (sc->rx_data[i].map != NULL)
|
|
bus_dmamap_destroy(sc->tsec_rx_mtag,
|
|
sc->rx_data[i].map);
|
|
}
|
|
/* Destroy tag for RX mbufs */
|
|
bus_dma_tag_destroy(sc->tsec_rx_mtag);
|
|
|
|
/* Unload TX/RX descriptors */
|
|
tsec_free_dma_desc(sc->tsec_tx_dtag, sc->tsec_tx_dmap,
|
|
sc->tsec_tx_vaddr);
|
|
tsec_free_dma_desc(sc->tsec_rx_dtag, sc->tsec_rx_dmap,
|
|
sc->tsec_rx_vaddr);
|
|
}
|
|
|
|
static void
|
|
tsec_stop(struct tsec_softc *sc)
|
|
{
|
|
struct ifnet *ifp;
|
|
struct mbuf *m0;
|
|
bus_dmamap_t *mapp;
|
|
uint32_t tmpval;
|
|
|
|
TSEC_GLOBAL_LOCK_ASSERT(sc);
|
|
|
|
ifp = sc->tsec_ifp;
|
|
|
|
/* Disable interface and watchdog timer */
|
|
callout_stop(&sc->tsec_callout);
|
|
ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE);
|
|
sc->tsec_watchdog = 0;
|
|
|
|
/* Disable all interrupts and stop DMA */
|
|
tsec_intrs_ctl(sc, 0);
|
|
tsec_dma_ctl(sc, 0);
|
|
|
|
/* Remove pending data from TX queue */
|
|
while (!TSEC_EMPTYQ_TX_MBUF(sc)) {
|
|
m0 = TSEC_GET_TX_MBUF(sc);
|
|
mapp = TSEC_GET_TX_MAP(sc);
|
|
|
|
bus_dmamap_sync(sc->tsec_tx_mtag, *mapp,
|
|
BUS_DMASYNC_POSTWRITE);
|
|
bus_dmamap_unload(sc->tsec_tx_mtag, *mapp);
|
|
|
|
TSEC_FREE_TX_MAP(sc, mapp);
|
|
m_freem(m0);
|
|
}
|
|
|
|
/* Disable RX and TX */
|
|
tmpval = TSEC_READ(sc, TSEC_REG_MACCFG1);
|
|
tmpval &= ~(TSEC_MACCFG1_RX_EN | TSEC_MACCFG1_TX_EN);
|
|
TSEC_WRITE(sc, TSEC_REG_MACCFG1, tmpval);
|
|
DELAY(10);
|
|
}
|
|
|
|
static void
|
|
tsec_tick(void *arg)
|
|
{
|
|
struct tsec_softc *sc = arg;
|
|
struct ifnet *ifp;
|
|
int link;
|
|
|
|
TSEC_GLOBAL_LOCK(sc);
|
|
|
|
tsec_watchdog(sc);
|
|
|
|
ifp = sc->tsec_ifp;
|
|
link = sc->tsec_link;
|
|
|
|
mii_tick(sc->tsec_mii);
|
|
|
|
if (link == 0 && sc->tsec_link == 1 &&
|
|
(!IFQ_DRV_IS_EMPTY(&ifp->if_snd)))
|
|
tsec_start_locked(ifp);
|
|
|
|
/* Schedule another timeout one second from now. */
|
|
callout_reset(&sc->tsec_callout, hz, tsec_tick, sc);
|
|
|
|
TSEC_GLOBAL_UNLOCK(sc);
|
|
}
|
|
|
|
/*
|
|
* This is the core RX routine. It replenishes mbufs in the descriptor and
|
|
* sends data which have been dma'ed into host memory to upper layer.
|
|
*
|
|
* Loops at most count times if count is > 0, or until done if count < 0.
|
|
*/
|
|
static int
|
|
tsec_receive_intr_locked(struct tsec_softc *sc, int count)
|
|
{
|
|
struct tsec_desc *rx_desc;
|
|
struct ifnet *ifp;
|
|
struct rx_data_type *rx_data;
|
|
struct mbuf *m;
|
|
device_t dev;
|
|
uint32_t i;
|
|
int c, rx_npkts;
|
|
uint16_t flags;
|
|
|
|
TSEC_RECEIVE_LOCK_ASSERT(sc);
|
|
|
|
ifp = sc->tsec_ifp;
|
|
rx_data = sc->rx_data;
|
|
dev = sc->dev;
|
|
rx_npkts = 0;
|
|
|
|
bus_dmamap_sync(sc->tsec_rx_dtag, sc->tsec_rx_dmap,
|
|
BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
|
|
|
|
for (c = 0; ; c++) {
|
|
if (count >= 0 && count-- == 0)
|
|
break;
|
|
|
|
rx_desc = TSEC_GET_CUR_RX_DESC(sc);
|
|
flags = rx_desc->flags;
|
|
|
|
/* Check if there is anything to receive */
|
|
if ((flags & TSEC_RXBD_E) || (c >= TSEC_RX_NUM_DESC)) {
|
|
/*
|
|
* Avoid generating another interrupt
|
|
*/
|
|
if (flags & TSEC_RXBD_E)
|
|
TSEC_WRITE(sc, TSEC_REG_IEVENT,
|
|
TSEC_IEVENT_RXB | TSEC_IEVENT_RXF);
|
|
/*
|
|
* We didn't consume current descriptor and have to
|
|
* return it to the queue
|
|
*/
|
|
TSEC_BACK_CUR_RX_DESC(sc);
|
|
break;
|
|
}
|
|
|
|
if (flags & (TSEC_RXBD_LG | TSEC_RXBD_SH | TSEC_RXBD_NO |
|
|
TSEC_RXBD_CR | TSEC_RXBD_OV | TSEC_RXBD_TR)) {
|
|
|
|
rx_desc->length = 0;
|
|
rx_desc->flags = (rx_desc->flags &
|
|
~TSEC_RXBD_ZEROONINIT) | TSEC_RXBD_E | TSEC_RXBD_I;
|
|
|
|
if (sc->frame != NULL) {
|
|
m_free(sc->frame);
|
|
sc->frame = NULL;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
/* Ok... process frame */
|
|
i = TSEC_GET_CUR_RX_DESC_CNT(sc);
|
|
m = rx_data[i].mbuf;
|
|
m->m_len = rx_desc->length;
|
|
|
|
if (sc->frame != NULL) {
|
|
if ((flags & TSEC_RXBD_L) != 0)
|
|
m->m_len -= m_length(sc->frame, NULL);
|
|
|
|
m->m_flags &= ~M_PKTHDR;
|
|
m_cat(sc->frame, m);
|
|
} else {
|
|
sc->frame = m;
|
|
}
|
|
|
|
m = NULL;
|
|
|
|
if ((flags & TSEC_RXBD_L) != 0) {
|
|
m = sc->frame;
|
|
sc->frame = NULL;
|
|
}
|
|
|
|
if (tsec_new_rxbuf(sc->tsec_rx_mtag, rx_data[i].map,
|
|
&rx_data[i].mbuf, &rx_data[i].paddr)) {
|
|
ifp->if_ierrors++;
|
|
/*
|
|
* We ran out of mbufs; didn't consume current
|
|
* descriptor and have to return it to the queue.
|
|
*/
|
|
TSEC_BACK_CUR_RX_DESC(sc);
|
|
break;
|
|
}
|
|
|
|
/* Attach new buffer to descriptor and clear flags */
|
|
rx_desc->bufptr = rx_data[i].paddr;
|
|
rx_desc->length = 0;
|
|
rx_desc->flags = (rx_desc->flags & ~TSEC_RXBD_ZEROONINIT) |
|
|
TSEC_RXBD_E | TSEC_RXBD_I;
|
|
|
|
if (m != NULL) {
|
|
m->m_pkthdr.rcvif = ifp;
|
|
|
|
m_fixhdr(m);
|
|
m_adj(m, -ETHER_CRC_LEN);
|
|
|
|
if (sc->is_etsec)
|
|
tsec_offload_process_frame(sc, m);
|
|
|
|
TSEC_RECEIVE_UNLOCK(sc);
|
|
(*ifp->if_input)(ifp, m);
|
|
TSEC_RECEIVE_LOCK(sc);
|
|
rx_npkts++;
|
|
}
|
|
}
|
|
|
|
bus_dmamap_sync(sc->tsec_rx_dtag, sc->tsec_rx_dmap,
|
|
BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
|
|
|
|
/*
|
|
* Make sure TSEC receiver is not halted.
|
|
*
|
|
* Various conditions can stop the TSEC receiver, but not all are
|
|
* signaled and handled by error interrupt, so make sure the receiver
|
|
* is running. Writing to TSEC_REG_RSTAT restarts the receiver when
|
|
* halted, and is harmless if already running.
|
|
*/
|
|
TSEC_WRITE(sc, TSEC_REG_RSTAT, TSEC_RSTAT_QHLT);
|
|
return (rx_npkts);
|
|
}
|
|
|
|
void
|
|
tsec_receive_intr(void *arg)
|
|
{
|
|
struct tsec_softc *sc = arg;
|
|
|
|
TSEC_RECEIVE_LOCK(sc);
|
|
|
|
#ifdef DEVICE_POLLING
|
|
if (sc->tsec_ifp->if_capenable & IFCAP_POLLING) {
|
|
TSEC_RECEIVE_UNLOCK(sc);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
/* Confirm the interrupt was received by driver */
|
|
TSEC_WRITE(sc, TSEC_REG_IEVENT, TSEC_IEVENT_RXB | TSEC_IEVENT_RXF);
|
|
tsec_receive_intr_locked(sc, -1);
|
|
|
|
TSEC_RECEIVE_UNLOCK(sc);
|
|
}
|
|
|
|
static void
|
|
tsec_transmit_intr_locked(struct tsec_softc *sc)
|
|
{
|
|
struct tsec_desc *tx_desc;
|
|
struct ifnet *ifp;
|
|
struct mbuf *m0;
|
|
bus_dmamap_t *mapp;
|
|
int send = 0;
|
|
|
|
TSEC_TRANSMIT_LOCK_ASSERT(sc);
|
|
|
|
ifp = sc->tsec_ifp;
|
|
|
|
/* Update collision statistics */
|
|
ifp->if_collisions += TSEC_READ(sc, TSEC_REG_MON_TNCL);
|
|
|
|
/* Reset collision counters in hardware */
|
|
TSEC_WRITE(sc, TSEC_REG_MON_TSCL, 0);
|
|
TSEC_WRITE(sc, TSEC_REG_MON_TMCL, 0);
|
|
TSEC_WRITE(sc, TSEC_REG_MON_TLCL, 0);
|
|
TSEC_WRITE(sc, TSEC_REG_MON_TXCL, 0);
|
|
TSEC_WRITE(sc, TSEC_REG_MON_TNCL, 0);
|
|
|
|
bus_dmamap_sync(sc->tsec_tx_dtag, sc->tsec_tx_dmap,
|
|
BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
|
|
|
|
while (TSEC_CUR_DIFF_DIRTY_TX_DESC(sc)) {
|
|
tx_desc = TSEC_GET_DIRTY_TX_DESC(sc);
|
|
if (tx_desc->flags & TSEC_TXBD_R) {
|
|
TSEC_BACK_DIRTY_TX_DESC(sc);
|
|
break;
|
|
}
|
|
|
|
if ((tx_desc->flags & TSEC_TXBD_L) == 0)
|
|
continue;
|
|
|
|
/*
|
|
* This is the last buf in this packet, so unmap and free it.
|
|
*/
|
|
m0 = TSEC_GET_TX_MBUF(sc);
|
|
mapp = TSEC_GET_TX_MAP(sc);
|
|
|
|
bus_dmamap_sync(sc->tsec_tx_mtag, *mapp,
|
|
BUS_DMASYNC_POSTWRITE);
|
|
bus_dmamap_unload(sc->tsec_tx_mtag, *mapp);
|
|
|
|
TSEC_FREE_TX_MAP(sc, mapp);
|
|
m_freem(m0);
|
|
|
|
ifp->if_opackets++;
|
|
send = 1;
|
|
}
|
|
bus_dmamap_sync(sc->tsec_tx_dtag, sc->tsec_tx_dmap,
|
|
BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
|
|
|
|
if (send) {
|
|
/* Now send anything that was pending */
|
|
ifp->if_drv_flags &= ~IFF_DRV_OACTIVE;
|
|
tsec_start_locked(ifp);
|
|
|
|
/* Stop wathdog if all sent */
|
|
if (TSEC_EMPTYQ_TX_MBUF(sc))
|
|
sc->tsec_watchdog = 0;
|
|
}
|
|
}
|
|
|
|
void
|
|
tsec_transmit_intr(void *arg)
|
|
{
|
|
struct tsec_softc *sc = arg;
|
|
|
|
TSEC_TRANSMIT_LOCK(sc);
|
|
|
|
#ifdef DEVICE_POLLING
|
|
if (sc->tsec_ifp->if_capenable & IFCAP_POLLING) {
|
|
TSEC_TRANSMIT_UNLOCK(sc);
|
|
return;
|
|
}
|
|
#endif
|
|
/* Confirm the interrupt was received by driver */
|
|
TSEC_WRITE(sc, TSEC_REG_IEVENT, TSEC_IEVENT_TXB | TSEC_IEVENT_TXF);
|
|
tsec_transmit_intr_locked(sc);
|
|
|
|
TSEC_TRANSMIT_UNLOCK(sc);
|
|
}
|
|
|
|
static void
|
|
tsec_error_intr_locked(struct tsec_softc *sc, int count)
|
|
{
|
|
struct ifnet *ifp;
|
|
uint32_t eflags;
|
|
|
|
TSEC_GLOBAL_LOCK_ASSERT(sc);
|
|
|
|
ifp = sc->tsec_ifp;
|
|
|
|
eflags = TSEC_READ(sc, TSEC_REG_IEVENT);
|
|
|
|
/* Clear events bits in hardware */
|
|
TSEC_WRITE(sc, TSEC_REG_IEVENT, TSEC_IEVENT_RXC | TSEC_IEVENT_BSY |
|
|
TSEC_IEVENT_EBERR | TSEC_IEVENT_MSRO | TSEC_IEVENT_BABT |
|
|
TSEC_IEVENT_TXC | TSEC_IEVENT_TXE | TSEC_IEVENT_LC |
|
|
TSEC_IEVENT_CRL | TSEC_IEVENT_XFUN);
|
|
|
|
/* Check transmitter errors */
|
|
if (eflags & TSEC_IEVENT_TXE) {
|
|
ifp->if_oerrors++;
|
|
|
|
if (eflags & TSEC_IEVENT_LC)
|
|
ifp->if_collisions++;
|
|
|
|
TSEC_WRITE(sc, TSEC_REG_TSTAT, TSEC_TSTAT_THLT);
|
|
}
|
|
|
|
/* Check receiver errors */
|
|
if (eflags & TSEC_IEVENT_BSY) {
|
|
ifp->if_ierrors++;
|
|
ifp->if_iqdrops++;
|
|
|
|
/* Get data from RX buffers */
|
|
tsec_receive_intr_locked(sc, count);
|
|
}
|
|
|
|
if (ifp->if_flags & IFF_DEBUG)
|
|
if_printf(ifp, "tsec_error_intr(): event flags: 0x%x\n",
|
|
eflags);
|
|
|
|
if (eflags & TSEC_IEVENT_EBERR) {
|
|
if_printf(ifp, "System bus error occurred during"
|
|
"DMA transaction (flags: 0x%x)\n", eflags);
|
|
tsec_init_locked(sc);
|
|
}
|
|
|
|
if (eflags & TSEC_IEVENT_BABT)
|
|
ifp->if_oerrors++;
|
|
|
|
if (eflags & TSEC_IEVENT_BABR)
|
|
ifp->if_ierrors++;
|
|
}
|
|
|
|
void
|
|
tsec_error_intr(void *arg)
|
|
{
|
|
struct tsec_softc *sc = arg;
|
|
|
|
TSEC_GLOBAL_LOCK(sc);
|
|
tsec_error_intr_locked(sc, -1);
|
|
TSEC_GLOBAL_UNLOCK(sc);
|
|
}
|
|
|
|
int
|
|
tsec_miibus_readreg(device_t dev, int phy, int reg)
|
|
{
|
|
struct tsec_softc *sc;
|
|
uint32_t timeout;
|
|
|
|
sc = device_get_softc(dev);
|
|
|
|
if (device_get_unit(dev) != phy)
|
|
return (0);
|
|
|
|
sc = tsec0_sc;
|
|
|
|
TSEC_WRITE(sc, TSEC_REG_MIIMADD, (phy << 8) | reg);
|
|
TSEC_WRITE(sc, TSEC_REG_MIIMCOM, 0);
|
|
TSEC_WRITE(sc, TSEC_REG_MIIMCOM, TSEC_MIIMCOM_READCYCLE);
|
|
|
|
timeout = TSEC_READ_RETRY;
|
|
while (--timeout && TSEC_READ(sc, TSEC_REG_MIIMIND) &
|
|
(TSEC_MIIMIND_NOTVALID | TSEC_MIIMIND_BUSY))
|
|
DELAY(TSEC_READ_DELAY);
|
|
|
|
if (timeout == 0)
|
|
device_printf(dev, "Timeout while reading from PHY!\n");
|
|
|
|
return (TSEC_READ(sc, TSEC_REG_MIIMSTAT));
|
|
}
|
|
|
|
int
|
|
tsec_miibus_writereg(device_t dev, int phy, int reg, int value)
|
|
{
|
|
struct tsec_softc *sc;
|
|
uint32_t timeout;
|
|
|
|
sc = device_get_softc(dev);
|
|
|
|
if (device_get_unit(dev) != phy)
|
|
device_printf(dev, "Trying to write to an alien PHY(%d)\n",
|
|
phy);
|
|
|
|
sc = tsec0_sc;
|
|
|
|
TSEC_WRITE(sc, TSEC_REG_MIIMADD, (phy << 8) | reg);
|
|
TSEC_WRITE(sc, TSEC_REG_MIIMCON, value);
|
|
|
|
timeout = TSEC_READ_RETRY;
|
|
while (--timeout && (TSEC_READ(sc, TSEC_REG_MIIMIND) &
|
|
TSEC_MIIMIND_BUSY))
|
|
DELAY(TSEC_READ_DELAY);
|
|
|
|
if (timeout == 0)
|
|
device_printf(dev, "Timeout while writing to PHY!\n");
|
|
|
|
return (0);
|
|
}
|
|
|
|
void
|
|
tsec_miibus_statchg(device_t dev)
|
|
{
|
|
struct tsec_softc *sc;
|
|
struct mii_data *mii;
|
|
uint32_t ecntrl, id, tmp;
|
|
int link;
|
|
|
|
sc = device_get_softc(dev);
|
|
mii = sc->tsec_mii;
|
|
link = ((mii->mii_media_status & IFM_ACTIVE) ? 1 : 0);
|
|
|
|
tmp = TSEC_READ(sc, TSEC_REG_MACCFG2) & ~TSEC_MACCFG2_IF;
|
|
|
|
if ((mii->mii_media_active & IFM_GMASK) == IFM_FDX)
|
|
tmp |= TSEC_MACCFG2_FULLDUPLEX;
|
|
else
|
|
tmp &= ~TSEC_MACCFG2_FULLDUPLEX;
|
|
|
|
switch (IFM_SUBTYPE(mii->mii_media_active)) {
|
|
case IFM_1000_T:
|
|
case IFM_1000_SX:
|
|
tmp |= TSEC_MACCFG2_GMII;
|
|
sc->tsec_link = link;
|
|
break;
|
|
case IFM_100_TX:
|
|
case IFM_10_T:
|
|
tmp |= TSEC_MACCFG2_MII;
|
|
sc->tsec_link = link;
|
|
break;
|
|
case IFM_NONE:
|
|
if (link)
|
|
device_printf(dev, "No speed selected but link "
|
|
"active!\n");
|
|
sc->tsec_link = 0;
|
|
return;
|
|
default:
|
|
sc->tsec_link = 0;
|
|
device_printf(dev, "Unknown speed (%d), link %s!\n",
|
|
IFM_SUBTYPE(mii->mii_media_active),
|
|
((link) ? "up" : "down"));
|
|
return;
|
|
}
|
|
TSEC_WRITE(sc, TSEC_REG_MACCFG2, tmp);
|
|
|
|
/* XXX kludge - use circumstantial evidence for reduced mode. */
|
|
id = TSEC_READ(sc, TSEC_REG_ID2);
|
|
if (id & 0xffff) {
|
|
ecntrl = TSEC_READ(sc, TSEC_REG_ECNTRL) & ~TSEC_ECNTRL_R100M;
|
|
ecntrl |= (tmp & TSEC_MACCFG2_MII) ? TSEC_ECNTRL_R100M : 0;
|
|
TSEC_WRITE(sc, TSEC_REG_ECNTRL, ecntrl);
|
|
}
|
|
}
|
|
|
|
static void
|
|
tsec_add_sysctls(struct tsec_softc *sc)
|
|
{
|
|
struct sysctl_ctx_list *ctx;
|
|
struct sysctl_oid_list *children;
|
|
struct sysctl_oid *tree;
|
|
|
|
ctx = device_get_sysctl_ctx(sc->dev);
|
|
children = SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev));
|
|
tree = SYSCTL_ADD_NODE(ctx, children, OID_AUTO, "int_coal",
|
|
CTLFLAG_RD, 0, "TSEC Interrupts coalescing");
|
|
children = SYSCTL_CHILDREN(tree);
|
|
|
|
SYSCTL_ADD_PROC(ctx, children, OID_AUTO, "rx_time",
|
|
CTLTYPE_UINT | CTLFLAG_RW, sc, TSEC_IC_RX, tsec_sysctl_ic_time,
|
|
"I", "IC RX time threshold (0-65535)");
|
|
SYSCTL_ADD_PROC(ctx, children, OID_AUTO, "rx_count",
|
|
CTLTYPE_UINT | CTLFLAG_RW, sc, TSEC_IC_RX, tsec_sysctl_ic_count,
|
|
"I", "IC RX frame count threshold (0-255)");
|
|
|
|
SYSCTL_ADD_PROC(ctx, children, OID_AUTO, "tx_time",
|
|
CTLTYPE_UINT | CTLFLAG_RW, sc, TSEC_IC_TX, tsec_sysctl_ic_time,
|
|
"I", "IC TX time threshold (0-65535)");
|
|
SYSCTL_ADD_PROC(ctx, children, OID_AUTO, "tx_count",
|
|
CTLTYPE_UINT | CTLFLAG_RW, sc, TSEC_IC_TX, tsec_sysctl_ic_count,
|
|
"I", "IC TX frame count threshold (0-255)");
|
|
}
|
|
|
|
/*
|
|
* With Interrupt Coalescing (IC) active, a transmit/receive frame
|
|
* interrupt is raised either upon:
|
|
*
|
|
* - threshold-defined period of time elapsed, or
|
|
* - threshold-defined number of frames is received/transmitted,
|
|
* whichever occurs first.
|
|
*
|
|
* The following sysctls regulate IC behaviour (for TX/RX separately):
|
|
*
|
|
* dev.tsec.<unit>.int_coal.rx_time
|
|
* dev.tsec.<unit>.int_coal.rx_count
|
|
* dev.tsec.<unit>.int_coal.tx_time
|
|
* dev.tsec.<unit>.int_coal.tx_count
|
|
*
|
|
* Values:
|
|
*
|
|
* - 0 for either time or count disables IC on the given TX/RX path
|
|
*
|
|
* - count: 1-255 (expresses frame count number; note that value of 1 is
|
|
* effectively IC off)
|
|
*
|
|
* - time: 1-65535 (value corresponds to a real time period and is
|
|
* expressed in units equivalent to 64 TSEC interface clocks, i.e. one timer
|
|
* threshold unit is 26.5 us, 2.56 us, or 512 ns, corresponding to 10 Mbps,
|
|
* 100 Mbps, or 1Gbps, respectively. For detailed discussion consult the
|
|
* TSEC reference manual.
|
|
*/
|
|
static int
|
|
tsec_sysctl_ic_time(SYSCTL_HANDLER_ARGS)
|
|
{
|
|
int error;
|
|
uint32_t time;
|
|
struct tsec_softc *sc = (struct tsec_softc *)arg1;
|
|
|
|
time = (arg2 == TSEC_IC_RX) ? sc->rx_ic_time : sc->tx_ic_time;
|
|
|
|
error = sysctl_handle_int(oidp, &time, 0, req);
|
|
if (error != 0)
|
|
return (error);
|
|
|
|
if (time > 65535)
|
|
return (EINVAL);
|
|
|
|
TSEC_IC_LOCK(sc);
|
|
if (arg2 == TSEC_IC_RX) {
|
|
sc->rx_ic_time = time;
|
|
tsec_set_rxic(sc);
|
|
} else {
|
|
sc->tx_ic_time = time;
|
|
tsec_set_txic(sc);
|
|
}
|
|
TSEC_IC_UNLOCK(sc);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
tsec_sysctl_ic_count(SYSCTL_HANDLER_ARGS)
|
|
{
|
|
int error;
|
|
uint32_t count;
|
|
struct tsec_softc *sc = (struct tsec_softc *)arg1;
|
|
|
|
count = (arg2 == TSEC_IC_RX) ? sc->rx_ic_count : sc->tx_ic_count;
|
|
|
|
error = sysctl_handle_int(oidp, &count, 0, req);
|
|
if (error != 0)
|
|
return (error);
|
|
|
|
if (count > 255)
|
|
return (EINVAL);
|
|
|
|
TSEC_IC_LOCK(sc);
|
|
if (arg2 == TSEC_IC_RX) {
|
|
sc->rx_ic_count = count;
|
|
tsec_set_rxic(sc);
|
|
} else {
|
|
sc->tx_ic_count = count;
|
|
tsec_set_txic(sc);
|
|
}
|
|
TSEC_IC_UNLOCK(sc);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static void
|
|
tsec_set_rxic(struct tsec_softc *sc)
|
|
{
|
|
uint32_t rxic_val;
|
|
|
|
if (sc->rx_ic_count == 0 || sc->rx_ic_time == 0)
|
|
/* Disable RX IC */
|
|
rxic_val = 0;
|
|
else {
|
|
rxic_val = 0x80000000;
|
|
rxic_val |= (sc->rx_ic_count << 21);
|
|
rxic_val |= sc->rx_ic_time;
|
|
}
|
|
|
|
TSEC_WRITE(sc, TSEC_REG_RXIC, rxic_val);
|
|
}
|
|
|
|
static void
|
|
tsec_set_txic(struct tsec_softc *sc)
|
|
{
|
|
uint32_t txic_val;
|
|
|
|
if (sc->tx_ic_count == 0 || sc->tx_ic_time == 0)
|
|
/* Disable TX IC */
|
|
txic_val = 0;
|
|
else {
|
|
txic_val = 0x80000000;
|
|
txic_val |= (sc->tx_ic_count << 21);
|
|
txic_val |= sc->tx_ic_time;
|
|
}
|
|
|
|
TSEC_WRITE(sc, TSEC_REG_TXIC, txic_val);
|
|
}
|
|
|
|
static void
|
|
tsec_offload_setup(struct tsec_softc *sc)
|
|
{
|
|
struct ifnet *ifp = sc->tsec_ifp;
|
|
uint32_t reg;
|
|
|
|
TSEC_GLOBAL_LOCK_ASSERT(sc);
|
|
|
|
reg = TSEC_READ(sc, TSEC_REG_TCTRL);
|
|
reg |= TSEC_TCTRL_IPCSEN | TSEC_TCTRL_TUCSEN;
|
|
|
|
if (ifp->if_capenable & IFCAP_TXCSUM)
|
|
ifp->if_hwassist = TSEC_CHECKSUM_FEATURES;
|
|
else
|
|
ifp->if_hwassist = 0;
|
|
|
|
TSEC_WRITE(sc, TSEC_REG_TCTRL, reg);
|
|
|
|
reg = TSEC_READ(sc, TSEC_REG_RCTRL);
|
|
reg &= ~(TSEC_RCTRL_IPCSEN | TSEC_RCTRL_TUCSEN | TSEC_RCTRL_PRSDEP);
|
|
reg |= TSEC_RCTRL_PRSDEP_PARSE_L2 | TSEC_RCTRL_VLEX;
|
|
|
|
if (ifp->if_capenable & IFCAP_RXCSUM)
|
|
reg |= TSEC_RCTRL_IPCSEN | TSEC_RCTRL_TUCSEN |
|
|
TSEC_RCTRL_PRSDEP_PARSE_L234;
|
|
|
|
TSEC_WRITE(sc, TSEC_REG_RCTRL, reg);
|
|
}
|
|
|
|
|
|
static void
|
|
tsec_offload_process_frame(struct tsec_softc *sc, struct mbuf *m)
|
|
{
|
|
struct tsec_rx_fcb rx_fcb;
|
|
int csum_flags = 0;
|
|
int protocol, flags;
|
|
|
|
TSEC_RECEIVE_LOCK_ASSERT(sc);
|
|
|
|
m_copydata(m, 0, sizeof(struct tsec_rx_fcb), (caddr_t)(&rx_fcb));
|
|
flags = rx_fcb.flags;
|
|
protocol = rx_fcb.protocol;
|
|
|
|
if (TSEC_RX_FCB_IP_CSUM_CHECKED(flags)) {
|
|
csum_flags |= CSUM_IP_CHECKED;
|
|
|
|
if ((flags & TSEC_RX_FCB_IP_CSUM_ERROR) == 0)
|
|
csum_flags |= CSUM_IP_VALID;
|
|
}
|
|
|
|
if ((protocol == IPPROTO_TCP || protocol == IPPROTO_UDP) &&
|
|
TSEC_RX_FCB_TCP_UDP_CSUM_CHECKED(flags) &&
|
|
(flags & TSEC_RX_FCB_TCP_UDP_CSUM_ERROR) == 0) {
|
|
|
|
csum_flags |= CSUM_DATA_VALID | CSUM_PSEUDO_HDR;
|
|
m->m_pkthdr.csum_data = 0xFFFF;
|
|
}
|
|
|
|
m->m_pkthdr.csum_flags = csum_flags;
|
|
|
|
if (flags & TSEC_RX_FCB_VLAN) {
|
|
m->m_pkthdr.ether_vtag = rx_fcb.vlan;
|
|
m->m_flags |= M_VLANTAG;
|
|
}
|
|
|
|
m_adj(m, sizeof(struct tsec_rx_fcb));
|
|
}
|
|
|
|
static void
|
|
tsec_setup_multicast(struct tsec_softc *sc)
|
|
{
|
|
uint32_t hashtable[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
|
|
struct ifnet *ifp = sc->tsec_ifp;
|
|
struct ifmultiaddr *ifma;
|
|
uint32_t h;
|
|
int i;
|
|
|
|
TSEC_GLOBAL_LOCK_ASSERT(sc);
|
|
|
|
if (ifp->if_flags & IFF_ALLMULTI) {
|
|
for (i = 0; i < 8; i++)
|
|
TSEC_WRITE(sc, TSEC_REG_GADDR(i), 0xFFFFFFFF);
|
|
|
|
return;
|
|
}
|
|
|
|
if_maddr_rlock(ifp);
|
|
TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) {
|
|
|
|
if (ifma->ifma_addr->sa_family != AF_LINK)
|
|
continue;
|
|
|
|
h = (ether_crc32_be(LLADDR((struct sockaddr_dl *)
|
|
ifma->ifma_addr), ETHER_ADDR_LEN) >> 24) & 0xFF;
|
|
|
|
hashtable[(h >> 5)] |= 1 << (0x1F - (h & 0x1F));
|
|
}
|
|
if_maddr_runlock(ifp);
|
|
|
|
for (i = 0; i < 8; i++)
|
|
TSEC_WRITE(sc, TSEC_REG_GADDR(i), hashtable[i]);
|
|
}
|
|
|
|
static int
|
|
tsec_set_mtu(struct tsec_softc *sc, unsigned int mtu)
|
|
{
|
|
|
|
mtu += ETHER_HDR_LEN + ETHER_VLAN_ENCAP_LEN + ETHER_CRC_LEN;
|
|
|
|
TSEC_GLOBAL_LOCK_ASSERT(sc);
|
|
|
|
if (mtu >= TSEC_MIN_FRAME_SIZE && mtu <= TSEC_MAX_FRAME_SIZE) {
|
|
TSEC_WRITE(sc, TSEC_REG_MAXFRM, mtu);
|
|
return (mtu);
|
|
}
|
|
|
|
return (0);
|
|
}
|