/*
 * Copyright (c) 1997-1998, 2000-2002, 2004, 2006-2008, 2011-2015 Wind River
 * Systems, Inc.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#define DT_DRV_COMPAT intel_ioapic

/**
 * @file
 * @brief Intel IO APIC/xAPIC driver
 *
 * This module is a driver for the IO APIC/xAPIC (Advanced Programmable
 * Interrupt Controller) for P6 (PentiumPro, II, III) family processors
 * and P7 (Pentium4) family processors.  The IO APIC/xAPIC is included
 * in the Intel's system chip set, such as ICH2.  Software intervention
 * may be required to enable the IO APIC/xAPIC in some chip sets.
 * The 8259A interrupt controller is intended for use in a uni-processor
 * system, IO APIC can be used in either a uni-processor or multi-processor
 * system.  The IO APIC handles interrupts very differently than the 8259A.
 * Briefly, these differences are:
 *  - Method of Interrupt Transmission. The IO APIC transmits interrupts
 *    through a 3-wire bus and interrupts are handled without the need for
 *    the processor to run an interrupt acknowledge cycle.
 *  - Interrupt Priority. The priority of interrupts in the IO APIC is
 *    independent of the interrupt number.  For example, interrupt 10 can
 *    be given a higher priority than interrupt 3.
 *  - More Interrupts. The IO APIC supports a total of 24 interrupts.
 *
 * The IO APIC unit consists of a set of interrupt input signals, a 24-entry
 * by 64-bit Interrupt Redirection Table, programmable registers, and a message
 * unit for sending and receiving APIC messages over the APIC bus or the
 * Front-Side (system) bus.  IO devices inject interrupts into the system by
 * asserting one of the interrupt lines to the IO APIC.  The IO APIC selects the
 * corresponding entry in the Redirection Table and uses the information in that
 * entry to format an interrupt request message.  Each entry in the Redirection
 * Table can be individually programmed to indicate edge/level sensitive interrupt
 * signals, the interrupt vector and priority, the destination processor, and how
 * the processor is selected (statically and dynamically).  The information in
 * the table is used to transmit a message to other APIC units (via the APIC bus
 * or the Front-Side (system) bus).  IO APIC is used in the Symmetric IO Mode.
 * The base address of IO APIC is determined in loapic_init() and stored in the
 * global variable ioApicBase and ioApicData.
 * The lower 32 bit value of the redirection table entries for IRQ 0
 * to 15 are edge triggered positive high, and for IRQ 16 to 23 are level
 * triggered positive low.
 *
 * This implementation doesn't support multiple IO APICs.
 *
 * INCLUDE FILES: ioapic.h loapic.h
 *
 */

#include <zephyr/kernel.h>
#include <zephyr/arch/cpu.h>

#include <zephyr/toolchain.h>
#include <zephyr/linker/sections.h>
#include <zephyr/device.h>
#include <zephyr/pm/device.h>
#include <string.h>

#include <zephyr/drivers/interrupt_controller/ioapic.h> /* public API declarations */
#include <zephyr/drivers/interrupt_controller/loapic.h> /* public API declarations and registers */
#include "intc_ioapic_priv.h"

DEVICE_MMIO_TOPLEVEL_STATIC(ioapic_regs, DT_DRV_INST(0));

#define IOAPIC_REG DEVICE_MMIO_TOPLEVEL_GET(ioapic_regs)

/*
 * Destination field (bits[56:63]) defines a set of processors, which is
 * used to be compared with local LDR to determine which local APICs accept
 * the interrupt.
 *
 * XAPIC: in logical destination mode and flat model (determined by DFR).
 * LDR bits[24:31] can accommodate up to 8 logical APIC IDs.
 *
 * X2APIC: in logical destination mode and cluster model.
 * In this case, LDR is read-only to system software and supports up to 16
 * logical IDs. (Cluster ID: don't care to IO APIC).
 *
 * In either case, regardless how many CPUs in the system, 0xff implies that
 * it's intended to deliver to all possible 8 local APICs.
 */
#define DEFAULT_RTE_DEST	(0xFF << 24)

static __pinned_bss uint32_t ioapic_rtes;

#ifdef CONFIG_PM_DEVICE

#define BITS_PER_IRQ  4
#define IOAPIC_BITFIELD_HI_LO	0
#define IOAPIC_BITFIELD_LVL_EDGE 1
#define IOAPIC_BITFIELD_ENBL_DSBL 2
#define IOAPIC_BITFIELD_DELIV_MODE 3

#define BIT_POS_FOR_IRQ_OPTION(irq, option) ((irq) * BITS_PER_IRQ + (option))

/* Allocating up to 256 irq bits bufffer for RTEs, RTEs are dynamically found
 * so let's just assume the maximum, it's only 128 bytes in total.
 */
#define SUSPEND_BITS_REQD (ROUND_UP((256 * BITS_PER_IRQ), 32))

__pinned_bss
uint32_t ioapic_suspend_buf[SUSPEND_BITS_REQD / 32] = {0};
#endif

static uint32_t __IoApicGet(int32_t offset);
static void __IoApicSet(int32_t offset, uint32_t value);
static void ioApicRedSetHi(unsigned int irq, uint32_t upper32);
static void ioApicRedSetLo(unsigned int irq, uint32_t lower32);
static uint32_t ioApicRedGetLo(unsigned int irq);
static void IoApicRedUpdateLo(unsigned int irq, uint32_t value,
					uint32_t mask);

#if defined(CONFIG_INTEL_VTD_ICTL) &&				\
	!defined(CONFIG_INTEL_VTD_ICTL_XAPIC_PASSTHROUGH)

#include <zephyr/drivers/interrupt_controller/intel_vtd.h>
#include <zephyr/acpi/acpi.h>

static const struct device *const vtd =
	DEVICE_DT_GET_OR_NULL(DT_INST(0, intel_vt_d));
static uint16_t ioapic_id;

static bool get_vtd(void)
{
	if (!device_is_ready(vtd)) {
		return false;
	}

	if (ioapic_id != 0) {
		return true;
	}

	return acpi_dmar_ioapic_get(&ioapic_id) == 0;
}
#endif /* CONFIG_INTEL_VTD_ICTL && !INTEL_VTD_ICTL_XAPIC_PASSTHROUGH */

/*
 * The functions irq_enable() and irq_disable() are implemented in the
 * interrupt controller driver due to the IRQ virtualization imposed by
 * the x86 architecture.
 */

/**
 * @brief Initialize the IO APIC or xAPIC
 *
 * This routine initializes the IO APIC or xAPIC.
 *
 * @retval 0 on success.
 */
__boot_func
int ioapic_init(const struct device *unused)
{
	ARG_UNUSED(unused);

	DEVICE_MMIO_TOPLEVEL_MAP(ioapic_regs, K_MEM_CACHE_NONE);

	/* Reading MRE: this will give the number of RTEs available */
	ioapic_rtes = ((__IoApicGet(IOAPIC_VERS) &
			IOAPIC_MRE_MASK) >> IOAPIC_MRE_POS) + 1;

#ifdef CONFIG_IOAPIC_MASK_RTE
	int32_t ix;	/* redirection table index */
	uint32_t rteValue; /* value to copy into redirection table entry */

	rteValue = IOAPIC_EDGE | IOAPIC_HIGH | IOAPIC_FIXED | IOAPIC_INT_MASK |
		   IOAPIC_LOGICAL | 0 /* dummy vector */;

	for (ix = 0; ix < ioapic_rtes; ix++) {
		ioApicRedSetHi(ix, DEFAULT_RTE_DEST);
		ioApicRedSetLo(ix, rteValue);
	}
#endif
	return 0;
}

__pinned_func
uint32_t z_ioapic_num_rtes(void)
{
	return ioapic_rtes;
}

/**
 * @brief Enable a specified APIC interrupt input line
 *
 * This routine enables a specified APIC interrupt input line.
 *
 * @param irq IRQ number to enable
 */
__pinned_func
void z_ioapic_irq_enable(unsigned int irq)
{
	IoApicRedUpdateLo(irq, 0, IOAPIC_INT_MASK);
}

/**
 * @brief Disable a specified APIC interrupt input line
 *
 * This routine disables a specified APIC interrupt input line.
 * @param irq IRQ number to disable
 */
__pinned_func
void z_ioapic_irq_disable(unsigned int irq)
{
	IoApicRedUpdateLo(irq, IOAPIC_INT_MASK, IOAPIC_INT_MASK);
}


#ifdef CONFIG_PM_DEVICE

__pinned_func
void store_flags(unsigned int irq, uint32_t flags)
{
	/* Currently only the following four flags are modified */
	if (flags & IOAPIC_LOW) {
		sys_bitfield_set_bit((mem_addr_t) ioapic_suspend_buf,
			BIT_POS_FOR_IRQ_OPTION(irq, IOAPIC_BITFIELD_HI_LO));
	}

	if (flags & IOAPIC_LEVEL) {
		sys_bitfield_set_bit((mem_addr_t) ioapic_suspend_buf,
			BIT_POS_FOR_IRQ_OPTION(irq, IOAPIC_BITFIELD_LVL_EDGE));
	}

	if (flags & IOAPIC_INT_MASK) {
		sys_bitfield_set_bit((mem_addr_t) ioapic_suspend_buf,
			BIT_POS_FOR_IRQ_OPTION(irq, IOAPIC_BITFIELD_ENBL_DSBL));
	}

	/*
	 * We support lowest priority and fixed mode only, so only one bit
	 * needs to be saved.
	 */
	if (flags & IOAPIC_LOWEST) {
		sys_bitfield_set_bit((mem_addr_t) ioapic_suspend_buf,
			BIT_POS_FOR_IRQ_OPTION(irq, IOAPIC_BITFIELD_DELIV_MODE));
	}
}

__pinned_func
uint32_t restore_flags(unsigned int irq)
{
	uint32_t flags = 0U;

	if (sys_bitfield_test_bit((mem_addr_t) ioapic_suspend_buf,
		BIT_POS_FOR_IRQ_OPTION(irq, IOAPIC_BITFIELD_HI_LO))) {
		flags |= IOAPIC_LOW;
	}

	if (sys_bitfield_test_bit((mem_addr_t) ioapic_suspend_buf,
		BIT_POS_FOR_IRQ_OPTION(irq, IOAPIC_BITFIELD_LVL_EDGE))) {
		flags |= IOAPIC_LEVEL;
	}

	if (sys_bitfield_test_bit((mem_addr_t) ioapic_suspend_buf,
		BIT_POS_FOR_IRQ_OPTION(irq, IOAPIC_BITFIELD_ENBL_DSBL))) {
		flags |= IOAPIC_INT_MASK;
	}

	if (sys_bitfield_test_bit((mem_addr_t) ioapic_suspend_buf,
		BIT_POS_FOR_IRQ_OPTION(irq, IOAPIC_BITFIELD_DELIV_MODE))) {
		flags |= IOAPIC_LOWEST;
	}

	return flags;
}


__pinned_func
int ioapic_suspend(const struct device *port)
{
	int irq;
	uint32_t rte_lo;

	ARG_UNUSED(port);
	(void)memset(ioapic_suspend_buf, 0, (SUSPEND_BITS_REQD >> 3));
	for (irq = 0; irq < ioapic_rtes; irq++) {
		/*
		 * The following check is to figure out the registered
		 * IRQ lines, so as to limit ourselves to saving the
		 *  flags for them only.
		 */
		if (_irq_to_interrupt_vector[irq]) {
			rte_lo = ioApicRedGetLo(irq);
			store_flags(irq, rte_lo);
		}
	}
	return 0;
}

__pinned_func
int ioapic_resume_from_suspend(const struct device *port)
{
	int irq;
	uint32_t flags;
	uint32_t rteValue;

	ARG_UNUSED(port);

	for (irq = 0; irq < ioapic_rtes; irq++) {
		if (_irq_to_interrupt_vector[irq]) {
			/* Get the saved flags */
			flags = restore_flags(irq);
			/* Appending the flags that are never modified */
			flags = flags | IOAPIC_LOGICAL;

			rteValue = (_irq_to_interrupt_vector[irq] &
					IOAPIC_VEC_MASK) | flags;
		} else {
			/* Initialize the other RTEs to sane values */
			rteValue = IOAPIC_EDGE | IOAPIC_HIGH |
				IOAPIC_FIXED | IOAPIC_INT_MASK |
				IOAPIC_LOGICAL | 0 ; /* dummy vector*/
		}
		ioApicRedSetHi(irq, DEFAULT_RTE_DEST);
		ioApicRedSetLo(irq, rteValue);
	}
	return 0;
}

/*
* Implements the driver control management functionality
* the *context may include IN data or/and OUT data
*/
__pinned_func
static int ioapic_pm_action(const struct device *dev,
			    enum pm_device_action action)
{
	int ret = 0;

	switch (action) {
	case PM_DEVICE_ACTION_RESUME:
		ret = ioapic_resume_from_suspend(dev);
		break;
	case PM_DEVICE_ACTION_SUSPEND:
		ret = ioapic_suspend(dev);
		break;
	default:
		ret = -ENOTSUP;
	}

	return ret;
}

#endif  /*CONFIG_PM_DEVICE*/

/**
 * @brief Programs the interrupt redirection table
 *
 * This routine sets up the redirection table entry for the specified IRQ
 * @param irq Virtualized IRQ
 * @param vector Vector number
 * @param flags Interrupt flags
 */
__boot_func
void z_ioapic_irq_set(unsigned int irq, unsigned int vector, uint32_t flags)
{
	uint32_t rteValue;   /* value to copy into redirection table entry */
#if defined(CONFIG_INTEL_VTD_ICTL) &&				\
	!defined(CONFIG_INTEL_VTD_ICTL_XAPIC_PASSTHROUGH)
	int irte_idx;

	if (!get_vtd()) {
		goto no_vtd;
	}

	irte_idx = vtd_get_irte_by_vector(vtd, vector);
	if (irte_idx < 0) {
		irte_idx = vtd_get_irte_by_irq(vtd, irq);
	}

	if (irte_idx >= 0 && !vtd_irte_is_msi(vtd, irte_idx)) {
		/* Enable interrupt remapping format and set the irte index */
		rteValue = IOAPIC_VTD_REMAP_FORMAT |
			IOAPIC_VTD_INDEX(irte_idx);
		ioApicRedSetHi(irq, rteValue);

		/* Remapped: delivery mode is Fixed (000) and
		 * destination mode is no longer present as it is replaced by
		 * the 15th bit of irte index, which is always 0 in our case.
		 */
		rteValue = IOAPIC_INT_MASK |
			(vector & IOAPIC_VEC_MASK) |
			(flags & IOAPIC_TRIGGER_MASK) |
			(flags & IOAPIC_POLARITY_MASK);
		ioApicRedSetLo(irq, rteValue);

		vtd_remap(vtd, irte_idx, vector, flags, ioapic_id);
	} else {
no_vtd:
#else
	{
#endif /* CONFIG_INTEL_VTD_ICTL && !CONFIG_INTEL_VTD_ICTL_XAPIC_PASSTHROUGH */
		/* the delivery mode is determined by the flags
		 * passed from drivers
		 */
		rteValue = IOAPIC_INT_MASK | IOAPIC_LOGICAL |
			(vector & IOAPIC_VEC_MASK) | flags;
		ioApicRedSetHi(irq, DEFAULT_RTE_DEST);
		ioApicRedSetLo(irq, rteValue);
	}
}

/**
 * @brief Program interrupt vector for specified irq
 *
 * The routine writes the interrupt vector in the Interrupt Redirection
 * Table for specified irq number
 *
 * @param irq Interrupt number
 * @param vector Vector number
 */
__boot_func
void z_ioapic_int_vec_set(unsigned int irq, unsigned int vector)
{
	IoApicRedUpdateLo(irq, vector, IOAPIC_VEC_MASK);
}

/**
 * @brief Read a 32 bit IO APIC register
 *
 * This routine reads the specified IO APIC register using indirect addressing.
 * @param offset Register offset (8 bits)
 *
 * @return register value
 */
__pinned_func
static uint32_t __IoApicGet(int32_t offset)
{
	uint32_t value; /* value */
	unsigned int key;	/* interrupt lock level */

	/* lock interrupts to ensure indirect addressing works "atomically" */

	key = irq_lock();

	*((volatile uint32_t *) (IOAPIC_REG + IOAPIC_IND)) = (unsigned char)offset;
	value = *((volatile uint32_t *)(IOAPIC_REG + IOAPIC_DATA));

	irq_unlock(key);

	return value;
}

/**
 * @brief Write a 32 bit IO APIC register
 *
 * This routine writes the specified IO APIC register using indirect addressing.
 *
 * @param offset Register offset (8 bits)
 * @param value Value to set the register
 */
__pinned_func
static void __IoApicSet(int32_t offset, uint32_t value)
{
	unsigned int key; /* interrupt lock level */

	/* lock interrupts to ensure indirect addressing works "atomically" */

	key = irq_lock();

	*(volatile uint32_t *)(IOAPIC_REG + IOAPIC_IND) = (unsigned char)offset;
	*((volatile uint32_t *)(IOAPIC_REG + IOAPIC_DATA)) = value;

	irq_unlock(key);
}

/**
 * @brief Get low 32 bits of Redirection Table entry
 *
 * This routine reads the low-order 32 bits of a Redirection Table entry.
 *
 * @param irq INTIN number
 * @return 32 low-order bits
 */
__pinned_func
static uint32_t ioApicRedGetLo(unsigned int irq)
{
	int32_t offset = IOAPIC_REDTBL + (irq << 1); /* register offset */

	return __IoApicGet(offset);
}

/**
 * @brief Set low 32 bits of Redirection Table entry
 *
 * This routine writes the low-order 32 bits of a Redirection Table entry.
 *
 * @param irq INTIN number
 * @param lower32 Value to be written
 */
__pinned_func
static void ioApicRedSetLo(unsigned int irq, uint32_t lower32)
{
	int32_t offset = IOAPIC_REDTBL + (irq << 1); /* register offset */

	__IoApicSet(offset, lower32);
}

/**
 * @brief Set high 32 bits of Redirection Table entry
 *
 * This routine writes the high-order 32 bits of a Redirection Table entry.
 *
 * @param irq INTIN number
 * @param upper32 Value to be written
 */
__pinned_func
static void ioApicRedSetHi(unsigned int irq, uint32_t upper32)
{
	int32_t offset = IOAPIC_REDTBL + (irq << 1) + 1; /* register offset */

	__IoApicSet(offset, upper32);
}

/**
 * @brief Modify low 32 bits of Redirection Table entry
 *
 * This routine modifies selected portions of the low-order 32 bits of a
 * Redirection Table entry, as indicated by the associate bit mask.
 *
 * @param irq INTIN number
 * @param value Value to be written
 * @param mask  Mask of bits to be modified
 */
__pinned_func
static void IoApicRedUpdateLo(unsigned int irq,
				uint32_t value,
				uint32_t mask)
{
	ioApicRedSetLo(irq, (ioApicRedGetLo(irq) & ~mask) | (value & mask));
}

PM_DEVICE_DT_INST_DEFINE(0, ioapic_pm_action);

DEVICE_DT_INST_DEFINE(0, ioapic_init, PM_DEVICE_DT_INST_GET(0), NULL, NULL,
		      PRE_KERNEL_1, CONFIG_INTC_INIT_PRIORITY, NULL);
