/* SPDX-License-Identifier: BSD-3-Clause */
/* Copyright(c) 2007-2022 Intel Corporation */
#include "qat_freebsd.h"
#include "adf_cfg.h"
#include "adf_common_drv.h"
#include "adf_accel_devices.h"
#include "icp_qat_uclo.h"
#include "icp_qat_fw.h"
#include "icp_qat_fw_init_admin.h"
#include "adf_cfg_strings.h"
#include "adf_transport_access_macros.h"
#include "adf_transport_internal.h"
#include <sys/types.h>
#include <sys/bus.h>
#include <sys/smp.h>
#include <dev/pci/pcivar.h>
#include <sys/malloc.h>
#include "adf_accel_devices.h"
#include "adf_common_drv.h"
#include "adf_cfg.h"
#include "adf_cfg_strings.h"
#include "adf_cfg_common.h"
#include "adf_transport_access_macros.h"
#include "adf_transport_internal.h"
#include "adf_dev_err.h"

TASKQUEUE_DEFINE_THREAD(qat_pf);

static int
adf_enable_msix(struct adf_accel_dev *accel_dev)
{
	struct adf_accel_pci *info_pci_dev = &accel_dev->accel_pci_dev;
	struct adf_hw_device_data *hw_data = accel_dev->hw_device;
	int msix_num_entries = 1;
	int count = 0;
	int error = 0;
	int num_vectors = 0;
	u_int *vectors;

	if (hw_data->set_msix_rttable)
		hw_data->set_msix_rttable(accel_dev);

	/* If SR-IOV is disabled, add entries for each bank */
	if (!accel_dev->u1.pf.vf_info) {
		msix_num_entries += hw_data->num_banks;
		num_vectors = 0;
		vectors = NULL;
	} else {
		num_vectors = hw_data->num_banks + 1;
		vectors = malloc(num_vectors * sizeof(u_int),
				 M_QAT,
				 M_WAITOK | M_ZERO);
		vectors[hw_data->num_banks] = 1;
	}

	count = msix_num_entries;
	error = pci_alloc_msix(info_pci_dev->pci_dev, &count);
	if (error == 0 && count != msix_num_entries) {
		pci_release_msi(info_pci_dev->pci_dev);
		error = EFBIG;
	}
	if (error) {
		device_printf(GET_DEV(accel_dev),
			      "Failed to enable MSI-X IRQ(s)\n");
		free(vectors, M_QAT);
		return error;
	}

	if (vectors != NULL) {
		error =
		    pci_remap_msix(info_pci_dev->pci_dev, num_vectors, vectors);
		free(vectors, M_QAT);
		if (error) {
			device_printf(GET_DEV(accel_dev),
				      "Failed to remap MSI-X IRQ(s)\n");
			pci_release_msi(info_pci_dev->pci_dev);
			return error;
		}
	}

	return 0;
}

static void
adf_disable_msix(struct adf_accel_pci *info_pci_dev)
{
	pci_release_msi(info_pci_dev->pci_dev);
}

static void
adf_msix_isr_bundle(void *bank_ptr)
{
	struct adf_etr_bank_data *bank = bank_ptr;
	struct adf_etr_data *priv_data = bank->accel_dev->transport;
	struct adf_hw_csr_ops *csr_ops = GET_CSR_OPS(bank->accel_dev);

	csr_ops->write_csr_int_flag_and_col(bank->csr_addr,
					    bank->bank_number,
					    0);
	adf_response_handler((uintptr_t)&priv_data->banks[bank->bank_number]);
	return;
}

static void
adf_msix_isr_ae(void *dev_ptr)
{
	struct adf_accel_dev *accel_dev = dev_ptr;
	struct adf_hw_device_data *hw_data = accel_dev->hw_device;
	struct adf_bar *pmisc =
	    &GET_BARS(accel_dev)[hw_data->get_misc_bar_id(hw_data)];
	struct resource *pmisc_bar_addr = pmisc->virt_addr;
	u32 errsou3;
	u32 errsou5;
	bool reset_required = false;

	if (hw_data->ras_interrupts &&
	    hw_data->ras_interrupts(accel_dev, &reset_required))
		if (reset_required) {
			adf_notify_fatal_error(accel_dev);
			goto exit;
		}

	if (hw_data->check_slice_hang && hw_data->check_slice_hang(accel_dev)) {
	}

exit:
	errsou3 = ADF_CSR_RD(pmisc_bar_addr, ADF_ERRSOU3);
	errsou5 = ADF_CSR_RD(pmisc_bar_addr, ADF_ERRSOU5);
	if (errsou3 | errsou5)
		adf_print_err_registers(accel_dev);
	else
		device_printf(GET_DEV(accel_dev), "spurious AE interrupt\n");

	return;
}

static int
adf_get_irq_affinity(struct adf_accel_dev *accel_dev, int bank)
{
	int core = CPU_FIRST();
	char val[ADF_CFG_MAX_VAL_LEN_IN_BYTES];
	char bankName[ADF_CFG_MAX_KEY_LEN_IN_BYTES];

	snprintf(bankName,
		 ADF_CFG_MAX_KEY_LEN_IN_BYTES - 1,
		 ADF_ETRMGR_CORE_AFFINITY_FORMAT,
		 bank);
	bankName[ADF_CFG_MAX_KEY_LEN_IN_BYTES - 1] = '\0';

	if (adf_cfg_get_param_value(accel_dev, "Accelerator0", bankName, val)) {
		device_printf(GET_DEV(accel_dev),
			      "No CoreAffinity Set - using default core: %d\n",
			      core);
	} else {
		if (compat_strtouint(val, 10, &core)) {
			device_printf(GET_DEV(accel_dev),
				      "Can't get cpu core ID\n");
		}
	}
	return (core);
}

static int
adf_request_irqs(struct adf_accel_dev *accel_dev)
{
	struct adf_accel_pci *info_pci_dev = &accel_dev->accel_pci_dev;
	struct adf_hw_device_data *hw_data = accel_dev->hw_device;
	struct msix_entry *msixe = info_pci_dev->msix_entries.entries;
	int ret = 0, rid = 0, i = 0;
	struct adf_etr_data *etr_data = accel_dev->transport;
	int computed_core = 0;

	/* Request msix irq for all banks unless SR-IOV enabled */
	if (!accel_dev->u1.pf.vf_info) {
		for (i = 0; i < hw_data->num_banks; i++) {
			struct adf_etr_bank_data *bank = &etr_data->banks[i];

			rid = i + 1;
			msixe[i].irq =
			    bus_alloc_resource_any(info_pci_dev->pci_dev,
						   SYS_RES_IRQ,
						   &rid,
						   RF_ACTIVE);
			if (msixe[i].irq == NULL) {
				device_printf(
				    GET_DEV(accel_dev),
				    "failed to allocate IRQ for bundle %d\n",
				    i);
				return ENXIO;
			}

			ret = bus_setup_intr(info_pci_dev->pci_dev,
					     msixe[i].irq,
					     INTR_TYPE_MISC | INTR_MPSAFE,
					     NULL,
					     adf_msix_isr_bundle,
					     bank,
					     &msixe[i].cookie);
			if (ret) {
				device_printf(
				    GET_DEV(accel_dev),
				    "failed to enable IRQ for bundle %d\n",
				    i);
				bus_release_resource(info_pci_dev->pci_dev,
						     SYS_RES_IRQ,
						     rid,
						     msixe[i].irq);
				msixe[i].irq = NULL;
				return ret;
			}

			computed_core = adf_get_irq_affinity(accel_dev, i);
			bus_describe_intr(info_pci_dev->pci_dev,
					  msixe[i].irq,
					  msixe[i].cookie,
					  "b%d",
					  i);
			bus_bind_intr(info_pci_dev->pci_dev,
				      msixe[i].irq,
				      computed_core);
		}
	}

	/* Request msix irq for AE */
	rid = hw_data->num_banks + 1;
	msixe[i].irq = bus_alloc_resource_any(info_pci_dev->pci_dev,
					      SYS_RES_IRQ,
					      &rid,
					      RF_ACTIVE);
	if (msixe[i].irq == NULL) {
		device_printf(GET_DEV(accel_dev),
			      "failed to allocate IRQ for ae-cluster\n");
		return ENXIO;
	}

	ret = bus_setup_intr(info_pci_dev->pci_dev,
			     msixe[i].irq,
			     INTR_TYPE_MISC | INTR_MPSAFE,
			     NULL,
			     adf_msix_isr_ae,
			     accel_dev,
			     &msixe[i].cookie);
	if (ret) {
		device_printf(GET_DEV(accel_dev),
			      "failed to enable IRQ for ae-cluster\n");
		bus_release_resource(info_pci_dev->pci_dev,
				     SYS_RES_IRQ,
				     rid,
				     msixe[i].irq);
		msixe[i].irq = NULL;
		return ret;
	}

	bus_describe_intr(info_pci_dev->pci_dev,
			  msixe[i].irq,
			  msixe[i].cookie,
			  "ae");
	return ret;
}

static void
adf_free_irqs(struct adf_accel_dev *accel_dev)
{
	struct adf_accel_pci *info_pci_dev = &accel_dev->accel_pci_dev;
	struct msix_entry *msixe = info_pci_dev->msix_entries.entries;
	int i = 0;

	if (info_pci_dev->msix_entries.num_entries > 0) {
		for (i = 0; i < info_pci_dev->msix_entries.num_entries; i++) {
			if (msixe[i].irq != NULL && msixe[i].cookie != NULL) {
				bus_teardown_intr(info_pci_dev->pci_dev,
						  msixe[i].irq,
						  msixe[i].cookie);
				bus_free_resource(info_pci_dev->pci_dev,
						  SYS_RES_IRQ,
						  msixe[i].irq);
			}
		}
	}
}

static int
adf_isr_alloc_msix_entry_table(struct adf_accel_dev *accel_dev)
{
	struct msix_entry *entries;
	u32 msix_num_entries = 1;

	struct adf_hw_device_data *hw_data = accel_dev->hw_device;
	/* If SR-IOV is disabled (vf_info is NULL), add entries for each bank */
	if (!accel_dev->u1.pf.vf_info)
		msix_num_entries += hw_data->num_banks;

	entries = malloc(msix_num_entries * sizeof(struct msix_entry),
			 M_QAT,
			 M_WAITOK | M_ZERO);

	accel_dev->accel_pci_dev.msix_entries.num_entries = msix_num_entries;
	accel_dev->accel_pci_dev.msix_entries.entries = entries;
	return 0;
}

static void
adf_isr_free_msix_entry_table(struct adf_accel_dev *accel_dev)
{

	free(accel_dev->accel_pci_dev.msix_entries.entries, M_QAT);
	accel_dev->accel_pci_dev.msix_entries.entries = NULL;
}

/**
 * adf_vf_isr_resource_free() - Free IRQ for acceleration device
 * @accel_dev:  Pointer to acceleration device.
 *
 * Function frees interrupts for acceleration device.
 */
void
adf_isr_resource_free(struct adf_accel_dev *accel_dev)
{
	adf_free_irqs(accel_dev);
	adf_disable_msix(&accel_dev->accel_pci_dev);
	adf_isr_free_msix_entry_table(accel_dev);
}

/**
 * adf_vf_isr_resource_alloc() - Allocate IRQ for acceleration device
 * @accel_dev:  Pointer to acceleration device.
 *
 * Function allocates interrupts for acceleration device.
 *
 * Return: 0 on success, error code otherwise.
 */
int
adf_isr_resource_alloc(struct adf_accel_dev *accel_dev)
{
	int ret;

	ret = adf_isr_alloc_msix_entry_table(accel_dev);
	if (ret)
		return ret;
	if (adf_enable_msix(accel_dev))
		goto err_out;

	if (adf_request_irqs(accel_dev))
		goto err_out;

	return 0;
err_out:
	adf_isr_resource_free(accel_dev);
	return EFAULT;
}