diff options
author | philip <philip@FreeBSD.org> | 2011-11-16 17:11:13 +0000 |
---|---|---|
committer | philip <philip@FreeBSD.org> | 2011-11-16 17:11:13 +0000 |
commit | d8198c572a23ba57ab4a97b436b0cfc41401268c (patch) | |
tree | 9a743f33d1ace33f6ed4c5c3d2fcd59a0496a32d /sys/dev/sfxge/sfxge_mcdi.c | |
parent | 3692d0165926e89d0ad0d746888b510d4e49348c (diff) | |
download | FreeBSD-src-d8198c572a23ba57ab4a97b436b0cfc41401268c.zip FreeBSD-src-d8198c572a23ba57ab4a97b436b0cfc41401268c.tar.gz |
Add the sfxge(4) device driver, providing support for 10Gb Ethernet adapters
based on Solarflare SFC9000 family controllers. The driver supports jumbo
frames, transmit/receive checksum offload, TCP Segmentation Offload (TSO),
Large Receive Offload (LRO), VLAN checksum offload, VLAN TSO, and Receive Side
Scaling (RSS) using MSI-X interrupts.
This work was sponsored by Solarflare Communications, Inc.
My sincere thanks to Ben Hutchings for doing a lot of the hard work!
Sponsored by: Solarflare Communications, Inc.
MFC after: 3 weeks
Diffstat (limited to 'sys/dev/sfxge/sfxge_mcdi.c')
-rw-r--r-- | sys/dev/sfxge/sfxge_mcdi.c | 250 |
1 files changed, 250 insertions, 0 deletions
diff --git a/sys/dev/sfxge/sfxge_mcdi.c b/sys/dev/sfxge/sfxge_mcdi.c new file mode 100644 index 0000000..6368ab4 --- /dev/null +++ b/sys/dev/sfxge/sfxge_mcdi.c @@ -0,0 +1,250 @@ +/*- + * Copyright (c) 2010-2011 Solarflare Communications, Inc. + * All rights reserved. + * + * This software was developed in part by Philip Paeps under contract for + * Solarflare Communications, Inc. + * + * 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$"); + +#include <sys/param.h> +#include <sys/condvar.h> +#include <sys/lock.h> +#include <sys/mutex.h> +#include <sys/proc.h> +#include <sys/syslog.h> +#include <sys/taskqueue.h> + +#include "common/efx.h" +#include "common/efx_mcdi.h" +#include "common/efx_regs_mcdi.h" + +#include "sfxge.h" + +#define SFXGE_MCDI_POLL_INTERVAL_MIN 10 /* 10us in 1us units */ +#define SFXGE_MCDI_POLL_INTERVAL_MAX 100000 /* 100ms in 1us units */ +#define SFXGE_MCDI_WATCHDOG_INTERVAL 10000000 /* 10s in 1us units */ + +/* Acquire exclusive access to MCDI for the duration of a request. */ +static void +sfxge_mcdi_acquire(struct sfxge_mcdi *mcdi) +{ + + mtx_lock(&mcdi->lock); + KASSERT(mcdi->state != SFXGE_MCDI_UNINITIALIZED, + ("MCDI not initialized")); + + while (mcdi->state != SFXGE_MCDI_INITIALIZED) + (void)cv_wait_sig(&mcdi->cv, &mcdi->lock); + mcdi->state = SFXGE_MCDI_BUSY; + + mtx_unlock(&mcdi->lock); +} + +/* Release ownership of MCDI on request completion. */ +static void +sfxge_mcdi_release(struct sfxge_mcdi *mcdi) +{ + + mtx_lock(&mcdi->lock); + KASSERT((mcdi->state == SFXGE_MCDI_BUSY || + mcdi->state == SFXGE_MCDI_COMPLETED), + ("MCDI not busy or task not completed")); + + mcdi->state = SFXGE_MCDI_INITIALIZED; + cv_broadcast(&mcdi->cv); + + mtx_unlock(&mcdi->lock); +} + +static void +sfxge_mcdi_timeout(struct sfxge_softc *sc) +{ + device_t dev = sc->dev; + + log(LOG_WARNING, "[%s%d] MC_TIMEOUT", device_get_name(dev), + device_get_unit(dev)); + + EFSYS_PROBE(mcdi_timeout); + sfxge_schedule_reset(sc); +} + +static void +sfxge_mcdi_poll(struct sfxge_softc *sc) +{ + efx_nic_t *enp; + clock_t delay_total; + clock_t delay_us; + boolean_t aborted; + + delay_total = 0; + delay_us = SFXGE_MCDI_POLL_INTERVAL_MIN; + enp = sc->enp; + + do { + if (efx_mcdi_request_poll(enp)) { + EFSYS_PROBE1(mcdi_delay, clock_t, delay_total); + return; + } + + if (delay_total > SFXGE_MCDI_WATCHDOG_INTERVAL) { + aborted = efx_mcdi_request_abort(enp); + KASSERT(aborted, ("abort failed")); + sfxge_mcdi_timeout(sc); + return; + } + + /* Spin or block depending on delay interval. */ + if (delay_us < 1000000) + DELAY(delay_us); + else + pause("mcdi wait", delay_us * hz / 1000000); + + delay_total += delay_us; + + /* Exponentially back off the poll frequency. */ + delay_us = delay_us * 2; + if (delay_us > SFXGE_MCDI_POLL_INTERVAL_MAX) + delay_us = SFXGE_MCDI_POLL_INTERVAL_MAX; + + } while (1); +} + +static void +sfxge_mcdi_execute(void *arg, efx_mcdi_req_t *emrp) +{ + struct sfxge_softc *sc; + struct sfxge_mcdi *mcdi; + + sc = (struct sfxge_softc *)arg; + mcdi = &sc->mcdi; + + sfxge_mcdi_acquire(mcdi); + + /* Issue request and poll for completion. */ + efx_mcdi_request_start(sc->enp, emrp, B_FALSE); + sfxge_mcdi_poll(sc); + + sfxge_mcdi_release(mcdi); +} + +static void +sfxge_mcdi_ev_cpl(void *arg) +{ + struct sfxge_softc *sc; + struct sfxge_mcdi *mcdi; + + sc = (struct sfxge_softc *)arg; + mcdi = &sc->mcdi; + + mtx_lock(&mcdi->lock); + KASSERT(mcdi->state == SFXGE_MCDI_BUSY, ("MCDI not busy")); + mcdi->state = SFXGE_MCDI_COMPLETED; + cv_broadcast(&mcdi->cv); + mtx_unlock(&mcdi->lock); +} + +static void +sfxge_mcdi_exception(void *arg, efx_mcdi_exception_t eme) +{ + struct sfxge_softc *sc; + device_t dev; + + sc = (struct sfxge_softc *)arg; + dev = sc->dev; + + log(LOG_WARNING, "[%s%d] MC_%s", device_get_name(dev), + device_get_unit(dev), + (eme == EFX_MCDI_EXCEPTION_MC_REBOOT) + ? "REBOOT" + : (eme == EFX_MCDI_EXCEPTION_MC_BADASSERT) + ? "BADASSERT" : "UNKNOWN"); + + EFSYS_PROBE(mcdi_exception); + + sfxge_schedule_reset(sc); +} + +int +sfxge_mcdi_init(struct sfxge_softc *sc) +{ + efx_nic_t *enp; + struct sfxge_mcdi *mcdi; + efx_mcdi_transport_t *emtp; + int rc; + + enp = sc->enp; + mcdi = &sc->mcdi; + emtp = &mcdi->transport; + + KASSERT(mcdi->state == SFXGE_MCDI_UNINITIALIZED, + ("MCDI already initialized")); + + mtx_init(&mcdi->lock, "sfxge_mcdi", NULL, MTX_DEF); + + mcdi->state = SFXGE_MCDI_INITIALIZED; + + emtp->emt_context = sc; + emtp->emt_execute = sfxge_mcdi_execute; + emtp->emt_ev_cpl = sfxge_mcdi_ev_cpl; + emtp->emt_exception = sfxge_mcdi_exception; + + cv_init(&mcdi->cv, "sfxge_mcdi"); + + if ((rc = efx_mcdi_init(enp, emtp)) != 0) + goto fail; + + return (0); + +fail: + mtx_destroy(&mcdi->lock); + mcdi->state = SFXGE_MCDI_UNINITIALIZED; + return (rc); +} + +void +sfxge_mcdi_fini(struct sfxge_softc *sc) +{ + struct sfxge_mcdi *mcdi; + efx_nic_t *enp; + efx_mcdi_transport_t *emtp; + + enp = sc->enp; + mcdi = &sc->mcdi; + emtp = &mcdi->transport; + + mtx_lock(&mcdi->lock); + KASSERT(mcdi->state == SFXGE_MCDI_INITIALIZED, + ("MCDI not initialized")); + + efx_mcdi_fini(enp); + bzero(emtp, sizeof(*emtp)); + + cv_destroy(&mcdi->cv); + mtx_unlock(&mcdi->lock); + + mtx_destroy(&mcdi->lock); +} |