/* SPDX-License-Identifier: GPL-2.0-only */

#include <acpi/acpi.h>
#include <arch/ioapic.h>
#include <assert.h>
#include <cpu/cpu.h>
#include <lib.h>
#include <version.h>

void acpi_create_dmar(acpi_dmar_t *dmar, enum dmar_flags flags,
		      unsigned long (*acpi_fill_dmar)(unsigned long))
{
	acpi_header_t *header = &(dmar->header);
	unsigned long current = (unsigned long)dmar + sizeof(acpi_dmar_t);

	memset((void *)dmar, 0, sizeof(acpi_dmar_t));

	if (!header)
		return;

	/* Fill out header fields. */
	memcpy(header->signature, "DMAR", 4);
	memcpy(header->oem_id, OEM_ID, 6);
	memcpy(header->oem_table_id, ACPI_TABLE_CREATOR, 8);
	memcpy(header->asl_compiler_id, ASLC, 4);

	header->asl_compiler_revision = asl_revision;
	header->length = sizeof(acpi_dmar_t);
	header->revision = get_acpi_table_revision(DMAR);

	dmar->host_address_width = soc_phys_address_size() - 1;
	dmar->flags = flags;

	current = acpi_fill_dmar(current);

	/* (Re)calculate length and checksum. */
	header->length = current - (unsigned long)dmar;
	header->checksum = acpi_checksum((void *)dmar, header->length);
}

unsigned long acpi_create_dmar_drhd_4k(unsigned long current, u8 flags,
				       u16 segment, u64 bar)
{
	return acpi_create_dmar_drhd(current, flags, segment, bar, 4 * KiB);
}

unsigned long acpi_create_dmar_drhd(unsigned long current, u8 flags,
	u16 segment, u64 bar, size_t size)
{
	/*
	 * Refer to IntelĀ® Virtualization Technology for Directed I/O
	 * Architecture Specification Revision 4.1,
	 * size is at least 1 page and max 2^15 pages, 4 KiB each, and the bar
	 * should be aligned with size.
	 */
	assert(4 * KiB <= size && size <= (1 << 15) * 4 * KiB && IS_POWER_OF_2(size));
	assert(IS_ALIGNED(bar, size));

	dmar_entry_t *drhd = (dmar_entry_t *)current;
	memset(drhd, 0, sizeof(*drhd));
	drhd->type = DMAR_DRHD;
	drhd->length = sizeof(*drhd); /* will be fixed up later */
	drhd->flags = flags;
	drhd->segment = segment;
	drhd->bar = bar;
	drhd->size = log2_64(size) - 12;

	return drhd->length;
}

unsigned long acpi_create_dmar_rmrr(unsigned long current, u16 segment,
				    u64 bar, u64 limit)
{
	dmar_rmrr_entry_t *rmrr = (dmar_rmrr_entry_t *)current;
	memset(rmrr, 0, sizeof(*rmrr));
	rmrr->type = DMAR_RMRR;
	rmrr->length = sizeof(*rmrr); /* will be fixed up later */
	rmrr->segment = segment;
	rmrr->bar = bar;
	rmrr->limit = limit;

	return rmrr->length;
}

unsigned long acpi_create_dmar_atsr(unsigned long current, u8 flags,
	u16 segment)
{
	dmar_atsr_entry_t *atsr = (dmar_atsr_entry_t *)current;
	memset(atsr, 0, sizeof(*atsr));
	atsr->type = DMAR_ATSR;
	atsr->length = sizeof(*atsr); /* will be fixed up later */
	atsr->flags = flags;
	atsr->segment = segment;

	return atsr->length;
}

unsigned long acpi_create_dmar_rhsa(unsigned long current, u64 base_addr,
	u32 proximity_domain)
{
	dmar_rhsa_entry_t *rhsa = (dmar_rhsa_entry_t *)current;
	memset(rhsa, 0, sizeof(*rhsa));
	rhsa->type = DMAR_RHSA;
	rhsa->length = sizeof(*rhsa);
	rhsa->base_address = base_addr;
	rhsa->proximity_domain = proximity_domain;

	return rhsa->length;
}

unsigned long acpi_create_dmar_andd(unsigned long current, u8 device_number,
	const char *device_name)
{
	dmar_andd_entry_t *andd = (dmar_andd_entry_t *)current;
	int andd_len = sizeof(dmar_andd_entry_t) + strlen(device_name) + 1;
	memset(andd, 0, andd_len);
	andd->type = DMAR_ANDD;
	andd->length = andd_len;
	andd->device_number = device_number;
	memcpy(&andd->device_name, device_name, strlen(device_name));

	return andd->length;
}

unsigned long acpi_create_dmar_satc(unsigned long current, u8 flags, u16 segment)
{
	dmar_satc_entry_t *satc = (dmar_satc_entry_t *)current;
	int satc_len = sizeof(dmar_satc_entry_t);
	memset(satc, 0, satc_len);
	satc->type = DMAR_SATC;
	satc->length = satc_len;
	satc->flags = flags;
	satc->segment_number = segment;

	return satc->length;
}

void acpi_dmar_drhd_fixup(unsigned long base, unsigned long current)
{
	dmar_entry_t *drhd = (dmar_entry_t *)base;
	drhd->length = current - base;
}

void acpi_dmar_rmrr_fixup(unsigned long base, unsigned long current)
{
	dmar_rmrr_entry_t *rmrr = (dmar_rmrr_entry_t *)base;
	rmrr->length = current - base;
}

void acpi_dmar_atsr_fixup(unsigned long base, unsigned long current)
{
	dmar_atsr_entry_t *atsr = (dmar_atsr_entry_t *)base;
	atsr->length = current - base;
}

void acpi_dmar_satc_fixup(unsigned long base, unsigned long current)
{
	dmar_satc_entry_t *satc = (dmar_satc_entry_t *)base;
	satc->length = current - base;
}

static unsigned long acpi_create_dmar_ds(unsigned long current,
	enum dev_scope_type type, u8 enumeration_id, u8 bus, u8 dev, u8 fn)
{
	/* we don't support longer paths yet */
	const size_t dev_scope_length = sizeof(dev_scope_t) + 2;

	dev_scope_t *ds = (dev_scope_t *)current;
	memset(ds, 0, dev_scope_length);
	ds->type	= type;
	ds->length	= dev_scope_length;
	ds->enumeration	= enumeration_id;
	ds->start_bus	= bus;
	ds->path[0].dev	= dev;
	ds->path[0].fn	= fn;

	return ds->length;
}

unsigned long acpi_create_dmar_ds_pci_br(unsigned long current, u8 bus,
	u8 dev, u8 fn)
{
	return acpi_create_dmar_ds(current,
			SCOPE_PCI_SUB, 0, bus, dev, fn);
}

unsigned long acpi_create_dmar_ds_pci(unsigned long current, u8 bus,
	u8 dev, u8 fn)
{
	return acpi_create_dmar_ds(current,
			SCOPE_PCI_ENDPOINT, 0, bus, dev, fn);
}

unsigned long acpi_create_dmar_ds_ioapic(unsigned long current,
	u8 enumeration_id, u8 bus, u8 dev, u8 fn)
{
	return acpi_create_dmar_ds(current,
			SCOPE_IOAPIC, enumeration_id, bus, dev, fn);
}

unsigned long acpi_create_dmar_ds_ioapic_from_hw(unsigned long current,
						 u32 addr, u8 bus, u8 dev, u8 fn)
{
	u8 enumeration_id = get_ioapic_id((uintptr_t)addr);
	return acpi_create_dmar_ds(current,
			SCOPE_IOAPIC, enumeration_id, bus, dev, fn);
}

unsigned long acpi_create_dmar_ds_msi_hpet(unsigned long current,
	u8 enumeration_id, u8 bus, u8 dev, u8 fn)
{
	return acpi_create_dmar_ds(current,
			SCOPE_MSI_HPET, enumeration_id, bus, dev, fn);
}