#include "kvm/kvm.h"
#include "kvm/bios.h"
#include "kvm/apic.h"
#include "kvm/mptable.h"
#include "kvm/util.h"
#include "kvm/devices.h"
#include "kvm/pci.h"

#include <linux/kernel.h>
#include <string.h>

#include <asm/mpspec_def.h>
#include <linux/types.h>

/*
 * FIXME: please make sure the addresses borrowed
 * for apic/ioapic never overlaped! We need a global
 * tracker of system resources (including io, mmio,
 * and friends).
 */

static unsigned int mpf_checksum(unsigned char *mp, int len)
{
	unsigned int sum = 0;

	while (len--)
		sum += *mp++;

	return sum & 0xFF;
}

static unsigned int gen_cpu_flag(unsigned int cpu, unsigned int ncpu)
{
	/* sets enabled/disabled | BSP/AP processor */
	return ( (cpu < ncpu) ? CPU_ENABLED       : 0) |
		((cpu == 0)   ? CPU_BOOTPROCESSOR : 0x00);
}

#define MPTABLE_SIG_FLOATING	"_MP_"
#define MPTABLE_OEM		"KVMCPU00"
#define MPTABLE_PRODUCTID	"0.1         "
#define MPTABLE_PCIBUSTYPE	"PCI   "
#define MPTABLE_ISABUSTYPE	"ISA   "

#define MPTABLE_STRNCPY(d, s)	memcpy(d, s, sizeof(d))

/* It should be more than enough */
#define MPTABLE_MAX_SIZE	(32 << 20)

/*
 * Too many cpus will require x2apic mode
 * and rather ACPI support so we limit it
 * here for a while.
 */
#define MPTABLE_MAX_CPUS	255

static void mptable_add_irq_src(struct mpc_intsrc *mpc_intsrc,
				u16 srcbusid,	u16 srcbusirq,
				u16 dstapic,	u16 dstirq)
{
	*mpc_intsrc = (struct mpc_intsrc) {
		.type		= MP_INTSRC,
		.irqtype	= mp_INT,
		.irqflag	= MP_IRQDIR_DEFAULT,
		.srcbus		= srcbusid,
		.srcbusirq	= srcbusirq,
		.dstapic	= dstapic,
		.dstirq		= dstirq
	};
}

/**
 * mptable_setup - create mptable and fill guest memory with it
 */
int mptable__init(struct kvm *kvm)
{
	unsigned long real_mpc_table, real_mpf_intel, size;
	struct mpf_intel *mpf_intel;
	struct mpc_table *mpc_table;
	struct mpc_cpu *mpc_cpu;
	struct mpc_bus *mpc_bus;
	struct mpc_ioapic *mpc_ioapic;
	struct mpc_intsrc *mpc_intsrc;
	struct device_header *dev_hdr;

	const int pcibusid = 0;
	const int isabusid = 1;

	unsigned int i, nentries = 0, ncpus = kvm->nrcpus;
	unsigned int ioapicid;
	void *last_addr;

	/* That is where MP table will be in guest memory */
	real_mpc_table = ALIGN(MB_BIOS_BEGIN + bios_rom_size, 16);

	if (ncpus > MPTABLE_MAX_CPUS) {
		pr_warning("Too many cpus: %d limited to %d",
			ncpus, MPTABLE_MAX_CPUS);
		ncpus = MPTABLE_MAX_CPUS;
	}

	mpc_table = calloc(1, MPTABLE_MAX_SIZE);
	if (!mpc_table)
		return -ENOMEM;

	MPTABLE_STRNCPY(mpc_table->signature,	MPC_SIGNATURE);
	MPTABLE_STRNCPY(mpc_table->oem,		MPTABLE_OEM);
	MPTABLE_STRNCPY(mpc_table->productid,	MPTABLE_PRODUCTID);

	mpc_table->spec		= 4;
	mpc_table->lapic	= APIC_ADDR(0);
	mpc_table->oemcount	= ncpus; /* will be updated again at end */

	/*
	 * CPUs enumeration. Technically speaking we should
	 * ask either host or HV for apic version supported
	 * but for a while we simply put some random value
	 * here.
	 */
	mpc_cpu = (void *)&mpc_table[1];
	for (i = 0; i < ncpus; i++) {
		mpc_cpu->type		= MP_PROCESSOR;
		mpc_cpu->apicid		= i;
		mpc_cpu->apicver	= KVM_APIC_VERSION;
		mpc_cpu->cpuflag	= gen_cpu_flag(i, ncpus);
		mpc_cpu->cpufeature	= 0x600; /* some default value */
		mpc_cpu->featureflag	= 0x201; /* some default value */
		mpc_cpu++;
	}

	last_addr = (void *)mpc_cpu;
	nentries += ncpus;

	/*
	 * PCI buses.
	 * FIXME: Some callback here to obtain real number
	 * of PCI buses present in system.
	 */
	mpc_bus		= last_addr;
	mpc_bus->type	= MP_BUS;
	mpc_bus->busid	= pcibusid;
	MPTABLE_STRNCPY(mpc_bus->bustype, MPTABLE_PCIBUSTYPE);

	last_addr = (void *)&mpc_bus[1];
	nentries++;

	/*
	 * ISA bus.
	 * FIXME: Same issue as for PCI bus.
	 */
	mpc_bus		= last_addr;
	mpc_bus->type	= MP_BUS;
	mpc_bus->busid	= isabusid;
	MPTABLE_STRNCPY(mpc_bus->bustype, MPTABLE_ISABUSTYPE);

	last_addr = (void *)&mpc_bus[1];
	nentries++;

	/*
	 * IO-APIC chip.
	 */
	ioapicid		= ncpus + 1;
	mpc_ioapic		= last_addr;
	mpc_ioapic->type	= MP_IOAPIC;
	mpc_ioapic->apicid	= ioapicid;
	mpc_ioapic->apicver	= KVM_APIC_VERSION;
	mpc_ioapic->flags	= MPC_APIC_USABLE;
	mpc_ioapic->apicaddr	= IOAPIC_ADDR(0);

	last_addr = (void *)&mpc_ioapic[1];
	nentries++;

	/*
	 * IRQ sources.
	 * Also note we use PCI irqs here, no for ISA bus yet.
	 */

	dev_hdr = device__first_dev(DEVICE_BUS_PCI);
	while (dev_hdr) {
		unsigned char srcbusirq;
		struct pci_device_header *pci_hdr = dev_hdr->data;

		srcbusirq = (pci_hdr->subsys_id << 2) | (pci_hdr->irq_pin - 1);
		mpc_intsrc = last_addr;
		mptable_add_irq_src(mpc_intsrc, pcibusid, srcbusirq, ioapicid, pci_hdr->irq_line);

		last_addr = (void *)&mpc_intsrc[dev_hdr->dev_num];
		nentries++;
		dev_hdr = device__next_dev(dev_hdr);
	}

	/*
	 * Local IRQs assignment (LINT0, LINT1)
	 */
	mpc_intsrc		= last_addr;
	mpc_intsrc->type	= MP_LINTSRC;
	mpc_intsrc->irqtype	= mp_ExtINT;
	mpc_intsrc->irqtype	= mp_INT;
	mpc_intsrc->irqflag	= MP_IRQDIR_DEFAULT;
	mpc_intsrc->srcbus	= isabusid;
	mpc_intsrc->srcbusirq	= 0;
	mpc_intsrc->dstapic	= 0; /* FIXME: BSP apic */
	mpc_intsrc->dstirq	= 0; /* LINT0 */

	last_addr = (void *)&mpc_intsrc[1];
	nentries++;

	mpc_intsrc		= last_addr;
	mpc_intsrc->type	= MP_LINTSRC;
	mpc_intsrc->irqtype	= mp_NMI;
	mpc_intsrc->irqflag	= MP_IRQDIR_DEFAULT;
	mpc_intsrc->srcbus	= isabusid;
	mpc_intsrc->srcbusirq	= 0;
	mpc_intsrc->dstapic	= 0; /* FIXME: BSP apic */
	mpc_intsrc->dstirq	= 1; /* LINT1 */

	last_addr = (void *)&mpc_intsrc[1];
	nentries++;

	/*
	 * Floating MP table finally.
	 */
	real_mpf_intel	= ALIGN((unsigned long)last_addr - (unsigned long)mpc_table, 16);
	mpf_intel	= (void *)((unsigned long)mpc_table + real_mpf_intel);

	MPTABLE_STRNCPY(mpf_intel->signature, MPTABLE_SIG_FLOATING);
	mpf_intel->length	= 1;
	mpf_intel->specification= 4;
	mpf_intel->physptr	= (unsigned int)real_mpc_table;
	mpf_intel->checksum	= -mpf_checksum((unsigned char *)mpf_intel, sizeof(*mpf_intel));

	/*
	 * No last_addr inclrement here please, we need last
	 * active position here to compute table size.
	 */

	/*
	 * Don't forget to update header in fixed table.
	*/
	mpc_table->oemcount	= nentries;
	mpc_table->length	= last_addr - (void *)mpc_table;
	mpc_table->checksum	= -mpf_checksum((unsigned char *)mpc_table, mpc_table->length);


	/*
	 * We will copy the whole table, no need to separate
	 * floating structure and table itkvm.
	 */
	size = (unsigned long)mpf_intel + sizeof(*mpf_intel) - (unsigned long)mpc_table;

	/*
	 * The finial check -- never get out of system bios
	 * area. Lets also check for allocated memory overrun,
	 * in real it's late but still usefull.
	 */

	if (size > (unsigned long)(MB_BIOS_END - bios_rom_size) ||
	    size > MPTABLE_MAX_SIZE) {
		free(mpc_table);
		pr_err("MP table is too big");

		return -E2BIG;
	}

	/*
	 * OK, it is time to move it to guest memory.
	 */
	memcpy(guest_flat_to_host(kvm, real_mpc_table), mpc_table, size);

	free(mpc_table);

	return 0;
}
firmware_init(mptable__init);

int mptable__exit(struct kvm *kvm)
{
	return 0;
}
firmware_exit(mptable__exit);
