/*
 * Copyright (c) 2020 Intel Corporation
 * SPDX-License-Identifier: Apache-2.0
 */

#define DT_DRV_COMPAT intel_vt_d

#include <errno.h>

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

#include <soc.h>
#include <zephyr/device.h>
#include <zephyr/init.h>
#include <string.h>


#include <zephyr/cache.h>

#include <zephyr/arch/x86/intel_vtd.h>
#include <zephyr/drivers/interrupt_controller/intel_vtd.h>
#include <zephyr/drivers/interrupt_controller/ioapic.h>
#include <zephyr/drivers/interrupt_controller/loapic.h>
#include <zephyr/drivers/pcie/msi.h>

#include <kernel_arch_func.h>

#include "intc_intel_vtd.h"

static inline void vtd_pause_cpu(void)
{
	__asm__ volatile("pause" ::: "memory");
}

static void vtd_write_reg32(const struct device *dev,
			    uint16_t reg, uint32_t value)
{
	uintptr_t base_address = DEVICE_MMIO_GET(dev);

	sys_write32(value, (base_address + reg));
}

static uint32_t vtd_read_reg32(const struct device *dev, uint16_t reg)
{
	uintptr_t base_address = DEVICE_MMIO_GET(dev);

	return sys_read32(base_address + reg);
}

static void vtd_write_reg64(const struct device *dev,
			    uint16_t reg, uint64_t value)
{
	uintptr_t base_address = DEVICE_MMIO_GET(dev);

	sys_write64(value, (base_address + reg));
}

static uint64_t vtd_read_reg64(const struct device *dev, uint16_t reg)
{
	uintptr_t base_address = DEVICE_MMIO_GET(dev);

	return sys_read64(base_address + reg);
}

static void vtd_send_cmd(const struct device *dev,
			 uint16_t cmd_bit, uint16_t status_bit)
{
	uintptr_t base_address = DEVICE_MMIO_GET(dev);
	uint32_t value;

	value = vtd_read_reg32(dev, VTD_GSTS_REG);
	value |= BIT(cmd_bit);

	vtd_write_reg32(dev, VTD_GCMD_REG, value);

	while (!sys_test_bit((base_address + VTD_GSTS_REG),
			     status_bit)) {
		/* Do nothing */
	}
}

static void vtd_flush_irte_from_cache(const struct device *dev,
				      uint8_t irte_idx)
{
	struct vtd_ictl_data *data = dev->data;

	if (!data->pwc) {
		cache_data_flush_range(&data->irte[irte_idx],
				       sizeof(union vtd_irte));
	}
}

static void vtd_qi_init(const struct device *dev)
{
	struct vtd_ictl_data *data = dev->data;
	uint64_t value;

	vtd_write_reg64(dev, VTD_IQT_REG, 0);
	data->qi_tail = 0;

	value = VTD_IQA_REG_GEN_CONTENT((uintptr_t)data->qi,
					VTD_IQA_WIDTH_128_BIT, QI_SIZE);
	vtd_write_reg64(dev, VTD_IQA_REG, value);

	vtd_send_cmd(dev, VTD_GCMD_QIE, VTD_GSTS_QIES);
}

static inline void vtd_qi_tail_inc(const struct device *dev)
{
	struct vtd_ictl_data *data = dev->data;

	data->qi_tail += sizeof(struct qi_descriptor);
	data->qi_tail %= (QI_NUM * sizeof(struct qi_descriptor));
}

static int vtd_qi_send(const struct device *dev,
		       struct qi_descriptor *descriptor)
{
	struct vtd_ictl_data *data = dev->data;
	union qi_wait_descriptor wait_desc = { 0 };
	struct qi_descriptor *desc;
	uint32_t wait_status;
	uint32_t wait_count;

	desc = (struct qi_descriptor *)((uintptr_t)data->qi + data->qi_tail);

	desc->low = descriptor->low;
	desc->high = descriptor->high;

	vtd_qi_tail_inc(dev);

	desc++;

	wait_status = QI_WAIT_STATUS_INCOMPLETE;

	wait_desc.wait.type = QI_TYPE_WAIT;
	wait_desc.wait.status_write = 1;
	wait_desc.wait.status_data = QI_WAIT_STATUS_COMPLETE;
	wait_desc.wait.address = ((uintptr_t)&wait_status) >> 2;

	desc->low = wait_desc.desc.low;
	desc->high = wait_desc.desc.high;

	vtd_qi_tail_inc(dev);

	vtd_write_reg64(dev, VTD_IQT_REG, data->qi_tail);

	wait_count = 0;

	while (wait_status != QI_WAIT_STATUS_COMPLETE) {
		/* We cannot use timeout here, this function being called
		 * at init time, it might result that the system clock
		 * is not initialized yet since VT-D init comes first.
		 */
		if (wait_count > QI_WAIT_COUNT_LIMIT) {
			printk("QI timeout\n");
			return -ETIME;
		}

		if (vtd_read_reg32(dev, VTD_FSTS_REG) & VTD_FSTS_IQE) {
			printk("QI error\n");
			return -EIO;
		}

		vtd_pause_cpu();
		wait_count++;
	}

	return 0;
}

static int vtd_global_cc_invalidate(const struct device *dev)
{
	union qi_icc_descriptor iec_desc = { 0 };

	iec_desc.icc.type = QI_TYPE_ICC;
	iec_desc.icc.granularity = 1; /* Global Invalidation requested */

	return vtd_qi_send(dev, &iec_desc.desc);
}

static int vtd_global_iec_invalidate(const struct device *dev)
{
	union qi_iec_descriptor iec_desc = { 0 };

	iec_desc.iec.type = QI_TYPE_IEC;
	iec_desc.iec.granularity = 0; /* Global Invalidation requested */

	return vtd_qi_send(dev, &iec_desc.desc);
}

static int vtd_index_iec_invalidate(const struct device *dev, uint8_t irte_idx)
{
	union qi_iec_descriptor iec_desc = { 0 };

	iec_desc.iec.type = QI_TYPE_IEC;
	iec_desc.iec.granularity = 1; /* Index based invalidation requested */

	iec_desc.iec.interrupt_index = irte_idx;
	iec_desc.iec.index_mask = 0;

	return vtd_qi_send(dev, &iec_desc.desc);
}

static void fault_status_description(uint32_t status)
{
	if (status & VTD_FSTS_PFO) {
		printk("Primary Fault Overflow (PFO)\n");
	}

	if (status & VTD_FSTS_AFO) {
		printk("Advanced Fault Overflow (AFO)\n");
	}

	if (status & VTD_FSTS_APF) {
		printk("Advanced Primary Fault (APF)\n");
	}

	if (status & VTD_FSTS_IQE) {
		printk("Invalidation Queue Error (IQE)\n");
	}

	if (status & VTD_FSTS_ICE) {
		printk("Invalidation Completion Error (ICE)\n");
	}

	if (status & VTD_FSTS_ITE) {
		printk("Invalidation Timeout Error\n");
	}

	if (status & VTD_FSTS_PPF) {
		printk("Primary Pending Fault (PPF) %u\n",
		       VTD_FSTS_FRI(status));
	}
}

static void fault_record_description(uint64_t low, uint64_t high)
{
	printk("Fault %s request: Reason 0x%x info 0x%llx src 0x%x\n",
	       (high & VTD_FRCD_T) ? "Read/Atomic" : "Write/Page",
	       VTD_FRCD_FR(high), VTD_FRCD_FI(low), VTD_FRCD_SID(high));
}

static void fault_event_isr(const void *arg)
{
	const struct device *dev = arg;
	struct vtd_ictl_data *data = dev->data;
	uint32_t status;
	uint8_t f_idx;

	status = vtd_read_reg32(dev, VTD_FSTS_REG);
	fault_status_description(status);

	if (!(status & VTD_FSTS_PPF)) {
		goto out;
	}

	f_idx = VTD_FSTS_FRI(status);
	while (f_idx < data->fault_record_num) {
		uint64_t fault_l, fault_h;

		/* Reading fault's 64 lowest bits */
		fault_l = vtd_read_reg64(dev, data->fault_record_reg +
					 (VTD_FRCD_REG_SIZE * f_idx));
		/* Reading fault's 64 highest bits */
		fault_h = vtd_read_reg64(dev, data->fault_record_reg +
				       (VTD_FRCD_REG_SIZE * f_idx) + 8);

		if (fault_h & VTD_FRCD_F) {
			fault_record_description(fault_l, fault_h);
		}

		/* Clearing the fault */
		vtd_write_reg64(dev, data->fault_record_reg +
				(VTD_FRCD_REG_SIZE * f_idx), fault_l);
		vtd_write_reg64(dev, data->fault_record_reg +
				(VTD_FRCD_REG_SIZE * f_idx) + 8, fault_h);
		f_idx++;
	}
out:
	/* Clearing fault status */
	vtd_write_reg32(dev, VTD_FSTS_REG, VTD_FSTS_CLEAR(status));
}

static void vtd_fault_event_init(const struct device *dev)
{
	struct vtd_ictl_data *data = dev->data;
	uint64_t value;
	uint32_t reg;

	value = vtd_read_reg64(dev, VTD_CAP_REG);
	data->fault_record_num = VTD_CAP_NFR(value) + 1;
	data->fault_record_reg = DEVICE_MMIO_GET(dev) +
		(uintptr_t)(16 * VTD_CAP_FRO(value));

	/* Allocating IRQ & vector and connecting the ISR handler,
	 * by-passing remapping by using x86 functions directly.
	 */
	data->fault_irq = arch_irq_allocate();
	data->fault_vector = z_x86_allocate_vector(0, -1);

	vtd_write_reg32(dev, VTD_FEDATA_REG, data->fault_vector);
	vtd_write_reg32(dev, VTD_FEADDR_REG,
			pcie_msi_map(data->fault_irq, NULL, 0));
	vtd_write_reg32(dev, VTD_FEUADDR_REG, 0);

	z_x86_irq_connect_on_vector(data->fault_irq, data->fault_vector,
				    fault_event_isr, dev);

	vtd_write_reg32(dev, VTD_FSTS_REG,
			VTD_FSTS_CLEAR(vtd_read_reg32(dev, VTD_FSTS_REG)));

	/* Unmasking interrupts */
	reg = vtd_read_reg32(dev, VTD_FECTL_REG);
	reg &= ~BIT(VTD_FECTL_REG_IM);
	vtd_write_reg32(dev, VTD_FECTL_REG, reg);
}

static int vtd_ictl_allocate_entries(const struct device *dev,
				     uint8_t n_entries)
{
	struct vtd_ictl_data *data = dev->data;
	int irte_idx_start;

	if ((data->irte_num_used + n_entries) > IRTE_NUM) {
		return -EBUSY;
	}

	irte_idx_start = data->irte_num_used;
	data->irte_num_used += n_entries;

	return irte_idx_start;
}

static uint32_t vtd_ictl_remap_msi(const struct device *dev,
				   msi_vector_t *vector,
				   uint8_t n_vector)
{
	uint32_t shv = (n_vector > 1) ? VTD_INT_SHV : 0;

	return VTD_MSI_MAP(vector->arch.irte, shv);
}

static int vtd_ictl_remap(const struct device *dev,
			  uint8_t irte_idx,
			  uint16_t vector,
			  uint32_t flags,
			  uint16_t src_id)
{
	struct vtd_ictl_data *data = dev->data;
	union vtd_irte irte = { 0 };
	uint32_t delivery_mode;

	irte.bits.vector = vector;

	if (IS_ENABLED(CONFIG_X2APIC)) {
		/* Getting the logical APIC ID */
		irte.bits.dst_id = x86_read_loapic(LOAPIC_LDR);
	} else {
		/* As for IOAPIC: let's mask all possible IDs */
		irte.bits.dst_id = 0xFF << 8;
	}

	if (src_id != USHRT_MAX &&
	    !IS_ENABLED(CONFIG_INTEL_VTD_ICTL_NO_SRC_ID_CHECK)) {
		irte.bits.src_validation_type = 1;
		irte.bits.src_id = src_id;
	}

	delivery_mode = (flags & IOAPIC_DELIVERY_MODE_MASK);
	if ((delivery_mode != IOAPIC_FIXED) ||
	    (delivery_mode != IOAPIC_LOW)) {
		delivery_mode = IOAPIC_LOW;
	}

	irte.bits.trigger_mode = (flags & IOAPIC_TRIGGER_MASK) >> 15;
	irte.bits.delivery_mode = delivery_mode >> 8;
	irte.bits.redirection_hint = 1;
	irte.bits.dst_mode = 1; /* Always logical */
	irte.bits.present = 1;

	data->irte[irte_idx].parts.low = irte.parts.low;
	data->irte[irte_idx].parts.high = irte.parts.high;

	vtd_index_iec_invalidate(dev, irte_idx);

	vtd_flush_irte_from_cache(dev, irte_idx);

	return 0;
}

static int vtd_ictl_set_irte_vector(const struct device *dev,
				    uint8_t irte_idx,
				    uint16_t vector)
{
	struct vtd_ictl_data *data = dev->data;

	data->vectors[irte_idx] = vector;

	return 0;
}

static int vtd_ictl_get_irte_by_vector(const struct device *dev,
				       uint16_t vector)
{
	struct vtd_ictl_data *data = dev->data;
	int irte_idx;

	for (irte_idx = 0; irte_idx < IRTE_NUM; irte_idx++) {
		if (data->vectors[irte_idx] == vector) {
			return irte_idx;
		}
	}

	return -EINVAL;
}

static uint16_t vtd_ictl_get_irte_vector(const struct device *dev,
					 uint8_t irte_idx)
{
	struct vtd_ictl_data *data = dev->data;

	return data->vectors[irte_idx];
}

static int vtd_ictl_set_irte_irq(const struct device *dev,
				 uint8_t irte_idx,
				 unsigned int irq)
{
	struct vtd_ictl_data *data = dev->data;

	data->irqs[irte_idx] = irq;

	return 0;
}

static int vtd_ictl_get_irte_by_irq(const struct device *dev,
				    unsigned int irq)
{
	struct vtd_ictl_data *data = dev->data;
	int irte_idx;

	for (irte_idx = 0; irte_idx < IRTE_NUM; irte_idx++) {
		if (data->irqs[irte_idx] == irq) {
			return irte_idx;
		}
	}

	return -EINVAL;
}

static void vtd_ictl_set_irte_msi(const struct device *dev,
				  uint8_t irte_idx, bool msi)
{
	struct vtd_ictl_data *data = dev->data;

	data->msi[irte_idx] = msi;
}

static bool vtd_ictl_irte_is_msi(const struct device *dev,
				 uint8_t irte_idx)
{
	struct vtd_ictl_data *data = dev->data;

	return data->msi[irte_idx];
}

static int vtd_ictl_init(const struct device *dev)
{
	struct vtd_ictl_data *data = dev->data;
	unsigned int key = irq_lock();
	uint64_t eime = 0;
	uint64_t value;
	int ret = 0;

	DEVICE_MMIO_MAP(dev, K_MEM_CACHE_NONE);

	if (vtd_read_reg64(dev, VTD_ECAP_REG) & VTD_ECAP_C) {
		printk("Page walk coherency supported\n");
		data->pwc = true;
	}

	vtd_fault_event_init(dev);

	vtd_qi_init(dev);

	if (vtd_global_cc_invalidate(dev) != 0) {
		printk("Could not perform ICC invalidation\n");
		ret = -EIO;
		goto out;
	}

	if (IS_ENABLED(CONFIG_X2APIC)) {
		eime = VTD_IRTA_EIME;
	}

	value = VTD_IRTA_REG_GEN_CONTENT((uintptr_t)data->irte,
					 IRTA_SIZE, eime);

	vtd_write_reg64(dev, VTD_IRTA_REG, value);

	if (vtd_global_iec_invalidate(dev) != 0) {
		printk("Could not perform IEC invalidation\n");
		ret = -EIO;
		goto out;
	}

	if (!IS_ENABLED(CONFIG_X2APIC) &&
	    IS_ENABLED(CONFIG_INTEL_VTD_ICTL_XAPIC_PASSTHROUGH)) {
		vtd_send_cmd(dev, VTD_GCMD_CFI, VTD_GSTS_CFIS);
	}

	vtd_send_cmd(dev, VTD_GCMD_SIRTP, VTD_GSTS_SIRTPS);
	vtd_send_cmd(dev, VTD_GCMD_IRE, VTD_GSTS_IRES);

	printk("Intel VT-D up and running (status 0x%x)\n",
	       vtd_read_reg32(dev, VTD_GSTS_REG));

out:
	irq_unlock(key);

	return ret;
}

static const struct vtd_driver_api vtd_api = {
	.allocate_entries = vtd_ictl_allocate_entries,
	.remap_msi = vtd_ictl_remap_msi,
	.remap = vtd_ictl_remap,
	.set_irte_vector = vtd_ictl_set_irte_vector,
	.get_irte_by_vector = vtd_ictl_get_irte_by_vector,
	.get_irte_vector = vtd_ictl_get_irte_vector,
	.set_irte_irq = vtd_ictl_set_irte_irq,
	.get_irte_by_irq = vtd_ictl_get_irte_by_irq,
	.set_irte_msi = vtd_ictl_set_irte_msi,
	.irte_is_msi = vtd_ictl_irte_is_msi
};

static struct vtd_ictl_data vtd_ictl_data_0 = {
	.irqs = { -EINVAL },
	.vectors = { -EINVAL },
};

static const struct vtd_ictl_cfg vtd_ictl_cfg_0 = {
	DEVICE_MMIO_ROM_INIT(DT_DRV_INST(0)),
};

DEVICE_DT_INST_DEFINE(0,
	      vtd_ictl_init, NULL,
	      &vtd_ictl_data_0, &vtd_ictl_cfg_0,
	      PRE_KERNEL_1, CONFIG_INTEL_VTD_ICTL_INIT_PRIORITY, &vtd_api);
