/* * Copyright (c) 2008-2016 Solarflare Communications Inc. * 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 COPYRIGHT HOLDERS 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 COPYRIGHT OWNER 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. * * The views and conclusions contained in the software and documentation are * those of the authors and should not be interpreted as representing official * policies, either expressed or implied, of the FreeBSD Project. */ #include #include #include #include #include #include #include #include #include #include "sfxge.h" #include "efx.h" /* Interrupt table DMA attributes */ static ddi_device_acc_attr_t sfxge_intr_devacc = { DDI_DEVICE_ATTR_V0, /* devacc_attr_version */ DDI_NEVERSWAP_ACC, /* devacc_attr_endian_flags */ DDI_STRICTORDER_ACC /* devacc_attr_dataorder */ }; static ddi_dma_attr_t sfxge_intr_dma_attr = { DMA_ATTR_V0, /* dma_attr_version */ 0, /* dma_attr_addr_lo */ 0xffffffffffffffffull, /* dma_attr_addr_hi */ 0xffffffffffffffffull, /* dma_attr_count_max */ EFX_INTR_SIZE, /* dma_attr_align */ 0xffffffff, /* dma_attr_burstsizes */ 1, /* dma_attr_minxfer */ 0xffffffffffffffffull, /* dma_attr_maxxfer */ 0xffffffffffffffffull, /* dma_attr_seg */ 1, /* dma_attr_sgllen */ 1, /* dma_attr_granular */ 0 /* dma_attr_flags */ }; static unsigned int sfxge_intr_line(caddr_t arg1, caddr_t arg2) { sfxge_t *sp = (void *)arg1; efx_nic_t *enp = sp->s_enp; sfxge_intr_t *sip = &(sp->s_intr); unsigned int index; boolean_t fatal; uint32_t qmask; int rc; _NOTE(ARGUNUSED(arg2)) ASSERT3U(sip->si_type, ==, EFX_INTR_LINE); if (sip->si_state != SFXGE_INTR_STARTED && sip->si_state != SFXGE_INTR_TESTING) { rc = DDI_INTR_UNCLAIMED; goto done; } if (sip->si_state == SFXGE_INTR_TESTING) { sip->si_mask |= 1; /* only one interrupt */ rc = DDI_INTR_CLAIMED; goto done; } efx_intr_status_line(enp, &fatal, &qmask); if (fatal) { sfxge_intr_fatal(sp); rc = DDI_INTR_CLAIMED; goto done; } if (qmask != 0) { for (index = 0; index < EFX_INTR_NEVQS; index++) { if (qmask & (1 << index)) (void) sfxge_ev_qpoll(sp, index); } sip->si_zero_count = 0; sfxge_gld_rx_push(sp); rc = DDI_INTR_CLAIMED; goto done; } /* * bug15671/bug17203 workaround. Return CLAIMED for the first ISR=0 * interrupt, and poll all evqs for work. For subsequent ISR=0 * interrupts (the line must be shared in this case), just rearm the * event queues to ensure we don't miss an interrupt. */ if (sip->si_zero_count++ == 0) { for (index = 0; index < EFX_INTR_NEVQS; index++) { if (sp->s_sep[index] != NULL) (void) sfxge_ev_qpoll(sp, index); } rc = DDI_INTR_CLAIMED; } else { for (index = 0; index < EFX_INTR_NEVQS; index++) { if (sp->s_sep[index] != NULL) (void) sfxge_ev_qprime(sp, index); } rc = DDI_INTR_UNCLAIMED; } done: return (rc); } static unsigned int sfxge_intr_message(caddr_t arg1, caddr_t arg2) { sfxge_t *sp = (void *)arg1; efx_nic_t *enp = sp->s_enp; sfxge_intr_t *sip = &(sp->s_intr); unsigned int index = (unsigned int)(uintptr_t)arg2; boolean_t fatal; int rc; ASSERT3U(sip->si_type, ==, EFX_INTR_MESSAGE); if (sip->si_state != SFXGE_INTR_STARTED && sip->si_state != SFXGE_INTR_TESTING) { rc = DDI_INTR_UNCLAIMED; goto done; } if (sip->si_state == SFXGE_INTR_TESTING) { uint64_t mask; do { mask = sip->si_mask; } while (atomic_cas_64(&(sip->si_mask), mask, mask | (1 << index)) != mask); rc = DDI_INTR_CLAIMED; goto done; } efx_intr_status_message(enp, index, &fatal); if (fatal) { sfxge_intr_fatal(sp); rc = DDI_INTR_CLAIMED; goto done; } (void) sfxge_ev_qpoll(sp, index); sfxge_gld_rx_push(sp); rc = DDI_INTR_CLAIMED; done: return (rc); } static int sfxge_intr_bus_enable(sfxge_t *sp) { sfxge_intr_t *sip = &(sp->s_intr); ddi_intr_handler_t *handler; int add_index; int en_index; int err; int rc; /* Serialise all instances to avoid problems seen in bug31184. */ mutex_enter(&sfxge_global_lock); switch (sip->si_type) { case EFX_INTR_MESSAGE: handler = sfxge_intr_message; break; case EFX_INTR_LINE: handler = sfxge_intr_line; break; default: dev_err(sp->s_dip, CE_WARN, SFXGE_CMN_ERR "bus_enable: unknown intr type (si_type=%d nalloc=%d)", sip->si_type, sip->si_nalloc); ASSERT(B_FALSE); rc = EINVAL; goto fail1; } /* Try to add the handlers */ for (add_index = 0; add_index < sip->si_nalloc; add_index++) { unsigned int pri; /* This cannot fail unless given invalid inputs. */ err = ddi_intr_get_pri(sip->si_table[add_index], &pri); ASSERT(err == DDI_SUCCESS); DTRACE_PROBE2(pri, unsigned int, add_index, unsigned int, pri); err = ddi_intr_add_handler(sip->si_table[add_index], handler, (caddr_t)sp, (caddr_t)(uintptr_t)add_index); if (err != DDI_SUCCESS) { dev_err(sp->s_dip, CE_WARN, SFXGE_CMN_ERR "bus_enable: ddi_intr_add_handler failed" " err=%d (h=%p idx=%d nalloc=%d)", err, (void *)sip->si_table[add_index], add_index, sip->si_nalloc); rc = (err == DDI_EINVAL) ? EINVAL : EFAULT; goto fail2; } } /* Get interrupt capabilities */ err = ddi_intr_get_cap(sip->si_table[0], &(sip->si_cap)); if (err != DDI_SUCCESS) { dev_err(sp->s_dip, CE_WARN, SFXGE_CMN_ERR "bus_enable: ddi_intr_get_cap failed" " err=%d (h=%p idx=%d nalloc=%d)", err, (void *)sip->si_table[0], 0, sip->si_nalloc); if (err == DDI_EINVAL) rc = EINVAL; else if (err == DDI_ENOTSUP) rc = ENOTSUP; else rc = EFAULT; goto fail3; } /* Enable interrupts at the bus */ if (sip->si_cap & DDI_INTR_FLAG_BLOCK) { en_index = 0; /* Silence gcc */ err = ddi_intr_block_enable(sip->si_table, sip->si_nalloc); if (err != DDI_SUCCESS) { dev_err(sp->s_dip, CE_WARN, SFXGE_CMN_ERR "bus_enable: ddi_intr_block_enable failed" " err=%d (table=%p nalloc=%d)", err, (void *)sip->si_table, sip->si_nalloc); rc = (err == DDI_EINVAL) ? EINVAL : EFAULT; goto fail4; } } else { for (en_index = 0; en_index < sip->si_nalloc; en_index++) { err = ddi_intr_enable(sip->si_table[en_index]); if (err != DDI_SUCCESS) { dev_err(sp->s_dip, CE_WARN, SFXGE_CMN_ERR "bus_enable: ddi_intr_enable failed" " err=%d (h=%p idx=%d nalloc=%d)", err, (void *)sip->si_table[en_index], en_index, sip->si_nalloc); rc = (err == DDI_EINVAL) ? EINVAL : EFAULT; goto fail4; } } } mutex_exit(&sfxge_global_lock); return (0); fail4: DTRACE_PROBE(fail4); /* Disable the enabled handlers */ if (!(sip->si_cap & DDI_INTR_FLAG_BLOCK)) { while (--en_index >= 0) { err = ddi_intr_disable(sip->si_table[en_index]); if (err != DDI_SUCCESS) { dev_err(sp->s_dip, CE_WARN, SFXGE_CMN_ERR "bus_enable: ddi_intr_disable" " failed err=%d (h=%p idx=%d nalloc=%d)", err, (void *)sip->si_table[en_index], en_index, sip->si_nalloc); } } } fail3: DTRACE_PROBE(fail3); /* Remove all handlers */ add_index = sip->si_nalloc; fail2: DTRACE_PROBE(fail2); /* Remove remaining handlers */ while (--add_index >= 0) { err = ddi_intr_remove_handler(sip->si_table[add_index]); if (err != DDI_SUCCESS) { dev_err(sp->s_dip, CE_WARN, SFXGE_CMN_ERR "bus_enable: ddi_intr_remove_handler" " failed err=%d (h=%p idx=%d nalloc=%d)", err, (void *)sip->si_table[add_index], add_index, sip->si_nalloc); } } fail1: DTRACE_PROBE1(fail1, int, rc); mutex_exit(&sfxge_global_lock); return (rc); } static void sfxge_intr_bus_disable(sfxge_t *sp) { sfxge_intr_t *sip = &(sp->s_intr); int index; int err; /* Serialise all instances to avoid problems seen in bug31184. */ mutex_enter(&sfxge_global_lock); /* Disable interrupts at the bus */ if (sip->si_cap & DDI_INTR_FLAG_BLOCK) { err = ddi_intr_block_disable(sip->si_table, sip->si_nalloc); if (err != DDI_SUCCESS) { dev_err(sp->s_dip, CE_WARN, SFXGE_CMN_ERR "bus_disable: ddi_intr_block_disable" " failed err=%d (table=%p nalloc=%d)", err, (void *)sip->si_table, sip->si_nalloc); } } else { index = sip->si_nalloc; while (--index >= 0) { err = ddi_intr_disable(sip->si_table[index]); if (err != DDI_SUCCESS) { dev_err(sp->s_dip, CE_WARN, SFXGE_CMN_ERR "bus_disable: ddi_intr_disable" " failed err=%d (h=%p idx=%d nalloc=%d)", err, (void *)sip->si_table[index], index, sip->si_nalloc); } } } sip->si_cap = 0; /* Remove all handlers */ index = sip->si_nalloc; while (--index >= 0) { err = ddi_intr_remove_handler(sip->si_table[index]); if (err != DDI_SUCCESS) { dev_err(sp->s_dip, CE_WARN, SFXGE_CMN_ERR "bus_disable: ddi_intr_remove_handler" " failed err=%d (h=%p idx=%d nalloc=%d)", err, (void *)sip->si_table[index], index, sip->si_nalloc); } } mutex_exit(&sfxge_global_lock); } static int sfxge_intr_nic_enable(sfxge_t *sp) { sfxge_intr_t *sip = &(sp->s_intr); efsys_mem_t *esmp = &(sip->si_mem); efx_nic_t *enp = sp->s_enp; unsigned int index; uint64_t mask; unsigned int count; int rc; /* Zero the memory */ bzero(esmp->esm_base, EFX_INTR_SIZE); /* Enable interrupts at the NIC */ if ((rc = efx_intr_init(enp, sip->si_type, esmp)) != 0) goto fail1; efx_intr_enable(enp); /* FIXME FIXME FIXME */ if (sp->s_family == EFX_FAMILY_HUNTINGTON) { /* Disable interrupt test until supported on Huntington. */ return (0); } /* FIXME FIXME FIXME */ /* Test the interrupts */ mask = 0; for (index = 0; index < sip->si_nalloc; index++) { mask |= (1 << index); rc = efx_intr_trigger(enp, index); ASSERT3U(rc, ==, 0); } /* Wait for the tests to complete */ count = 0; do { DTRACE_PROBE1(wait, unsigned int, count); /* Spin for 1 ms */ drv_usecwait(1000); /* * Check to see that all the test interrupts have been * processed. */ if ((mask & sip->si_mask) == mask) goto done; } while (++count < 20); rc = ETIMEDOUT; goto fail2; done: return (0); fail2: DTRACE_PROBE(fail2); dev_err(sp->s_dip, CE_WARN, SFXGE_CMN_ERR "Interrupt test failed (mask=%"PRIx64" got=%" PRIx64"). NIC is disabled", mask, sip->si_mask); DTRACE_PROBE2(int_test_fail, uint64_t, mask, uint64_t, sip->si_mask); sip->si_mask = 0; /* Disable interrupts at the NIC */ efx_intr_disable(enp); efx_intr_fini(enp); fail1: DTRACE_PROBE1(fail1, int, rc); return (rc); } static void sfxge_intr_nic_disable(sfxge_t *sp) { sfxge_intr_t *sip = &(sp->s_intr); efx_nic_t *enp = sp->s_enp; sip->si_mask = 0; /* Disable interrupts at the NIC */ efx_intr_disable(enp); efx_intr_fini(enp); } static inline unsigned pow2_le(unsigned long n) { unsigned int order = 1; ASSERT3U(n, >, 0); while ((1ul << order) <= n) ++order; return (1ul << (order - 1)); } int sfxge_intr_init(sfxge_t *sp) { dev_info_t *dip = sp->s_dip; sfxge_intr_t *sip = &(sp->s_intr); efsys_mem_t *esmp = &(sip->si_mem); sfxge_dma_buffer_attr_t dma_attr; int err; int rc; int types; int type; int index; unsigned int nalloc; int navail; SFXGE_OBJ_CHECK(sip, sfxge_intr_t); ASSERT3U(sip->si_state, ==, SFXGE_INTR_UNINITIALIZED); #ifdef __sparc /* PSARC 2007/453 */ (void) ddi_prop_create(DDI_DEV_T_NONE, dip, DDI_PROP_CANSLEEP, "#msix-request", NULL, 0); #endif /* Get the map of supported interrupt types */ err = ddi_intr_get_supported_types(dip, &types); if (err != DDI_SUCCESS) { dev_err(dip, CE_WARN, SFXGE_CMN_ERR "intr_init: ddi_intr_get_supported_types failed err=%d", err); if (err == DDI_EINVAL) rc = EINVAL; else if (err == DDI_INTR_NOTFOUND) rc = ENOENT; else rc = EFAULT; goto fail1; } /* Choose most favourable type */ if (types & DDI_INTR_TYPE_MSIX) { DTRACE_PROBE(msix); type = DDI_INTR_TYPE_MSIX; sip->si_type = EFX_INTR_MESSAGE; } else { DTRACE_PROBE(fixed); ASSERT(types & DDI_INTR_TYPE_FIXED); type = DDI_INTR_TYPE_FIXED; sip->si_type = EFX_INTR_LINE; } /* Get the number of available interrupts */ navail = 0; err = ddi_intr_get_navail(dip, type, &navail); if (err != DDI_SUCCESS) { dev_err(dip, CE_WARN, SFXGE_CMN_ERR "intr_init: ddi_intr_get_navail failed err=%d", err); if (err == DDI_EINVAL) rc = EINVAL; else if (err == DDI_INTR_NOTFOUND) rc = ENOENT; else rc = EFAULT; goto fail2; } /* Double-check */ if (navail == 0) { rc = ENOENT; goto fail2; } /* * Allow greater number of MSI-X interrupts than CPUs. * This can be useful to prevent RX no desc drops; See task 32179. * Limit non MSI-X interrupts to a single instance. */ if (type != DDI_INTR_TYPE_MSIX) navail = 1; else navail = min(navail, sfxge_rx_scale_prop_get(sp)); DTRACE_PROBE1(navail, unsigned int, navail); /* Allocate a handle table */ sip->si_table_size = navail * sizeof (ddi_intr_handle_t); sip->si_table = kmem_zalloc(sip->si_table_size, KM_SLEEP); /* * Allocate interrupt handles. * Serialise all device instances to avoid problems seen in bug31184. */ mutex_enter(&sfxge_global_lock); err = ddi_intr_alloc(dip, sip->si_table, type, 0, navail, &(sip->si_nalloc), DDI_INTR_ALLOC_NORMAL); mutex_exit(&sfxge_global_lock); if (err != DDI_SUCCESS) { dev_err(dip, CE_WARN, SFXGE_CMN_ERR "intr_init: ddi_intr_alloc failed err=%d" " (navail=%d nalloc=%d)", err, navail, sip->si_nalloc); if (err == DDI_EINVAL) rc = EINVAL; else if (err == DDI_EAGAIN) rc = EAGAIN; else if (err == DDI_INTR_NOTFOUND) rc = ENOENT; else rc = EFAULT; goto fail3; } /* Double-check */ if (sip->si_nalloc == 0) { rc = ENOENT; goto fail3; } /* Round down to a power of 2 */ nalloc = pow2_le(sip->si_nalloc); /* Free off any excess handles */ mutex_enter(&sfxge_global_lock); index = sip->si_nalloc; while (--index >= nalloc) { (void) ddi_intr_free(sip->si_table[index]); sip->si_table[index] = NULL; } mutex_exit(&sfxge_global_lock); sip->si_nalloc = nalloc; DTRACE_PROBE1(nalloc, unsigned int, sip->si_nalloc); dma_attr.sdba_dip = sp->s_dip; dma_attr.sdba_dattrp = &sfxge_intr_dma_attr; dma_attr.sdba_callback = DDI_DMA_SLEEP; dma_attr.sdba_length = EFX_INTR_SIZE; dma_attr.sdba_memflags = DDI_DMA_CONSISTENT; dma_attr.sdba_devaccp = &sfxge_intr_devacc; dma_attr.sdba_bindflags = DDI_DMA_RDWR | DDI_DMA_CONSISTENT; dma_attr.sdba_maxcookies = 1; dma_attr.sdba_zeroinit = B_TRUE; if ((rc = sfxge_dma_buffer_create(esmp, &dma_attr)) != 0) goto fail4; /* Store the highest priority for convenience */ sip->si_intr_pri = 0; for (index = 0; index < sip->si_nalloc; index++) { uint_t pri; if ((rc = ddi_intr_get_pri(sip->si_table[index], &pri)) != 0) goto fail5; if (pri > sip->si_intr_pri) sip->si_intr_pri = pri; } sip->si_state = SFXGE_INTR_INITIALIZED; return (0); fail5: DTRACE_PROBE(fail5); fail4: DTRACE_PROBE(fail4); /* Free interrupt handles */ mutex_exit(&sfxge_global_lock); index = sip->si_nalloc; while (--index >= 0) { err = ddi_intr_free(sip->si_table[index]); if (err != DDI_SUCCESS) { dev_err(dip, CE_WARN, SFXGE_CMN_ERR "intr_init: ddi_intr_free failed err=%d" " (h=%p idx=%d nalloc=%d)", err, (void *)sip->si_table[index], index, sip->si_nalloc); } sip->si_table[index] = NULL; } sip->si_nalloc = 0; mutex_exit(&sfxge_global_lock); fail3: DTRACE_PROBE(fail3); /* Free the handle table */ kmem_free(sip->si_table, sip->si_table_size); sip->si_table = NULL; sip->si_table_size = 0; fail2: DTRACE_PROBE(fail2); /* Clear the interrupt type */ sip->si_type = EFX_INTR_INVALID; fail1: DTRACE_PROBE1(fail1, int, rc); SFXGE_OBJ_CHECK(sip, sfxge_intr_t); return (rc); } int sfxge_intr_start(sfxge_t *sp) { sfxge_intr_t *sip = &(sp->s_intr); int rc; ASSERT3U(sip->si_state, ==, SFXGE_INTR_INITIALIZED); /* Enable interrupts at the bus */ if ((rc = sfxge_intr_bus_enable(sp)) != 0) goto fail1; sip->si_state = SFXGE_INTR_TESTING; /* Enable interrupts at the NIC */ if ((rc = sfxge_intr_nic_enable(sp)) != 0) goto fail2; sip->si_state = SFXGE_INTR_STARTED; return (0); fail2: DTRACE_PROBE(fail2); /* Disable interrupts at the bus */ sfxge_intr_bus_disable(sp); fail1: DTRACE_PROBE1(fail1, int, rc); sip->si_state = SFXGE_INTR_INITIALIZED; return (rc); } void sfxge_intr_stop(sfxge_t *sp) { sfxge_intr_t *sip = &(sp->s_intr); ASSERT3U(sip->si_state, ==, SFXGE_INTR_STARTED); sip->si_state = SFXGE_INTR_INITIALIZED; /* Disable interrupts at the NIC */ sfxge_intr_nic_disable(sp); /* Disable interrupts at the bus */ sfxge_intr_bus_disable(sp); } void sfxge_intr_fini(sfxge_t *sp) { sfxge_intr_t *sip = &(sp->s_intr); efsys_mem_t *esmp = &(sip->si_mem); int index; int err; ASSERT3U(sip->si_state, ==, SFXGE_INTR_INITIALIZED); sip->si_state = SFXGE_INTR_UNINITIALIZED; /* Tear down dma setup */ sfxge_dma_buffer_destroy(esmp); /* Free interrupt handles */ mutex_enter(&sfxge_global_lock); index = sip->si_nalloc; while (--index >= 0) { err = ddi_intr_free(sip->si_table[index]); if (err != DDI_SUCCESS) { dev_err(sp->s_dip, CE_WARN, SFXGE_CMN_ERR "intr_fini: ddi_intr_free failed err=%d" " (h=%p idx=%d nalloc=%d)", err, (void *)sip->si_table[index], index, sip->si_nalloc); } sip->si_table[index] = NULL; } sip->si_nalloc = 0; mutex_exit(&sfxge_global_lock); /* Free the handle table */ kmem_free(sip->si_table, sip->si_table_size); sip->si_table = NULL; sip->si_table_size = 0; /* Clear the interrupt type */ sip->si_type = EFX_INTR_INVALID; SFXGE_OBJ_CHECK(sip, sfxge_intr_t); }