/*
 * 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 2021 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;

	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;

	ena_aenq_work(ena);
	return (DDI_INTR_CLAIMED);
}

void
ena_intr_remove_handlers(ena_t *ena)
{
	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.
 */
boolean_t
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 (B_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 (B_FALSE);
		}
	}

	return (B_TRUE);
}

boolean_t
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 (B_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 (B_FALSE);
			}
		}
	}

	return (B_TRUE);
}

boolean_t
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 (B_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 (B_FALSE);
			}
		}
	}

	return (B_TRUE);
}