root/usr/src/uts/common/io/bnx/bnxint.c
/*
 * Copyright 2014-2017 Cavium, Inc.
 * The contents of this file are subject to the terms of the Common Development
 * and Distribution License, v.1,  (the "License").
 *
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the License at available
 * at http://opensource.org/licenses/CDDL-1.0
 *
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
 * Copyright (c) 2019, Joyent, Inc.
 */

#include "bnxint.h"
#include "bnxsnd.h"
#include "bnxrcv.h"


#define BNX_INTR_NUMBER 0

/*
 * Name:    bnx_intr_priv
 *
 * Input:   ptr to um_device_t
 *
 * Return:  Interrupt status
 *
 * Description:
 *          This routine is called from ISR and POLL API routines to consume
 *          any pending events. This function determines if there is any
 *          pending status and calls corresponding LM functions to consume
 *          the event. L2 driver consumes three events - L2 Tx compete,
 *          L2 Rx indication and link status change.
 */
static lm_interrupt_status_t
bnx_intr_priv(um_device_t *const umdevice)
{
        u32_t idx;
        lm_device_t *lmdevice;
        lm_interrupt_status_t intrstat;

        lmdevice = &(umdevice->lm_dev);

        /*
         * Following LM routine checks for pending interrupts and
         * returns corresponding bits set in a 32bit integer value.
         */
        intrstat = lm_get_interrupt_status(lmdevice);

        if (intrstat & LM_KNOCK_KNOCK_EVENT) {
                um_send_driver_pulse(umdevice);
        }

        if (intrstat & LM_RX_EVENT_MASK) {
                for (idx = RX_CHAIN_IDX0; idx < NUM_RX_CHAIN; idx++) {
                        if (intrstat & (LM_RX0_EVENT_ACTIVE << idx)) {
                                s_list_t *waitq;

                                waitq = &(_RX_QINFO(umdevice, idx).waitq);

                                mutex_enter(&umdevice->os_param.rcv_mutex);
                                (void) lm_get_packets_rcvd(lmdevice, idx, 0,
                                    waitq);
                                mutex_exit(&umdevice->os_param.rcv_mutex);
                        }
                }
        }

        if (intrstat & LM_TX_EVENT_MASK) {
                for (idx = TX_CHAIN_IDX0; idx < NUM_TX_CHAIN; idx++) {
                        if (intrstat & (LM_TX0_EVENT_ACTIVE << idx)) {
                                /* This call is mutex protected internally. */
                                bnx_xmit_ring_intr(umdevice, idx);
                        }
                }
        }

        if (intrstat & LM_PHY_EVENT_ACTIVE) {
                mutex_enter(&umdevice->os_param.phy_mutex);
                lm_service_phy_int(lmdevice, FALSE);
                mutex_exit(&umdevice->os_param.phy_mutex);
        }

        return (intrstat);
}

/*
 * Description:
 *
 * This function sends rx traffic up the stack and replenishes the hardware
 * rx buffers.  Although we share the responsibility of replenishing the
 * rx buffers with the timer, we still need to wait here indefinitely.  This
 * is the only place where we send rx traffic back up the stack.
 *
 * We go through a lot of mental gymnastics to make sure we are not holding a
 * lock while calling gld_recv().  We can deadlock in the following scenario
 * if we aren't careful :
 *
 * Thread 1:
 *          bnx_intr_disable()
 *              bnx_intr_wait()
 *                  mutex_enter(intr_*_mutex)
 *
 * Thread 2:
 *          bnx_intr_[soft|1lvl]()
 *              bnx_intr_recv()
 *                  mutex_enter(rcv_mutex)
 *
 * Thread 3:
 *          bnx_intr_[soft|1lvl]()
 *              mutex_enter(intr_*_mutex)
 *              mutex_enter(rcv_mutex)
 *
 * Return:
 */
static void
bnx_intr_recv(um_device_t * const umdevice)
{
        mutex_enter(&umdevice->os_param.rcv_mutex);

        if (umdevice->intr_enabled == B_TRUE) {
                /*
                 * Send the rx packets up.  This function will release and
                 * acquire the receive mutex across calls to gld_recv().
                 */
                bnx_rxpkts_intr(umdevice);
        }

        /*
         * Since gld_recv() can hang while decommisioning the driver, we
         * need to double check that interrupts are still enabled before
         * attempting to replenish the rx buffers.
         */
        if (umdevice->intr_enabled == B_TRUE) {
                /* This function does an implicit *_fill(). */
                bnx_rxpkts_post(umdevice);
        }

        mutex_exit(&umdevice->os_param.rcv_mutex);
}

static void
bnx_intr_xmit(um_device_t *const umdevice)
{
        mutex_enter(&umdevice->os_param.xmit_mutex);

        if (umdevice->intr_enabled == B_TRUE) {
                /*
                 * Send the tx packets in waitq & notify the GLD.
                 */
                bnx_txpkts_intr(umdevice);
        }

        mutex_exit(&umdevice->os_param.xmit_mutex);
}

static unsigned int
bnx_intr_1lvl(caddr_t arg1, caddr_t arg2)
{
        lm_device_t *lmdevice;
        um_device_t *umdevice;
        lm_interrupt_status_t intrstat = 0;
        u32_t value32;
        umdevice = (um_device_t *)arg1;

        lmdevice = &(umdevice->lm_dev);

        mutex_enter(&umdevice->intr_mutex);

        if (umdevice->intr_enabled != B_TRUE) {
                /*
                 * The interrupt cannot be ours.  Interrupts
                 * from our device have been disabled.
                 */
                mutex_exit(&umdevice->intr_mutex);
                umdevice->intr_in_disabled++;
                return (DDI_INTR_UNCLAIMED);
        }

        /* Make sure we are working with current data. */
        (void) ddi_dma_sync(*(umdevice->os_param.status_block_dma_hdl), 0,
            STATUS_BLOCK_BUFFER_SIZE, DDI_DMA_SYNC_FORKERNEL);

        /* Make sure it is our device that is interrupting. */
        if (lmdevice->vars.status_virt->deflt.status_idx ==
            umdevice->dev_var.processed_status_idx) {
                /*
                 * It is possible that we could have arrived at the ISR
                 * before the status block had a chance to be DMA'd into
                 * host memory.  Reading the status of the INTA line will
                 * implicitly force the DMA, and inform us of whether we
                 * are truly interrupting.  INTA is active low.
                 */
                REG_RD(lmdevice, pci_config.pcicfg_misc_status, &value32);
                if (value32 & PCICFG_MISC_STATUS_INTA_VALUE) {
                        /* This isn't our interrupt. */
                        umdevice->intr_no_change++;
                        mutex_exit(&umdevice->intr_mutex);
                        return (DDI_INTR_UNCLAIMED);
                }
        }

        umdevice->intrFired++;

        /* Disable interrupt and enqueue soft intr processing. */
        REG_WR(lmdevice, pci_config.pcicfg_int_ack_cmd,
            (PCICFG_INT_ACK_CMD_USE_INT_HC_PARAM |
            PCICFG_INT_ACK_CMD_MASK_INT));

        FLUSHPOSTEDWRITES(lmdevice);

        umdevice->dev_var.processed_status_idx =
            lmdevice->vars.status_virt->deflt.status_idx;

        /* Service the interrupts. */
        intrstat = bnx_intr_priv(umdevice);

        value32 = umdevice->dev_var.processed_status_idx;
        value32 |= PCICFG_INT_ACK_CMD_INDEX_VALID;

        /*
         * Inform the hardware of the last interrupt event we processed
         * and reinstate the hardware's ability to assert interrupts.
         */
        REG_WR(lmdevice, pci_config.pcicfg_int_ack_cmd, value32);

        FLUSHPOSTEDWRITES(lmdevice);

        umdevice->intr_count++;

        if (intrstat & LM_RX_EVENT_MASK) {
                bnx_intr_recv(umdevice);
        }

        if (intrstat & LM_TX_EVENT_MASK) {
                bnx_intr_xmit(umdevice);
        }

        mutex_exit(&umdevice->intr_mutex);

        return (DDI_INTR_CLAIMED);
}

void
bnx_intr_enable(um_device_t * const umdevice)
{
        int rc;

        umdevice->intr_count = 0;

        /*
         * Allow interrupts to touch the hardware.
         */
        umdevice->intr_enabled = B_TRUE;

        if ((rc = ddi_intr_enable(umdevice->pIntrBlock[0])) != DDI_SUCCESS) {
                cmn_err(CE_WARN, "%s: Failed to enable default isr block (%d)",
                    umdevice->dev_name, rc);
                return; /* XXX return error */
        }

        /* Allow the hardware to generate interrupts. */
        lm_enable_int(&(umdevice->lm_dev));

        FLUSHPOSTEDWRITES(&(umdevice->lm_dev));

        /*
         * XXX This delay is here because of a discovered problem regarding a
         * call to ddi_intr_disable immediately after enabling interrupts.  This
         * can occur with the "ifconfig -a plumb up" command which brings an
         * interface up/down/up/down/up.  There seems to be a race condition
         * between the ddi_intr_enable/lm_enable_int and ddi_intr_disable
         * routines that results in interrupts to no longer fire on the
         * interface and a REBOOT IS REQUIRED to fix!
         */
        drv_usecwait(2000000);
}

/*
 * Description:
 *
 * This function makes sure the ISR no longer touches the hardware.  It
 * accomplishes this by making sure the ISR either completes, or that it
 * acknowledges the intr_enabled status change.
 *
 * Return:
 */
static void
bnx_intr_wait(um_device_t * const umdevice)
{
        if (mutex_tryenter(&umdevice->intr_mutex)) {
                /*
                 * If we were able to get the hardware interrupt mutex, then it
                 * means that either the ISR wasn't processing at this time, or
                 * that it was at the end, processing the receive packets. If it
                 * the latter case, then all we need to do is acquire the
                 * rcv_mutex.  If we can acquire it, it means the receive
                 * processing is stalled, waiting for a GLD mutex, or that the
                 * ISR is not processing RX packets.
                 */
                mutex_enter(&umdevice->os_param.rcv_mutex);
                mutex_exit(&umdevice->os_param.rcv_mutex);
        } else {
                /*
                 * We couldn't acquire the hardware interrupt mutex. This means
                 * the ISR is running.  Wait for it to complete by
                 * (re)attempting to acquire the interrupt mutex. Whether we
                 * acquire it immediately or not, we will know the ISR has
                 * acknowledged the intr_enabled status change.
                 */
                mutex_enter(&umdevice->intr_mutex);
        }
        mutex_exit(&umdevice->intr_mutex);
}


void
bnx_intr_disable(um_device_t * const umdevice)
{
        int rc;

        /*
         * Prevent any future interrupts to no longer touch the hardware.
         */
        umdevice->intr_enabled = B_FALSE;

        /*
         * Wait for any currently running interrupt to complete.
         */
        bnx_intr_wait(umdevice);

        /* Stop the device from generating any interrupts. */
        lm_disable_int(&(umdevice->lm_dev));

        FLUSHPOSTEDWRITES(&(umdevice->lm_dev));

        if ((rc = ddi_intr_disable(umdevice->pIntrBlock[0])) != DDI_SUCCESS) {
                cmn_err(CE_WARN, "%s: Failed to disable default isr (%d)",
                    umdevice->dev_name, rc);
        }
}

int
bnxIntrInit(um_device_t *umdevice)
{
        dev_info_t *pDev = umdevice->os_param.dip;
        int intrActual, rc;

        if ((umdevice->pIntrBlock = kmem_zalloc(sizeof (ddi_intr_handle_t),
            KM_SLEEP)) == NULL) {
                cmn_err(CE_WARN, "%s: Failed to allocate interrupt handle "
                    "block!", umdevice->dev_name);
                return (-1);
        }

        umdevice->intrType = (umdevice->dev_var.disableMsix) ?
            DDI_INTR_TYPE_FIXED : DDI_INTR_TYPE_MSIX;

        while (1) {
                if ((rc = ddi_intr_alloc(pDev, umdevice->pIntrBlock,
                    umdevice->intrType, 0, 1, &intrActual,
                    DDI_INTR_ALLOC_NORMAL)) != DDI_SUCCESS) {
                        cmn_err(CE_WARN, "!%s: Failed to initialize default "
                            "%s isr handle block (%d)", umdevice->dev_name,
                            (umdevice->intrType == DDI_INTR_TYPE_MSIX) ?
                            "MSIX" : "Fixed", rc);

                        if (umdevice->intrType == DDI_INTR_TYPE_MSIX) {
                                cmn_err(CE_WARN, "!%s: Reverting to Fixed "
                                    "level interrupts", umdevice->dev_name);

                                umdevice->intrType = DDI_INTR_TYPE_FIXED;
                                continue;
                        } else {
                                kmem_free(umdevice->pIntrBlock,
                                    sizeof (ddi_intr_handle_t));
                                return (-1);
                        }
                }
                break;
        }

        if (intrActual != 1) {
                cmn_err(CE_WARN, "%s: Failed to alloc minimum default "
                    "isr handler!", umdevice->dev_name);
                (void) ddi_intr_free(umdevice->pIntrBlock[0]);
                kmem_free(umdevice->pIntrBlock, sizeof (ddi_intr_handle_t));
                return (-1);
        }

        if ((rc = ddi_intr_get_pri(umdevice->pIntrBlock[0],
            &umdevice->intrPriority)) != DDI_SUCCESS) {
                cmn_err(CE_WARN, "%s: Failed to get isr priority (%d)",
                    umdevice->dev_name, rc);
                (void) ddi_intr_free(umdevice->pIntrBlock[0]);
                kmem_free(umdevice->pIntrBlock, sizeof (ddi_intr_handle_t));
                return (-1);
        }

        if (umdevice->intrPriority >= ddi_intr_get_hilevel_pri()) {
                cmn_err(CE_WARN, "%s: Interrupt priority is too high",
                    umdevice->dev_name);
                (void) ddi_intr_free(umdevice->pIntrBlock[0]);
                kmem_free(umdevice->pIntrBlock, sizeof (ddi_intr_handle_t));
                return (-1);
        }

        if ((rc = ddi_intr_add_handler(umdevice->pIntrBlock[0], bnx_intr_1lvl,
            (caddr_t)umdevice, NULL)) != DDI_SUCCESS) {
                cmn_err(CE_WARN, "%s: Failed to add the default isr "
                    "handler (%d)", umdevice->dev_name, rc);
                (void) ddi_intr_free(umdevice->pIntrBlock[0]);
                kmem_free(umdevice->pIntrBlock, sizeof (ddi_intr_handle_t));
                return (-1);
        }

        /* Intialize the mutex used by the hardware interrupt handler. */
        mutex_init(&umdevice->intr_mutex, NULL, MUTEX_DRIVER,
            DDI_INTR_PRI(umdevice->intrPriority));

        umdevice->lm_dev.vars.interrupt_mode =
            (umdevice->intrType == DDI_INTR_TYPE_FIXED) ?
            IRQ_MODE_LINE_BASED : IRQ_MODE_MSIX_BASED;
        return (0);
}

void
bnxIntrFini(um_device_t *umdevice)
{
        int ret;

        if ((ret = ddi_intr_disable(umdevice->pIntrBlock[0])) != 0) {
                dev_err(umdevice->os_param.dip, CE_WARN,
                    "failed to disable interrupt: %d", ret);
        }
        if ((ret = ddi_intr_remove_handler(umdevice->pIntrBlock[0])) != 0) {
                dev_err(umdevice->os_param.dip, CE_WARN,
                    "failed to remove interrupt: %d", ret);
        }
        if ((ret = ddi_intr_free(umdevice->pIntrBlock[0])) != 0) {
                dev_err(umdevice->os_param.dip, CE_WARN,
                    "failed to free interrupt: %d", ret);
        }
        kmem_free(umdevice->pIntrBlock, sizeof (ddi_intr_handle_t));

        umdevice->pIntrBlock = NULL;

        mutex_destroy(&umdevice->intr_mutex);
}