/*
 * SPDX-License-Identifier: Apache-2.0
 * Copyright (c) 2019 Intel Corp.
 */

#include <zephyr/kernel.h>
#include <zephyr/acpi/acpi.h>

static const char *get_dmar_scope_type(int type)
{
	switch (type) {
	case ACPI_DMAR_SCOPE_TYPE_ENDPOINT:
		return "PCI Endpoint";
	case ACPI_DMAR_SCOPE_TYPE_BRIDGE:
		return "PCI Sub-hierarchy";
	case ACPI_DMAR_SCOPE_TYPE_IOAPIC:
		return "IOAPIC";
	case ACPI_DMAR_SCOPE_TYPE_HPET:
		return "MSI Capable HPET";
	case ACPI_DMAR_SCOPE_TYPE_NAMESPACE:
		return "ACPI name-space enumerated";
	default:
		return "unknown";
	}
}

static void dmar_devsope_handler(ACPI_DMAR_DEVICE_SCOPE *devscope, void *arg)
{
	ARG_UNUSED(arg);

	printk("\t\t\t. Scope type %s\n", get_dmar_scope_type(devscope->EntryType));
	printk("\t\t\t. Enumeration ID %u\n", devscope->EnumerationId);

	if (devscope->EntryType < ACPI_DMAR_SCOPE_TYPE_RESERVED) {
		ACPI_DMAR_PCI_PATH *devpath;
		int num_path = (devscope->Length - 6u) / 2u;
		int i = 0;

		devpath = ACPI_ADD_PTR(ACPI_DMAR_PCI_PATH, devscope,
				       sizeof(ACPI_DMAR_DEVICE_SCOPE));

		while (num_path--) {
			printk("\t\t\t. PCI Path %02x:%02x.%02x\n", devscope->Bus,
			       devpath[i].Device, devpath[i].Function);
		}
	}
}

static void vtd_drhd_info(ACPI_DMAR_HEADER *subtable)
{
	struct acpi_dmar_hardware_unit *drhd = (void *)subtable;
	static int unit;

	printk("\t\t[ Hardware Unit Definition %d ]\n", unit++);

	if (drhd->Flags & ACPI_DRHD_FLAG_INCLUDE_PCI_ALL) {
		printk("\t\t- Includes all PCI devices");
	} else {
		printk("\t\t- Includes only listed PCI devices");
	}

	printk(" under given Segment\n");

	printk("\t\t- Segment number %u\n", drhd->Segment);
	printk("\t\t- Base Address 0x%llx\n", drhd->Address);

	printk("\t\t- Device Scopes:\n");

	acpi_dmar_foreach_devscope(drhd, dmar_devsope_handler, NULL);
}

static void dmar_subtable_handler(ACPI_DMAR_HEADER *subtable, void *arg)
{
	ARG_UNUSED(arg);

	if (subtable->Type != ACPI_DMAR_TYPE_HARDWARE_UNIT) {
		return;
	}

	vtd_drhd_info(subtable);
}

static void vtd_info(void)
{
	struct acpi_table_dmar *dmar;

	dmar = acpi_table_get("DMAR", 0);
	if (dmar == NULL) {
		printk("\tIntel VT-D not supported or exposed\n");
		return;
	}

	printk("\tIntel VT-D Supported:\n");

	printk("\t-> X2APIC ");
	if (dmar->Flags & ACPI_DMAR_FLAG_X2APIC_OPT_OUT) {
		printk("should be opted out\n");
	} else {
		printk("does not need to be opted out\n");
	}

	if (dmar->Flags & ACPI_DMAR_FLAG_INTR_REMAP) {
		printk("\t-> Interrupt remapping supported\n");
		acpi_dmar_foreach_subtable(dmar, dmar_subtable_handler, NULL);
	} else {
		printk("\t-> Interrupt remapping not supported\n");
	}
}

void acpi(void)
{
	int nr_cpus;

	for (nr_cpus = 0; acpi_local_apic_get(nr_cpus); ++nr_cpus) {
		/* count number of CPUs present */
	}

	if (nr_cpus == 0) {
		printk("ACPI: no RSDT/MADT found\n\n");
	} else {
		printk("ACPI: %d CPU%s found\n", nr_cpus, nr_cpus == 1 ? "" : "s");

		for (int i = 0; i < nr_cpus; ++i) {
			struct acpi_madt_local_apic *cpu = acpi_local_apic_get(i);

			printk("\tCPU #%d: APIC ID 0x%02x\n", i, cpu->Id);
		}
	}

	printk("\n");

	vtd_info();

	printk("\n");
}