root/sys/dev/smartpqi/smartpqi_intr.c
/*-
 * Copyright 2016-2025 Microchip Technology, Inc. and/or its subsidiaries.
 *
 * 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 "smartpqi_includes.h"


/*
 * Function to get processor count
 */
int
os_get_processor_config(pqisrc_softstate_t *softs)
{
        DBG_FUNC("IN\n");
        softs->num_cpus_online = mp_ncpus;
        DBG_FUNC("OUT\n");

        return PQI_STATUS_SUCCESS;
}

/*
 * Function to get interrupt count and type supported
 */
int
os_get_intr_config(pqisrc_softstate_t *softs)
{
        device_t dev = softs->os_specific.pqi_dev;
        int msi_count = pci_msix_count(dev);
        int error = BSD_SUCCESS;

        DBG_FUNC("IN\n");

        if (msi_count > softs->num_cpus_online)
                msi_count = softs->num_cpus_online;
        if (msi_count > PQI_MAX_MSIX)
                msi_count = PQI_MAX_MSIX;
        if (msi_count == 0 || (error = pci_alloc_msix(dev, &msi_count)) != 0) {
                device_printf(dev, "alloc msix failed - msi_count=%d, err=%d; "
                                   "will try MSI\n", msi_count, error);
                pci_release_msi(dev);
        } else {
                softs->intr_count = msi_count;
                softs->intr_type = INTR_TYPE_MSIX;
                softs->os_specific.msi_enabled = TRUE;
                device_printf(dev, "using MSI-X interrupts (%d vectors)\n",
                        msi_count);
        }
        if (!softs->intr_type) {
                msi_count = 1;
                if ((error = pci_alloc_msi(dev, &msi_count)) != 0) {
                        device_printf(dev, "alloc msi failed - err=%d; "
                                "will use INTx\n", error);
                        pci_release_msi(dev);
                } else {
                        softs->os_specific.msi_enabled = TRUE;
                        softs->intr_count = msi_count;
                        softs->intr_type = INTR_TYPE_MSI;
                        device_printf(dev, "using MSI interrupts\n");
                }
        }

        if (!softs->intr_type) {
                device_printf(dev, "using legacy interrupts\n");
                softs->intr_type = INTR_TYPE_FIXED;
                softs->intr_count = 1;
        }

        error = bsd_status_to_pqi_status(BSD_SUCCESS);

        DBG_FUNC("OUT\n");

        return error;
}

void
os_eventtaskqueue_enqueue(pqisrc_softstate_t *sc)
{
        taskqueue_enqueue(taskqueue_swi, &sc->os_specific.event_task);
}

void
pqisrc_event_worker(void *arg1, int arg2)
{
        pqisrc_ack_all_events(arg1);
}

/*
 * ithread routine to handle uniprocessor systems
 */
static void
shared_ithread_routine(void *arg)
{
        pqi_intr_ctx_t *intr_ctx = (pqi_intr_ctx_t *)arg;
        pqisrc_softstate_t *softs = device_get_softc(intr_ctx->pqi_dev);
        int oq_id  = intr_ctx->oq_id;

        DBG_FUNC("IN\n");

        if (!softs)
                return;

        pqisrc_process_response_queue(softs, oq_id);
        pqisrc_process_event_intr_src(softs, oq_id - 1);

        DBG_FUNC("OUT\n");
}

/*
 * ithread routine to process non event response
 */
static void
common_ithread_routine(void *arg)
{
        pqi_intr_ctx_t *intr_ctx = (pqi_intr_ctx_t *)arg;
        pqisrc_softstate_t *softs = device_get_softc(intr_ctx->pqi_dev);
        int oq_id  = intr_ctx->oq_id;

        DBG_FUNC("IN\n");

        if (!softs)
                return;

        pqisrc_process_response_queue(softs, oq_id);

        DBG_FUNC("OUT\n");
}

static void
event_ithread_routine(void *arg)
{
        pqi_intr_ctx_t *intr_ctx = (pqi_intr_ctx_t *)arg;
        pqisrc_softstate_t *softs = device_get_softc(intr_ctx->pqi_dev);
        int oq_id  = intr_ctx->oq_id;

        DBG_FUNC("IN\n");

        if (!softs)
                return;

        pqisrc_process_event_intr_src(softs, oq_id);

        DBG_FUNC("OUT\n");
}

/*
 * Registration of legacy interrupt in case MSI is unsupported
 */
int
register_legacy_intr(pqisrc_softstate_t *softs)
{
        int error = BSD_SUCCESS;
        device_t dev;

        DBG_FUNC("IN\n");

        dev = softs->os_specific.pqi_dev;

        softs->os_specific.pqi_irq_rid[0] = 0;
        softs->os_specific.pqi_irq[0] = bus_alloc_resource_any(dev, \
                SYS_RES_IRQ, &softs->os_specific.pqi_irq_rid[0],
                RF_ACTIVE | RF_SHAREABLE);
        if (NULL == softs->os_specific.pqi_irq[0]) {
                DBG_ERR("Failed to allocate resource for interrupt\n");
                return ENXIO;
        }
        if ((softs->os_specific.msi_ctx = os_mem_alloc(softs,sizeof(pqi_intr_ctx_t))) == NULL) {
                DBG_ERR("Failed to allocate memory for msi_ctx\n");
                return ENXIO;
        }
        softs->os_specific.msi_ctx[0].pqi_dev = dev;
        /* For Legacy support oq_id should be one */
        softs->os_specific.msi_ctx[0].oq_id = 1;

        error = bus_setup_intr(dev, softs->os_specific.pqi_irq[0],
                                INTR_TYPE_CAM | INTR_MPSAFE, \
                                NULL, shared_ithread_routine,
                                &softs->os_specific.msi_ctx[0],
                                &softs->os_specific.intrcookie[0]);
        if (error) {
                DBG_ERR("Failed to setup legacy interrupt err = %d\n", error);
                return error;
        }
        softs->os_specific.intr_registered[0] = TRUE;

        DBG_FUNC("OUT error = %d\n", error);

        return error;
}

/*
 * Registration of MSIx
 */
int
register_msix_intr(pqisrc_softstate_t *softs)
{
        int error = BSD_SUCCESS;
        int i = 0;
        device_t dev = softs->os_specific.pqi_dev;
        int msix_count = softs->intr_count;
        size_t msix_size =  sizeof(pqi_intr_ctx_t) * msix_count;

        DBG_FUNC("IN\n");

        softs->os_specific.msi_ctx = os_mem_alloc(softs, msix_size);
        if (!softs->os_specific.msi_ctx) {
                DBG_ERR("Memory allocation failed, Requested memory:%lu bytes\n", (unsigned long)msix_size);
                return ENXIO;
        }

        /*Add shared handler */
        if (softs->share_opq_and_eventq) {
                softs->os_specific.pqi_irq_rid[i] = i+1;
                softs->os_specific.pqi_irq[i] = bus_alloc_resource_any(dev, \
                                                SYS_RES_IRQ,
                                                &softs->os_specific.pqi_irq_rid[i],
                                                RF_SHAREABLE |  RF_ACTIVE);
                if (NULL == softs->os_specific.pqi_irq[i]) {
                        DBG_ERR("Failed to allocate \
                                event interrupt resource\n");
                        return ENXIO;
                }

                softs->os_specific.msi_ctx[i].pqi_dev = dev;
                softs->os_specific.msi_ctx[i].oq_id = i+1;

                error = bus_setup_intr(dev,softs->os_specific.pqi_irq[i],
                                        INTR_TYPE_CAM | INTR_MPSAFE,\
                                        NULL,
                                        shared_ithread_routine,
                                        &softs->os_specific.msi_ctx[i],
                                        &softs->os_specific.intrcookie[i]);

                if (error) {
                        DBG_ERR("Failed to setup interrupt for events r=%d\n",
                                error);
                        return error;
                }
                softs->os_specific.intr_registered[i] = TRUE;
        }
        else {
                /* Add event handler */
                softs->os_specific.pqi_irq_rid[i] = i+1;
                softs->os_specific.pqi_irq[i] = bus_alloc_resource_any(dev, \
                                                SYS_RES_IRQ,
                                                &softs->os_specific.pqi_irq_rid[i],
                                                RF_SHAREABLE |  RF_ACTIVE);
                if (NULL == softs->os_specific.pqi_irq[i]) {
                        DBG_ERR("Failed to allocate event interrupt resource\n");
                        return ENXIO;
                }

                softs->os_specific.msi_ctx[i].pqi_dev = dev;
                softs->os_specific.msi_ctx[i].oq_id = i;

                error = bus_setup_intr(dev,softs->os_specific.pqi_irq[i],
                                        INTR_TYPE_CAM | INTR_MPSAFE,\
                                        NULL,
                                        event_ithread_routine,
                                        &softs->os_specific.msi_ctx[i],
                                        &softs->os_specific.intrcookie[i]);
                if (error) {
                        DBG_ERR("Failed to setup interrupt for events err=%d\n",
                                error);
                        return error;
                }
                softs->os_specific.intr_registered[i] = TRUE;
                /* Add interrupt handlers*/
                for (i = 1; i < msix_count; ++i) {
                        softs->os_specific.pqi_irq_rid[i] = i+1;
                        softs->os_specific.pqi_irq[i] = \
                                        bus_alloc_resource_any(dev,
                                        SYS_RES_IRQ,
                                        &softs->os_specific.pqi_irq_rid[i],
                                        RF_SHAREABLE | RF_ACTIVE);
                        if (NULL == softs->os_specific.pqi_irq[i]) {
                                DBG_ERR("Failed to allocate \
                                        msi/x interrupt resource\n");
                                return ENXIO;
                        }
                        softs->os_specific.msi_ctx[i].pqi_dev = dev;
                        softs->os_specific.msi_ctx[i].oq_id = i;
                        error = bus_setup_intr(dev,
                                        softs->os_specific.pqi_irq[i],
                                        INTR_TYPE_CAM | INTR_MPSAFE,\
                                        NULL,
                                        common_ithread_routine,
                                        &softs->os_specific.msi_ctx[i],
                                        &softs->os_specific.intrcookie[i]);
                        if (error) {
                                DBG_ERR("Failed to setup \
                                        msi/x interrupt error = %d\n", error);
                                return error;
                        }
                        softs->os_specific.intr_registered[i] = TRUE;
                }
        }

        DBG_FUNC("OUT error = %d\n", error);

        return error;
}

/*
 * Setup interrupt depending on the configuration
 */
int
os_setup_intr(pqisrc_softstate_t *softs)
{
        int bsd_status, pqi_status;

        DBG_FUNC("IN\n");

        if (softs->intr_type == INTR_TYPE_FIXED) {
                bsd_status = register_legacy_intr(softs);
        }
        else {
                bsd_status = register_msix_intr(softs);
        }

        if (bsd_status)
                DBG_WARN("interrupt registration is failed, error = %d\n", bsd_status);

        pqi_status = bsd_status_to_pqi_status(bsd_status);

        DBG_FUNC("OUT\n");

        return pqi_status;
}

/*
 * Deregistration of legacy interrupt
 */
void
deregister_pqi_intx(pqisrc_softstate_t *softs)
{
        device_t dev = softs->os_specific.pqi_dev;

        DBG_FUNC("IN\n");

        if (softs->os_specific.pqi_irq[0] != NULL) {
                if (softs->os_specific.intr_registered[0]) {
                        bus_teardown_intr(dev, softs->os_specific.pqi_irq[0],
                                        softs->os_specific.intrcookie[0]);
                        softs->os_specific.intr_registered[0] = FALSE;
                }
                bus_release_resource(dev, SYS_RES_IRQ,
                        softs->os_specific.pqi_irq_rid[0],
                        softs->os_specific.pqi_irq[0]);
                softs->os_specific.pqi_irq[0] = NULL;
                os_mem_free(softs, (char*)softs->os_specific.msi_ctx, sizeof(pqi_intr_ctx_t));
        }

        DBG_FUNC("OUT\n");
}

/*
 * Deregistration of MSIx interrupt
 */
void
deregister_pqi_msix(pqisrc_softstate_t *softs)
{
        device_t dev = softs->os_specific.pqi_dev;
        int msix_count = softs->intr_count;
        int i = 0;

        DBG_FUNC("IN\n");

        os_mem_free(softs, (char*)softs->os_specific.msi_ctx, sizeof(pqi_intr_ctx_t) * msix_count);
        softs->os_specific.msi_ctx = NULL;

        for (; i < msix_count; ++i) {
                if (softs->os_specific.pqi_irq[i] != NULL) {
                        if (softs->os_specific.intr_registered[i]) {
                                bus_teardown_intr(dev,
                                        softs->os_specific.pqi_irq[i],
                                        softs->os_specific.intrcookie[i]);
                                softs->os_specific.intr_registered[i] = FALSE;
                        }
                        bus_release_resource(dev, SYS_RES_IRQ,
                                softs->os_specific.pqi_irq_rid[i],
                        softs->os_specific.pqi_irq[i]);
                        softs->os_specific.pqi_irq[i] = NULL;
                }
        }

        DBG_FUNC("OUT\n");
}

/*
 * Function to destroy interrupts registered
 */
int
os_destroy_intr(pqisrc_softstate_t *softs)
{
        device_t dev = softs->os_specific.pqi_dev;

        DBG_FUNC("IN\n");

        if (softs->intr_type == INTR_TYPE_FIXED) {
                deregister_pqi_intx(softs);
        } else if (softs->intr_type == INTR_TYPE_MSIX) {
                deregister_pqi_msix(softs);
        }
        if (softs->os_specific.msi_enabled) {
                pci_release_msi(dev);
                softs->os_specific.msi_enabled = FALSE;
        }

        DBG_FUNC("OUT\n");

        return PQI_STATUS_SUCCESS;
}

/*
 * Free interrupt related resources for the adapter
 */
void
os_free_intr_config(pqisrc_softstate_t *softs)
{
        device_t dev = softs->os_specific.pqi_dev;

        DBG_FUNC("IN\n");

        if (softs->os_specific.msi_enabled) {
                pci_release_msi(dev);
                softs->os_specific.msi_enabled = FALSE;
        }

        DBG_FUNC("OUT\n");
}