diff options
Diffstat (limited to 'sys/dev/sfxge/sfxge_intr.c')
-rw-r--r-- | sys/dev/sfxge/sfxge_intr.c | 577 |
1 files changed, 577 insertions, 0 deletions
diff --git a/sys/dev/sfxge/sfxge_intr.c b/sys/dev/sfxge/sfxge_intr.c new file mode 100644 index 0000000..5cf1362 --- /dev/null +++ b/sys/dev/sfxge/sfxge_intr.c @@ -0,0 +1,577 @@ +/*- + * 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/bus.h> +#include <sys/rman.h> +#include <sys/smp.h> +#include <sys/syslog.h> + +#include <machine/bus.h> +#include <machine/resource.h> + +#include <dev/pci/pcireg.h> +#include <dev/pci/pcivar.h> + +#include "common/efx.h" + +#include "sfxge.h" + +static int +sfxge_intr_line_filter(void *arg) +{ + struct sfxge_evq *evq; + struct sfxge_softc *sc; + efx_nic_t *enp; + struct sfxge_intr *intr; + boolean_t fatal; + uint32_t qmask; + + evq = (struct sfxge_evq *)arg; + sc = evq->sc; + enp = sc->enp; + intr = &sc->intr; + + KASSERT(intr != NULL, ("intr == NULL")); + KASSERT(intr->type == EFX_INTR_LINE, + ("intr->type != EFX_INTR_LINE")); + + if (intr->state != SFXGE_INTR_STARTED && + intr->state != SFXGE_INTR_TESTING) + return FILTER_STRAY; + + if (intr->state == SFXGE_INTR_TESTING) { + intr->mask |= 1; /* only one interrupt */ + return FILTER_HANDLED; + } + + (void)efx_intr_status_line(enp, &fatal, &qmask); + + if (fatal) { + (void) efx_intr_disable(enp); + (void) efx_intr_fatal(enp); + return FILTER_HANDLED; + } + + if (qmask != 0) { + intr->zero_count = 0; + return FILTER_SCHEDULE_THREAD; + } + + /* SF bug 15783: If the function is not asserting its IRQ and + * we read the queue mask on the cycle before a flag is added + * to the mask, this inhibits the function from asserting the + * IRQ even though we don't see the flag set. To work around + * this, we must re-prime all event queues and report the IRQ + * as handled when we see a mask of zero. To allow for shared + * IRQs, we don't repeat this if we see a mask of zero twice + * or more in a row. + */ + if (intr->zero_count++ == 0) { + if (evq->init_state == SFXGE_EVQ_STARTED) { + if (efx_ev_qpending(evq->common, evq->read_ptr)) + return FILTER_SCHEDULE_THREAD; + efx_ev_qprime(evq->common, evq->read_ptr); + return FILTER_HANDLED; + } + } + + return FILTER_STRAY; +} + +static void +sfxge_intr_line(void *arg) +{ + struct sfxge_evq *evq = arg; + struct sfxge_softc *sc = evq->sc; + + (void)sfxge_ev_qpoll(sc, 0); +} + +static void +sfxge_intr_message(void *arg) +{ + struct sfxge_evq *evq; + struct sfxge_softc *sc; + efx_nic_t *enp; + struct sfxge_intr *intr; + unsigned int index; + boolean_t fatal; + + evq = (struct sfxge_evq *)arg; + sc = evq->sc; + enp = sc->enp; + intr = &sc->intr; + index = evq->index; + + KASSERT(intr != NULL, ("intr == NULL")); + KASSERT(intr->type == EFX_INTR_MESSAGE, + ("intr->type != EFX_INTR_MESSAGE")); + + if (intr->state != SFXGE_INTR_STARTED && + intr->state != SFXGE_INTR_TESTING) + return; + + if (intr->state == SFXGE_INTR_TESTING) { + uint64_t mask; + + do { + mask = intr->mask; + } while (atomic_cmpset_long(&intr->mask, mask, + mask | (1 << index)) == 0); + + return; + } + + (void)efx_intr_status_message(enp, index, &fatal); + + if (fatal) { + (void)efx_intr_disable(enp); + (void)efx_intr_fatal(enp); + return; + } + + (void)sfxge_ev_qpoll(sc, index); +} + +static int +sfxge_intr_bus_enable(struct sfxge_softc *sc) +{ + struct sfxge_intr *intr; + struct sfxge_intr_hdl *table; + driver_filter_t *filter; + driver_intr_t *handler; + int index; + int err; + + intr = &sc->intr; + table = intr->table; + + switch (intr->type) { + case EFX_INTR_MESSAGE: + filter = NULL; /* not shared */ + handler = sfxge_intr_message; + break; + + case EFX_INTR_LINE: + filter = sfxge_intr_line_filter; + handler = sfxge_intr_line; + break; + + default: + KASSERT(0, ("Invalid interrupt type")); + return EINVAL; + } + + /* Try to add the handlers */ + for (index = 0; index < intr->n_alloc; index++) { + if ((err = bus_setup_intr(sc->dev, table[index].eih_res, + INTR_MPSAFE|INTR_TYPE_NET, filter, handler, + sc->evq[index], &table[index].eih_tag)) != 0) { + goto fail; + } +#ifdef SFXGE_HAVE_DESCRIBE_INTR + if (intr->n_alloc > 1) + bus_describe_intr(sc->dev, table[index].eih_res, + table[index].eih_tag, "%d", index); +#endif + bus_bind_intr(sc->dev, table[index].eih_res, index); + + } + + return (0); + +fail: + /* Remove remaining handlers */ + while (--index >= 0) + bus_teardown_intr(sc->dev, table[index].eih_res, + table[index].eih_tag); + + return (err); +} + +static void +sfxge_intr_bus_disable(struct sfxge_softc *sc) +{ + struct sfxge_intr *intr; + struct sfxge_intr_hdl *table; + int i; + + intr = &sc->intr; + table = intr->table; + + /* Remove all handlers */ + for (i = 0; i < intr->n_alloc; i++) + bus_teardown_intr(sc->dev, table[i].eih_res, + table[i].eih_tag); +} + +static int +sfxge_intr_alloc(struct sfxge_softc *sc, int count) +{ + device_t dev; + struct sfxge_intr_hdl *table; + struct sfxge_intr *intr; + struct resource *res; + int rid; + int error; + int i; + + dev = sc->dev; + intr = &sc->intr; + error = 0; + + table = malloc(count * sizeof(struct sfxge_intr_hdl), + M_SFXGE, M_WAITOK); + intr->table = table; + + for (i = 0; i < count; i++) { + rid = i + 1; + res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, + RF_SHAREABLE | RF_ACTIVE); + if (res == NULL) { + device_printf(dev, "Couldn't allocate interrupts for " + "message %d\n", rid); + error = ENOMEM; + break; + } + table[i].eih_rid = rid; + table[i].eih_res = res; + } + + if (error) { + count = i - 1; + for (i = 0; i < count; i++) + bus_release_resource(dev, SYS_RES_IRQ, + table[i].eih_rid, table[i].eih_res); + } + + return (error); +} + +static void +sfxge_intr_teardown_msix(struct sfxge_softc *sc) +{ + device_t dev; + struct resource *resp; + int rid; + + dev = sc->dev; + resp = sc->intr.msix_res; + + rid = rman_get_rid(resp); + bus_release_resource(dev, SYS_RES_MEMORY, rid, resp); +} + +static int +sfxge_intr_setup_msix(struct sfxge_softc *sc) +{ + struct sfxge_intr *intr; + struct resource *resp; + device_t dev; + int count; + int rid; + + dev = sc->dev; + intr = &sc->intr; + + /* Check if MSI-X is available. */ + count = pci_msix_count(dev); + if (count == 0) + return (EINVAL); + + /* Limit the number of interrupts to the number of CPUs. */ + if (count > mp_ncpus) + count = mp_ncpus; + + /* Not very likely these days... */ + if (count > EFX_MAXRSS) + count = EFX_MAXRSS; + + rid = PCIR_BAR(4); + resp = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); + if (resp == NULL) + return (ENOMEM); + + if (pci_alloc_msix(dev, &count) != 0) { + bus_release_resource(dev, SYS_RES_MEMORY, rid, resp); + return (ENOMEM); + } + + /* Allocate interrupt handlers. */ + if (sfxge_intr_alloc(sc, count) != 0) { + bus_release_resource(dev, SYS_RES_MEMORY, rid, resp); + pci_release_msi(dev); + return (ENOMEM); + } + + intr->type = EFX_INTR_MESSAGE; + intr->n_alloc = count; + intr->msix_res = resp; + + return (0); +} + +static int +sfxge_intr_setup_msi(struct sfxge_softc *sc) +{ + struct sfxge_intr_hdl *table; + struct sfxge_intr *intr; + device_t dev; + int count; + int error; + + dev = sc->dev; + intr = &sc->intr; + table = intr->table; + + /* + * Check if MSI is available. All messages must be written to + * the same address and on x86 this means the IRQs have the + * same CPU affinity. So we only ever allocate 1. + */ + count = pci_msi_count(dev) ? 1 : 0; + if (count == 0) + return (EINVAL); + + if ((error = pci_alloc_msi(dev, &count)) != 0) + return (ENOMEM); + + /* Allocate interrupt handler. */ + if (sfxge_intr_alloc(sc, count) != 0) { + pci_release_msi(dev); + return (ENOMEM); + } + + intr->type = EFX_INTR_MESSAGE; + intr->n_alloc = count; + + return (0); +} + +static int +sfxge_intr_setup_fixed(struct sfxge_softc *sc) +{ + struct sfxge_intr_hdl *table; + struct sfxge_intr *intr; + struct resource *res; + device_t dev; + int rid; + + dev = sc->dev; + intr = &sc->intr; + + rid = 0; + res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, + RF_SHAREABLE | RF_ACTIVE); + if (res == NULL) + return (ENOMEM); + + table = malloc(sizeof(struct sfxge_intr_hdl), M_SFXGE, M_WAITOK); + table[0].eih_rid = rid; + table[0].eih_res = res; + + intr->type = EFX_INTR_LINE; + intr->n_alloc = 1; + intr->table = table; + + return (0); +} + +static const char *const __sfxge_err[] = { + "", + "SRAM out-of-bounds", + "Buffer ID out-of-bounds", + "Internal memory parity", + "Receive buffer ownership", + "Transmit buffer ownership", + "Receive descriptor ownership", + "Transmit descriptor ownership", + "Event queue ownership", + "Event queue FIFO overflow", + "Illegal address", + "SRAM parity" +}; + +void +sfxge_err(efsys_identifier_t *arg, unsigned int code, uint32_t dword0, + uint32_t dword1) +{ + struct sfxge_softc *sc = (struct sfxge_softc *)arg; + device_t dev = sc->dev; + + log(LOG_WARNING, "[%s%d] FATAL ERROR: %s (0x%08x%08x)", + device_get_name(dev), device_get_unit(dev), + __sfxge_err[code], dword1, dword0); +} + +void +sfxge_intr_stop(struct sfxge_softc *sc) +{ + struct sfxge_intr *intr; + + intr = &sc->intr; + + KASSERT(intr->state == SFXGE_INTR_STARTED, + ("Interrupts not started")); + + intr->state = SFXGE_INTR_INITIALIZED; + + /* Disable interrupts at the NIC */ + intr->mask = 0; + efx_intr_disable(sc->enp); + + /* Disable interrupts at the bus */ + sfxge_intr_bus_disable(sc); + + /* Tear down common code interrupt bits. */ + efx_intr_fini(sc->enp); +} + +int +sfxge_intr_start(struct sfxge_softc *sc) +{ + struct sfxge_intr *intr; + efsys_mem_t *esmp; + int rc; + + intr = &sc->intr; + esmp = &intr->status; + + KASSERT(intr->state == SFXGE_INTR_INITIALIZED, + ("Interrupts not initialized")); + + /* Zero the memory. */ + (void)memset(esmp->esm_base, 0, EFX_INTR_SIZE); + + /* Initialize common code interrupt bits. */ + (void)efx_intr_init(sc->enp, intr->type, esmp); + + /* Enable interrupts at the bus */ + if ((rc = sfxge_intr_bus_enable(sc)) != 0) + goto fail; + + intr->state = SFXGE_INTR_TESTING; + + /* Enable interrupts at the NIC */ + efx_intr_enable(sc->enp); + + intr->state = SFXGE_INTR_STARTED; + + return (0); + +fail: + /* Tear down common code interrupt bits. */ + efx_intr_fini(sc->enp); + + intr->state = SFXGE_INTR_INITIALIZED; + + return (rc); +} + +void +sfxge_intr_fini(struct sfxge_softc *sc) +{ + struct sfxge_intr_hdl *table; + struct sfxge_intr *intr; + efsys_mem_t *esmp; + device_t dev; + int i; + + dev = sc->dev; + intr = &sc->intr; + esmp = &intr->status; + table = intr->table; + + KASSERT(intr->state == SFXGE_INTR_INITIALIZED, + ("intr->state != SFXGE_INTR_INITIALIZED")); + + /* Free DMA memory. */ + sfxge_dma_free(esmp); + + /* Free interrupt handles. */ + for (i = 0; i < intr->n_alloc; i++) + bus_release_resource(dev, SYS_RES_IRQ, + table[i].eih_rid, table[i].eih_res); + + if (table[0].eih_rid != 0) + pci_release_msi(dev); + + if (intr->msix_res != NULL) + sfxge_intr_teardown_msix(sc); + + /* Free the handle table */ + free(table, M_SFXGE); + intr->table = NULL; + intr->n_alloc = 0; + + /* Clear the interrupt type */ + intr->type = EFX_INTR_INVALID; + + intr->state = SFXGE_INTR_UNINITIALIZED; +} + +int +sfxge_intr_init(struct sfxge_softc *sc) +{ + device_t dev; + struct sfxge_intr *intr; + efsys_mem_t *esmp; + int rc; + + dev = sc->dev; + intr = &sc->intr; + esmp = &intr->status; + + KASSERT(intr->state == SFXGE_INTR_UNINITIALIZED, + ("Interrupts already initialized")); + + /* Try to setup MSI-X or MSI interrupts if available. */ + if ((rc = sfxge_intr_setup_msix(sc)) == 0) + device_printf(dev, "Using MSI-X interrupts\n"); + else if ((rc = sfxge_intr_setup_msi(sc)) == 0) + device_printf(dev, "Using MSI interrupts\n"); + else if ((rc = sfxge_intr_setup_fixed(sc)) == 0) { + device_printf(dev, "Using fixed interrupts\n"); + } else { + device_printf(dev, "Couldn't setup interrupts\n"); + return (ENOMEM); + } + + /* Set up DMA for interrupts. */ + if ((rc = sfxge_dma_alloc(sc, EFX_INTR_SIZE, esmp)) != 0) + return (ENOMEM); + + intr->state = SFXGE_INTR_INITIALIZED; + + return (0); +} |