root/usr/src/uts/common/io/ena/ena_intr.c
/*
 * This file and its contents are supplied under the terms of the
 * Common Development and Distribution License ("CDDL"), version 1.0.
 * You may only use this file in accordance with the terms of version
 * 1.0 of the CDDL.
 *
 * A full copy of the text of the CDDL should have accompanied this
 * source.  A copy of the CDDL is also available via the Internet at
 * http://www.illumos.org/license/CDDL.
 */

/*
 * Copyright 2024 Oxide Computer Company
 */

#include "ena.h"

/*
 * We currently limit the number of Tx/Rx queues to the number of
 * available interrupts (minus one for the admin queue).
 */
static uint_t
ena_io_intr(caddr_t arg1, caddr_t arg2)
{
        ena_t *ena = (ena_t *)arg1;
        uint16_t vector = (uintptr_t)(void *)arg2;
        ASSERT3U(vector, >, 0);
        ASSERT3U(vector, <, ena->ena_num_intrs);
        ena_txq_t *txq = &ena->ena_txqs[vector - 1];
        ena_rxq_t *rxq = &ena->ena_rxqs[vector - 1];
        uint32_t intr_ctrl;

        if ((ena->ena_state & ENA_STATE_STARTED) == 0)
                return (DDI_INTR_CLAIMED);

        ASSERT3P(txq, !=, NULL);
        ASSERT3P(rxq, !=, NULL);
        ena_tx_intr_work(txq);
        ena_rx_intr_work(rxq);

        /*
         * The Rx/Tx queue share the same interrupt, only need to
         * unmask interrupts for one of them.
         */
        intr_ctrl = ena_hw_abs_read32(ena, txq->et_cq_unmask_addr);
        ENAHW_REG_INTR_UNMASK(intr_ctrl);
        ena_hw_abs_write32(ena, txq->et_cq_unmask_addr, intr_ctrl);
        return (DDI_INTR_CLAIMED);
}

static uint_t
ena_admin_intr(caddr_t arg1, caddr_t arg2)
{
        ena_t *ena = (ena_t *)arg1;

        if ((ena->ena_state & ENA_STATE_STARTED) != 0)
                ena_aenq_work(ena);
        return (DDI_INTR_CLAIMED);
}

void
ena_intr_remove_handlers(ena_t *ena, bool resetting)
{
        VERIFY0(resetting);

        for (int i = 0; i < ena->ena_num_intrs; i++) {
                int ret = ddi_intr_remove_handler(ena->ena_intr_handles[i]);

                /* Nothing we can really do except log. */
                if (ret != DDI_SUCCESS) {
                        ena_err(ena, "failed to remove interrupt handler for "
                            "vector %d: %d", i, ret);
                }
        }
}

/*
 * The ena driver uses separate interrupt handlers for the admin queue
 * and I/O queues.
 */
bool
ena_intr_add_handlers(ena_t *ena)
{
        ASSERT3S(ena->ena_num_intrs, >=, 2);
        if (ddi_intr_add_handler(ena->ena_intr_handles[0], ena_admin_intr, ena,
            (void *)(uintptr_t)0) != DDI_SUCCESS) {
                ena_err(ena, "failed to add admin interrupt handler");
                return (false);
        }

        for (int i = 1; i < ena->ena_num_intrs; i++) {
                caddr_t vector = (void *)(uintptr_t)(i);
                int ret = ddi_intr_add_handler(ena->ena_intr_handles[i],
                    ena_io_intr, ena, vector);

                if (ret != DDI_SUCCESS) {
                        ena_err(ena, "failed to add I/O interrupt handler "
                            "for vector %u", i);

                        /*
                         * If we fail to add any I/O handler, then all
                         * successfully added handlers are removed,
                         * including the admin handler. For example,
                         * when i=2 we remove handler 1 (the first I/O
                         * handler), and when i=1 we remove handler 0
                         * (the admin handler).
                         */
                        while (i >= 1) {
                                i--;
                                (void) ddi_intr_remove_handler(
                                    ena->ena_intr_handles[i]);
                        }

                        return (false);
                }
        }

        return (true);
}

bool
ena_intrs_disable(ena_t *ena)
{
        int ret;

        if (ena->ena_intr_caps & DDI_INTR_FLAG_BLOCK) {
                if ((ret = ddi_intr_block_disable(ena->ena_intr_handles,
                    ena->ena_num_intrs)) != DDI_SUCCESS) {
                        ena_err(ena, "failed to block disable interrupts: %d",
                            ret);
                        return (false);
                }
        } else {
                for (int i = 0; i < ena->ena_num_intrs; i++) {
                        ret = ddi_intr_disable(ena->ena_intr_handles[i]);
                        if (ret != DDI_SUCCESS) {
                                ena_err(ena, "failed to disable interrupt "
                                    "%d: %d", i, ret);
                                return (false);
                        }
                }
        }

        return (true);
}

bool
ena_intrs_enable(ena_t *ena)
{
        int ret;

        if (ena->ena_intr_caps & DDI_INTR_FLAG_BLOCK) {
                if ((ret = ddi_intr_block_enable(ena->ena_intr_handles,
                    ena->ena_num_intrs)) != DDI_SUCCESS) {
                        ena_err(ena, "failed to block enable interrupts: %d",
                            ret);
                        return (false);
                }
        } else {
                for (int i = 0; i < ena->ena_num_intrs; i++) {
                        if ((ret = ddi_intr_enable(ena->ena_intr_handles[i])) !=
                            DDI_SUCCESS) {
                                ena_err(ena, "failed to enable interrupt "
                                    "%d: %d", i, ret);

                                /*
                                 * If we fail to enable any interrupt,
                                 * then all interrupts are disabled.
                                 */
                                while (i >= 1) {
                                        i--;
                                        (void) ddi_intr_disable(
                                            ena->ena_intr_handles[i]);
                                }

                                return (false);
                        }
                }
        }

        return (true);
}