/*
 * minimal acpi support for multiprocessors.
 *
 * avoids AML but that's only enough to discover
 * the processors, not the interrupt routing details.
 */
#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "io.h"
#include "mp.h"
#include "mpacpi.h"

/* 8c says: out of fixed registers */
#define L64GET(p)	((uvlong)L32GET((p)+4) << 32 | L32GET(p))

enum {
	/* apic types */
	Apiclproc,
	Apicio,
	Apicintrsrcoverride,
	Apicnmisrc,
	Apiclnmi,
	Apicladdroverride,
	Apicios,
	Apicls,
	Apicintrsrc,
	Apiclx2,
	Apiclx2nmi,

	PcmpUsed = 1ul<<31,		/* Apic->flags addition */

	Lapicbase = 0x1b,		/* msr */

	Lapicae	= 1<<11,		/* apic enable in Lapicbase */
};

#define dprint(...)	if(mpdebug) print(__VA_ARGS__); else USED(mpdebug)

/* from mp.c */
int	mpdebug;
int	mpmachno;
Apic	mpapic[MaxAPICNO+1];
int	machno2apicno[MaxAPICNO+1];	/* inverse map: machno -> APIC ID */

Apic	*bootapic;

static int nprocid;

static uvlong
l64get(uchar *p)
{
	return L64GET(p);
}

int
apicset(Apic *apic, int type, int apicno, int f)
{
	if(apicno > MaxAPICNO)
		return -1;
	apic->type = type;
	apic->apicno = apicno;
	apic->flags = f | PcmpEN | PcmpUsed;
	return 0;
}

int
mpnewproc(Apic *apic, int apicno, int f)
{
	if(apic->flags & PcmpUsed) {
		print("mpnewproc: apic already enabled\n");
		return -1;
	}
	if (apicset(apic, PcmpPROCESSOR, apicno, f) < 0)
		return -1;
	apic->lintr[1] = apic->lintr[0] = ApicIMASK;
	/* botch! just enumerate */
	if(apic->flags & PcmpBP)
		apic->machno = 0;
	else
		apic->machno = ++mpmachno;
	machno2apicno[apic->machno] = apicno;
	return 0;
}

static int
mpacpiproc(uchar *p, ulong laddr)
{
	int id, f;
	ulong *vladdr;
	vlong base;
	char *already;
	Apic *apic;

	/* p bytes: type (0), len (8), cpuid, cpu_lapic id, flags[4] */
	id = p[3];
	/* cpu unusable flag or id out of range? */
	if((L32GET(p+4) & 1) == 0 || id > MaxAPICNO)
		return -1;

	vladdr = nil;
	already = "";
	f = 0;
	apic = &mpapic[id];
	apic->paddr = laddr;
	if (nprocid++ == 0) {
		f = PcmpBP;
		vladdr = vmap(apic->paddr, 1024);
		if(apic->addr == nil){
			print("proc apic %d: failed to map %#p\n", id,
				apic->paddr);
			already = "(fail)";
		}
		bootapic = apic;
	}
	apic->addr = vladdr;

	if(apic->flags & PcmpUsed)
		already = "(on)";
	else
		mpnewproc(apic, id, f);

	if (0)
		dprint("\tapic proc %d/%d apicid %d flags%s%s %s\n", nprocid-1,
			apic->machno, id, f & PcmpBP? " boot": "",
			f & PcmpEN? " enabled": "", already);
	USED(already);

	rdmsr(Lapicbase, &base);
	if (!(base & Lapicae)) {
		dprint("mpacpiproc: enabling lapic\n");
		wrmsr(Lapicbase, base | Lapicae);
	}
	return 0;
}

static void
mpacpicpus(Madt *madt)
{
	int i, n;
	ulong laddr;
	uchar *p;

	laddr = L32GET(madt->addr);
	dprint("APIC mpacpicpus lapic addr %#lux, flags %#ux\n",
		laddr, L32GET(madt->flags));

	n = L32GET(&madt->sdthdr[4]);
	p = madt->structures;
	/* byte 0 is assumed to be type, 1 is assumed to be length */
	for(i = offsetof(Madt, structures[0]); i < n; i += p[1], p += p[1])
		switch(p[0]){
		case Apiclproc:
			mpacpiproc(p, laddr);
			break;
		}
}

/* returns nil iff checksum is bad */
static void *
mpacpirsdchecksum(void* addr, int length)
{
	uchar *p, sum;

	sum = 0;
	for(p = addr; length-- > 0; p++)
		sum += *p;
	return sum == 0? addr: nil;
}

static void *
mpacpirsdscan(uchar* addr, int len, char* signature)
{
	int sl;
	uchar *e, *p;

	sl = strlen(signature);
	e = addr + len - sl;
	for(p = addr; p < e; p += 16)
		if(memcmp(p, signature, sl) == 0)
			return p;
	return nil;
}

static void *
mpacpirsdsearch(char* signature)
{
	uintptr p;
	uchar *bda;
	Rsd *rsd;

	/*
	 * Search for the data structure signature:
	 * 1) in the first KB of the EBDA;
	 * 2) in the BIOS ROM between 0xE0000 and 0xFFFFF.
	 */
	if(strncmp((char*)KADDR(0xFFFD9), "EISA", 4) == 0){
		bda = BIOSSEG(0x40);
		p = bda[0x0F]<<8 | bda[0x0E];
		if(p != 0 &&
		    (rsd = mpacpirsdscan(KADDR(p), 1024, signature)) != nil)
			return rsd;
	}
	return mpacpirsdscan(BIOSSEG(0xE000), 0x20000, signature);
}

/* call func for each acpi table found */
static void
mpacpiscan(void (*func)(uchar *))
{
	int asize, i, tbllen, sdtlen;
	uintptr dhpa, sdtpa;
	uchar *tbl, *sdt;
	Rsd *rsd;

	dprint("ACPI...");
	if((rsd = mpacpirsdsearch("RSD PTR ")) == nil) {
		dprint("none\n");
		return;
	}

	dprint("rsd %#p physaddr %#ux length %ud %#llux rev %d oem %.6s\n",
		rsd, L32GET(rsd->raddr), L32GET(rsd->length),
		l64get(rsd->xaddr), rsd->revision, (char*)rsd->oemid);

	if(rsd->revision == 2){
		if(mpacpirsdchecksum(rsd, 36) == nil)
			return;
		asize = 8;
		sdtpa = l64get(rsd->xaddr);
	} else {
		if(mpacpirsdchecksum(rsd, 20) == nil)
			return;
		asize = 4;
		sdtpa = L32GET(rsd->raddr);
	}

	if((sdt = vmap(sdtpa, 8)) == nil)
		return;
	if((sdt[0] != 'R' && sdt[0] != 'X') || memcmp(sdt+1, "SDT", 3) != 0){
		vunmap(sdt, 8);
		return;
	}
	sdtlen = L32GET(sdt + 4);
	vunmap(sdt, 8);

	if((sdt = vmap(sdtpa, sdtlen)) == nil)
		return;
	if(mpacpirsdchecksum(sdt, sdtlen) != nil)
		for(i = 36; i < sdtlen; i += asize){
			if(asize == 8)
				dhpa = l64get(sdt+i);
			else
				dhpa = L32GET(sdt+i);
	
			if((tbl = vmap(dhpa, 8)) == nil)
				continue;
			tbllen = L32GET(tbl + 4);
			vunmap(tbl, 8);
	
			if((tbl = vmap(dhpa, tbllen)) == nil)
				continue;
			if(mpacpirsdchecksum(tbl, tbllen) != nil)
				(*func)(tbl);
			vunmap(tbl, tbllen);
		}
	vunmap(sdt, sdtlen);
}

static void
mpacpitbl(uchar *p)
{
	/* for now, just activate any idle cpus */
	if (memcmp(p, "APIC", 4) == 0)
		mpacpicpus((Madt *)p);
}

static void
mpacpi(void)
{
	mpdebug = getconf("*debugmp") != nil;
	mpacpiscan(mpacpitbl);
}

void	(*mpacpifunc)(void) = mpacpi;
