/*
 * hal_acpi.c - ACPI parser
 *
 * Copyright (c) 2017 Maxime Villard
 *
 * This file is part of ALMOS-MKH.
 *
 * ALMOS-MKH is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2.0 of the License.
 *
 * ALMOS-MKH is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with ALMOS-MKH; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include <hal_types.h>
#include <hal_boot.h>
#include <hal_acpi.h>
#include <hal_internal.h>

#include <memcpy.h>
#include <thread.h>
#include <string.h>
#include <process.h>
#include <printk.h>
#include <vmm.h>
#include <core.h>
#include <cluster.h>

/* -------------------------------------------------------------------------- */

#define RSDT_NENTRIES(rsdt) \
	( (rsdt->Header.Length - sizeof(rsdt_t) + sizeof(uint32_t)) / sizeof(uint32_t) )
#define RSDT_ENTRIES(rsdt) \
	((uint32_t *)&rsdt->TableOffsetEntry[0])

static bool_t hal_acpi_table_correct(header_t *header)
{
	uint8_t *ptr, sum = 0;
	size_t i, n;

	ptr = (uint8_t *)header;
	n = header->Length;

	/* Verify checksum */
	for (i = 0; i < n; i++) {
		sum += ptr[i];
	}

	return (sum == 0);
}

static vaddr_t hal_acpi_map_table(paddr_t headerpa, const char *sig)
{
	paddr_t basepa, pa;
	vaddr_t baseva, retva, va;
	size_t off, size;
	size_t npages, ngrow, n;
	header_t *header;

	/* Allocate the VA for the header */
	basepa = rounddown(headerpa, PAGE_SIZE);
	npages = roundup(headerpa + sizeof(header_t), PAGE_SIZE) - basepa;
	baseva = hal_gpt_bootstrap_valloc(npages);
	off = headerpa - basepa;

	/* Enter the VA, and get the total size of the structure */
	hal_gpt_enter_range(baseva, basepa, npages);
	retva = (vaddr_t)(baseva + off);
	header = (header_t *)retva;
	size = header->Length;
	XASSERT(size >= sizeof(header_t));

	/* Check the signature */
	if (memcmp((void *)&header->Signature, sig, ACPI_NAME_SIZE))
		return 0;

	/* Grow the VA to map the rest */
	ngrow = roundup(headerpa + size, PAGE_SIZE) - basepa;
	n = ngrow - npages;
	va = hal_gpt_bootstrap_valloc(n);
	pa = basepa + npages * PAGE_SIZE;
	hal_gpt_enter_range(va, pa, n);

	/* Verify the checksum */
	if (!hal_acpi_table_correct(header))
		x86_panic("Wrong checksum for table");

	return retva;
}

/* -------------------------------------------------------------------------- */

static void hal_acpi_parse_ioapic(madt_ioapic_t *ioapic)
{
	extern paddr_t ioapic_pa;
	uint32_t irqbase;
	ioapic_pa = ioapic->Address;
	irqbase = ioapic->GlobalIrqBase;

	x86_printf("-> IOAPIC address: %Z\n", ioapic_pa);
	x86_printf("-> IOAPIC irqbase: %z\n", (uint64_t)irqbase);

	if (irqbase != 0)
		x86_panic("irqbase != 0");
}

static void hal_acpi_parse_madt(madt_t *madt)
{
	madt_lapic_t *lapic;
	madt_lapic_override_t *override;
	void *ptr, *end;
	subheader_t *sub;
	size_t nioapic = 0;
	extern size_t ncpu;

	extern paddr_t lapic_pa;
	lapic_pa = (paddr_t)madt->Address;

	ptr = (void *)(madt + 1);
	end = (void *)madt + madt->Header.Length;

	while (ptr < end) {
		sub = (subheader_t *)ptr;
		if (sub->Type == ACPI_MADT_TYPE_IO_APIC) {
			hal_acpi_parse_ioapic((madt_ioapic_t *)sub);
			nioapic++;
		} else if (sub->Type == ACPI_MADT_TYPE_LOCAL_APIC_OVERRIDE) {
			override = (madt_lapic_override_t *)sub;
			lapic_pa = (paddr_t)override->Address;
			x86_printf("-> found LAPIC override\n");
		} else if (sub->Type == ACPI_MADT_TYPE_LOCAL_APIC) {
			lapic = (madt_lapic_t *)sub;
			if (lapic->LapicFlags & ACPI_MADT_LAPIC_ENABLED) {
				cpu_activate(lapic->Id);
				x86_printf("-> found LAPIC %z\n", (uint64_t)lapic->Id);
				ncpu++;
			}
		}

		ptr += sub->Length;
	}

	x86_printf("-> LAPIC address: %Z\n", lapic_pa);
	x86_printf("-> number of CPUs: %z\n", ncpu);
	x86_printf("-> number of IOAPICs: %z\n", nioapic);
}

static madt_t *hal_acpi_map_madt(rsdt_t *rsdt)
{
	vaddr_t va;
	paddr_t pa;
	uint32_t *ent;
	size_t i, n;

	n = RSDT_NENTRIES(rsdt);
	ent = RSDT_ENTRIES(rsdt);

	for (i = 0; i < n; i++) {
		pa = (paddr_t)ent[i];
		va = hal_acpi_map_table(pa, "APIC");
		if (va == 0)
			continue;
		return (madt_t *)va;
	}

	return NULL;
}

/* -------------------------------------------------------------------------- */

static void hal_acpi_parse_srat(srat_t *srat)
{
	srat_cpu_affinity_t *cpu;
	srat_mem_affinity_t *mem;
	void *ptr, *end;
	subheader_t *sub;
	uint32_t dom;

	ptr = (void *)(srat + 1);
	end = (void *)srat + srat->Header.Length;

	while (ptr < end) {
		sub = (subheader_t *)ptr;
		if (sub->Type == ACPI_SRAT_TYPE_CPU_AFFINITY) {
			cpu = (srat_cpu_affinity_t *)sub;
			if (cpu->Flags & ACPI_SRAT_CPU_USE_AFFINITY) {
				dom = ((uint32_t)cpu->ProximityDomainHi[2] << 24) |
				      ((uint32_t)cpu->ProximityDomainHi[1] << 16) |
				      ((uint32_t)cpu->ProximityDomainHi[0] << 8) |
				       (uint32_t)cpu->ProximityDomainLo;
				x86_printf("-> found CPU affinity: %z->%z\n",
				    (uint64_t)cpu->ApicId, (uint64_t)dom);
			}
		} else if (sub->Type == ACPI_SRAT_TYPE_MEMORY_AFFINITY) {
			mem = (srat_mem_affinity_t *)sub;
//			if (mem->Flags & ACPI_SRAT_MEM_ENABLED)
				x86_printf("-> found MEM affinity: %z->[%Z,%Z,%Z]\n",
				    mem->ProximityDomain,
				    mem->BaseAddress, mem->Length,(uint64_t)mem->Flags);
		} 

		ptr += sub->Length;
	}

}

static srat_t *hal_acpi_map_srat(rsdt_t *rsdt)
{
	vaddr_t va;
	paddr_t pa;
	uint32_t *ent;
	size_t i, n;

	n = RSDT_NENTRIES(rsdt);
	ent = RSDT_ENTRIES(rsdt);

	for (i = 0; i < n; i++) {
		pa = (paddr_t)ent[i];
		va = hal_acpi_map_table(pa, "SRAT");
		if (va == 0)
			continue;

		return (srat_t *)va;
	}

	return NULL;
}

/* -------------------------------------------------------------------------- */

static rsdt_t *hal_acpi_map_rsdt(rsdp_t *rsdp)
{
	paddr_t rsdt_pa;
	rsdt_t *rsdt;

	rsdt_pa = (paddr_t)rsdp->RsdtPhysicalAddress;
	rsdt = (rsdt_t *)hal_acpi_map_table(rsdt_pa, "RSDT");
	if (rsdt == 0)
		x86_panic("RSDT invalid");

	return rsdt;
}

/* -------------------------------------------------------------------------- */

static bool_t hal_acpi_rsdp_correct(uint8_t *ptr)
{
	uint8_t sum = 0;
	size_t i;

	/* Verify checksum */
	for (i = 0; i < 20; i++) {
		sum += ptr[i];
	}

	return (sum == 0);
}

static rsdp_t *hal_acpi_find_rsdp(vaddr_t va_start, vaddr_t va_end)
{
	rsdp_t *rsdp;
	vaddr_t va = va_start;
	size_t i, n = PAGE_SIZE / ACPI_RSDP_ALIGN;
	char oem[ACPI_OEM_ID_SIZE + 1];
	uint8_t *ptr;

	/* The table is on a 16bit boundary */
	for (va = va_start; va < va_end; va += PAGE_SIZE) {
		for (i = 0; i < n; i++) {
			ptr = (uint8_t *)va + i * ACPI_RSDP_ALIGN;
			if (memcmp(ptr, ACPI_RSDP_SIGNATURE, ACPI_RSDP_SIGNATURE_SIZE))
				continue;
			if (!hal_acpi_rsdp_correct(ptr))
				continue;
			goto found;
		}
	}

	return NULL;

found:
	rsdp = (rsdp_t *)ptr;

	memcpy(&oem, rsdp->OemId, ACPI_OEM_ID_SIZE);
	oem[ACPI_OEM_ID_SIZE] = '\0';
	x86_printf("-> OEM: %s\n", oem);
	if (rsdp->Revision != 0)
		x86_printf("[!] Using ACPI 1.0\n");

	return rsdp;
}

/* -------------------------------------------------------------------------- */

void hal_acpi_init()
{
	rsdp_t *rsdp;
	rsdt_t *rsdt;
	madt_t *madt;
	srat_t *srat;
	paddr_t bios_min = 0x0E0000;
	paddr_t bios_max = 0x100000;
	vaddr_t vabase;
	size_t npages;

	npages = (bios_max - bios_min) / PAGE_SIZE;
	vabase = hal_gpt_bootstrap_valloc(npages);

	hal_gpt_enter_range(vabase, bios_min, npages);

	/* First, find RSDP */
	rsdp = hal_acpi_find_rsdp(vabase, vabase + npages * PAGE_SIZE);
	if (rsdp == NULL)
		x86_panic("RSDP not found");

	/* Then, map RSDT */
	rsdt = hal_acpi_map_rsdt(rsdp);
	if (rsdt == NULL)
		x86_panic("RSDT not found");

	/* Map MADT and parse it */
	madt = hal_acpi_map_madt(rsdt);
	if (madt == NULL)
		x86_panic("MADT not found");
	hal_acpi_parse_madt(madt);

	/* Map SRAT and parse it */
	srat = hal_acpi_map_srat(rsdt);
	if (srat != NULL)
		hal_acpi_parse_srat(srat);
	else
		x86_printf("-> SRAT not found, single cluster\n");
}

