#include <console/console.h>
#include <arch/smp/mpspec.h>
#include <arch/ioapic.h>
#include <device/pci.h>
#include <string.h>
#include <stdint.h>
#include <assert.h>
#include "bus.h"
#include "ioapic.h"

// Generate MP-table IRQ numbers for PCI devices.
#define INT_A	0
#define INT_B	1
#define INT_C	2
#define INT_D	3
#define PCI_IRQ(dev, intLine)	(((dev)<<2) | intLine)

static int bus_isa;

static void xe7501devkit_register_ioapics(struct mp_config_table *mc)
{
	device_t dev;
	struct resource *res;

	// TODO: Gack. This is REALLY ugly.

	// Southbridge IOAPIC
	smp_write_ioapic(mc, IOAPIC_ICH3, 0x20, IO_APIC_ADDR);	// APIC ID, Version, Address

	// P64H2#2 Bus A IOAPIC
	dev = dev_find_slot(PCI_BUS_E7501_HI_B, PCI_DEVFN(30, 0));
	if (!dev)
		BUG();
	res = find_resource(dev, PCI_BASE_ADDRESS_0);
	smp_write_ioapic(mc, IOAPIC_P64H2_2_BUS_A, P64H2_IOAPIC_VERSION, res->base);

	// P64H2#2 Bus B IOAPIC
	dev = dev_find_slot(PCI_BUS_E7501_HI_B, PCI_DEVFN(28, 0));
	if (!dev)
		BUG();
	res = find_resource(dev, PCI_BASE_ADDRESS_0);
	smp_write_ioapic(mc, IOAPIC_P64H2_2_BUS_B, P64H2_IOAPIC_VERSION, res->base);


	// P64H2#1 Bus A IOAPIC
	dev = dev_find_slot(PCI_BUS_E7501_HI_D, PCI_DEVFN(30, 0));
	if (!dev)
		BUG();
	res = find_resource(dev, PCI_BASE_ADDRESS_0);
	smp_write_ioapic(mc, IOAPIC_P64H2_1_BUS_A, P64H2_IOAPIC_VERSION, res->base);

	// P64H2#1 Bus B IOAPIC
	dev = dev_find_slot(PCI_BUS_E7501_HI_D, PCI_DEVFN(28, 0));
	if (!dev)
		BUG();
	res = find_resource(dev, PCI_BASE_ADDRESS_0);
	smp_write_ioapic(mc, IOAPIC_P64H2_1_BUS_B, P64H2_IOAPIC_VERSION, res->base);
}

static void xe7501devkit_register_interrupts(struct mp_config_table *mc)
{
	// Chipset PCI bus
	//					 Type		Trigger | Polarity							Bus ID				IRQ					APIC ID					PIN#
	smp_write_lintsrc(mc, mp_ExtINT, MP_IRQ_TRIGGER_EDGE |MP_IRQ_POLARITY_HIGH,	PCI_BUS_CHIPSET,	0,					MP_APIC_ALL,			 0);
	smp_write_lintsrc(mc, mp_NMI,	MP_IRQ_TRIGGER_EDGE |MP_IRQ_POLARITY_HIGH,	PCI_BUS_CHIPSET,	0,					MP_APIC_ALL,			 1);
	smp_write_intsrc(mc, mp_INT,	MP_IRQ_TRIGGER_LEVEL|MP_IRQ_POLARITY_LOW, 	PCI_BUS_CHIPSET, 	PCI_IRQ(29, INT_A), IOAPIC_ICH3, 			16);	// USB 1.1 Controller #1
	smp_write_intsrc(mc, mp_INT,	MP_IRQ_TRIGGER_LEVEL|MP_IRQ_POLARITY_LOW,	PCI_BUS_CHIPSET,	PCI_IRQ(31, INT_B),	IOAPIC_ICH3,			17);	// SMBus
	smp_write_intsrc(mc, mp_INT, 	MP_IRQ_TRIGGER_LEVEL|MP_IRQ_POLARITY_LOW, 	PCI_BUS_CHIPSET, 	PCI_IRQ(29, INT_C), IOAPIC_ICH3, 			18);	// USB 1.1 Controller #3
	smp_write_intsrc(mc, mp_INT, 	MP_IRQ_TRIGGER_LEVEL|MP_IRQ_POLARITY_LOW, 	PCI_BUS_CHIPSET, 	PCI_IRQ(31, INT_C), IOAPIC_ICH3, 			18);	// IDE
	smp_write_intsrc(mc, mp_INT, 	MP_IRQ_TRIGGER_LEVEL|MP_IRQ_POLARITY_LOW, 	PCI_BUS_CHIPSET, 	PCI_IRQ(29, INT_D), IOAPIC_ICH3, 			19);	// USB 1.1 Controller #2

	// P64H2#2 Bus B
	smp_write_intsrc(mc, mp_INT, 	MP_IRQ_TRIGGER_LEVEL|MP_IRQ_POLARITY_LOW, 	PCI_BUS_P64H2_2_B, 	PCI_IRQ(1, INT_A), 	IOAPIC_P64H2_2_BUS_B,    0);	// Slot 2A (J23)
	smp_write_intsrc(mc, mp_INT, 	MP_IRQ_TRIGGER_LEVEL|MP_IRQ_POLARITY_LOW, 	PCI_BUS_P64H2_2_B, 	PCI_IRQ(1, INT_B), 	IOAPIC_P64H2_2_BUS_B,    1);	// Slot 2A (J23)
	smp_write_intsrc(mc, mp_INT, 	MP_IRQ_TRIGGER_LEVEL|MP_IRQ_POLARITY_LOW, 	PCI_BUS_P64H2_2_B, 	PCI_IRQ(1, INT_C), 	IOAPIC_P64H2_2_BUS_B,    2);	// Slot 2A (J23)
	smp_write_intsrc(mc, mp_INT, 	MP_IRQ_TRIGGER_LEVEL|MP_IRQ_POLARITY_LOW, 	PCI_BUS_P64H2_2_B, 	PCI_IRQ(1, INT_D), 	IOAPIC_P64H2_2_BUS_B,    3);	// Slot 2A (J23)
	smp_write_intsrc(mc, mp_INT, 	MP_IRQ_TRIGGER_LEVEL|MP_IRQ_POLARITY_LOW, 	PCI_BUS_P64H2_2_B, 	PCI_IRQ(2, INT_A), 	IOAPIC_P64H2_2_BUS_B,    4);	// Slot 2B (J24)
	smp_write_intsrc(mc, mp_INT, 	MP_IRQ_TRIGGER_LEVEL|MP_IRQ_POLARITY_LOW, 	PCI_BUS_P64H2_2_B, 	PCI_IRQ(2, INT_B), 	IOAPIC_P64H2_2_BUS_B,    5);	// Slot 2B (J24)
	smp_write_intsrc(mc, mp_INT, 	MP_IRQ_TRIGGER_LEVEL|MP_IRQ_POLARITY_LOW, 	PCI_BUS_P64H2_2_B, 	PCI_IRQ(2, INT_C), 	IOAPIC_P64H2_2_BUS_B,    6);	// Slot 2B (J24)
	smp_write_intsrc(mc, mp_INT, 	MP_IRQ_TRIGGER_LEVEL|MP_IRQ_POLARITY_LOW, 	PCI_BUS_P64H2_2_B, 	PCI_IRQ(2, INT_D), 	IOAPIC_P64H2_2_BUS_B,    7);	// Slot 2B (J24)
	smp_write_intsrc(mc, mp_INT, 	MP_IRQ_TRIGGER_LEVEL|MP_IRQ_POLARITY_LOW, 	PCI_BUS_P64H2_2_B, 	PCI_IRQ(3, INT_A), 	IOAPIC_P64H2_2_BUS_B,    8);	// Slot 2C (J25)
	smp_write_intsrc(mc, mp_INT, 	MP_IRQ_TRIGGER_LEVEL|MP_IRQ_POLARITY_LOW, 	PCI_BUS_P64H2_2_B, 	PCI_IRQ(3, INT_B), 	IOAPIC_P64H2_2_BUS_B,    9);	// Slot 2C (J25)
	smp_write_intsrc(mc, mp_INT, 	MP_IRQ_TRIGGER_LEVEL|MP_IRQ_POLARITY_LOW, 	PCI_BUS_P64H2_2_B, 	PCI_IRQ(3, INT_C), 	IOAPIC_P64H2_2_BUS_B,   10);	// Slot 2C (J25)
	smp_write_intsrc(mc, mp_INT, 	MP_IRQ_TRIGGER_LEVEL|MP_IRQ_POLARITY_LOW, 	PCI_BUS_P64H2_2_B, 	PCI_IRQ(3, INT_D), 	IOAPIC_P64H2_2_BUS_B,   11);	// Slot 2C (J25)
	smp_write_intsrc(mc, mp_INT, 	MP_IRQ_TRIGGER_LEVEL|MP_IRQ_POLARITY_LOW, 	PCI_BUS_P64H2_2_B, 	PCI_IRQ(4, INT_A), 	IOAPIC_P64H2_2_BUS_B,   12);	// Slot 2D (J12)
	smp_write_intsrc(mc, mp_INT, 	MP_IRQ_TRIGGER_LEVEL|MP_IRQ_POLARITY_LOW, 	PCI_BUS_P64H2_2_B, 	PCI_IRQ(4, INT_B), 	IOAPIC_P64H2_2_BUS_B,   13);	// Slot 2D (J12)
	smp_write_intsrc(mc, mp_INT, 	MP_IRQ_TRIGGER_LEVEL|MP_IRQ_POLARITY_LOW, 	PCI_BUS_P64H2_2_B, 	PCI_IRQ(4, INT_C), 	IOAPIC_P64H2_2_BUS_B,   14);	// Slot 2D (J12)
	smp_write_intsrc(mc, mp_INT, 	MP_IRQ_TRIGGER_LEVEL|MP_IRQ_POLARITY_LOW, 	PCI_BUS_P64H2_2_B, 	PCI_IRQ(4, INT_D), 	IOAPIC_P64H2_2_BUS_B,   15);	// Slot 2D (J12)

	// P64H2#2 Bus A
	smp_write_intsrc(mc, mp_INT,	MP_IRQ_TRIGGER_LEVEL|MP_IRQ_POLARITY_LOW,	PCI_BUS_P64H2_2_A, 	PCI_IRQ(1, INT_A),	IOAPIC_P64H2_2_BUS_A,	 0);	// SCSI
	smp_write_intsrc(mc, mp_INT,	MP_IRQ_TRIGGER_LEVEL|MP_IRQ_POLARITY_LOW,	PCI_BUS_P64H2_2_A, 	PCI_IRQ(1, INT_B),	IOAPIC_P64H2_2_BUS_A,	 1);	// SCSI

	// P64H2#1 Bus B
	smp_write_intsrc(mc, mp_INT, 	MP_IRQ_TRIGGER_LEVEL|MP_IRQ_POLARITY_LOW, 	PCI_BUS_P64H2_1_B, 	PCI_IRQ(1, INT_A), 	IOAPIC_P64H2_1_BUS_B,    0);	// GB Ethernet
	smp_write_intsrc(mc, mp_INT, 	MP_IRQ_TRIGGER_LEVEL|MP_IRQ_POLARITY_LOW, 	PCI_BUS_P64H2_1_B, 	PCI_IRQ(2, INT_A), 	IOAPIC_P64H2_1_BUS_B,    4);	// Slot 1B (J21)
	smp_write_intsrc(mc, mp_INT, 	MP_IRQ_TRIGGER_LEVEL|MP_IRQ_POLARITY_LOW, 	PCI_BUS_P64H2_1_B, 	PCI_IRQ(2, INT_B), 	IOAPIC_P64H2_1_BUS_B,    5);	// Slot 1B (J21)
	smp_write_intsrc(mc, mp_INT, 	MP_IRQ_TRIGGER_LEVEL|MP_IRQ_POLARITY_LOW, 	PCI_BUS_P64H2_1_B, 	PCI_IRQ(2, INT_C), 	IOAPIC_P64H2_1_BUS_B,    6);	// Slot 1B (J21)
	smp_write_intsrc(mc, mp_INT, 	MP_IRQ_TRIGGER_LEVEL|MP_IRQ_POLARITY_LOW, 	PCI_BUS_P64H2_1_B, 	PCI_IRQ(2, INT_D), 	IOAPIC_P64H2_1_BUS_B,    7);	// Slot 1B (J21)

	// P64H2#1 Bus A
	smp_write_intsrc(mc, mp_INT, 	MP_IRQ_TRIGGER_LEVEL|MP_IRQ_POLARITY_LOW, 	PCI_BUS_P64H2_1_A, 	PCI_IRQ(1, INT_A), 	IOAPIC_P64H2_1_BUS_A,    0);	// Slot 1A (J20)
	smp_write_intsrc(mc, mp_INT, 	MP_IRQ_TRIGGER_LEVEL|MP_IRQ_POLARITY_LOW, 	PCI_BUS_P64H2_1_A, 	PCI_IRQ(1, INT_B), 	IOAPIC_P64H2_1_BUS_A,    1);	// Slot 1A (J20)
	smp_write_intsrc(mc, mp_INT, 	MP_IRQ_TRIGGER_LEVEL|MP_IRQ_POLARITY_LOW, 	PCI_BUS_P64H2_1_A, 	PCI_IRQ(1, INT_C), 	IOAPIC_P64H2_1_BUS_A,    2);	// Slot 1A (J20)
	smp_write_intsrc(mc, mp_INT, 	MP_IRQ_TRIGGER_LEVEL|MP_IRQ_POLARITY_LOW, 	PCI_BUS_P64H2_1_A, 	PCI_IRQ(1, INT_D), 	IOAPIC_P64H2_1_BUS_A,    3);	// Slot 1A (J20)

	// ICH-3

	smp_write_intsrc(mc, mp_INT,	MP_IRQ_TRIGGER_LEVEL|MP_IRQ_POLARITY_LOW,	PCI_BUS_ICH3,		PCI_IRQ(0, INT_A),	IOAPIC_ICH3,			16);	// Video
	smp_write_intsrc(mc, mp_INT,	MP_IRQ_TRIGGER_LEVEL|MP_IRQ_POLARITY_LOW,	PCI_BUS_ICH3,		PCI_IRQ(2, INT_A),	IOAPIC_ICH3,			18);	// Debug slot (J11)
	smp_write_intsrc(mc, mp_INT,	MP_IRQ_TRIGGER_LEVEL|MP_IRQ_POLARITY_LOW,	PCI_BUS_ICH3,		PCI_IRQ(2, INT_B),	IOAPIC_ICH3,			19);	// Debug slot (J11)
	smp_write_intsrc(mc, mp_INT,	MP_IRQ_TRIGGER_LEVEL|MP_IRQ_POLARITY_LOW,	PCI_BUS_ICH3,		PCI_IRQ(2, INT_C),	IOAPIC_ICH3,			16);	// Debug slot (J11)
	smp_write_intsrc(mc, mp_INT,	MP_IRQ_TRIGGER_LEVEL|MP_IRQ_POLARITY_LOW,	PCI_BUS_ICH3,		PCI_IRQ(2, INT_D),	IOAPIC_ICH3,			17);	// Debug slot (J11)

	// TODO: Not sure how to handle BT_INTR# signals from the P64H2s. Do we even need to, in APIC mode?

	mptable_add_isa_interrupts(mc, bus_isa, IOAPIC_ICH3, 0);
}

static void *smp_write_config_table(void* v)
{
	struct mp_config_table *mc;

	mc = (void *)(((char *)v) + SMP_FLOATING_TABLE_LEN);

	mptable_init(mc, LAPIC_ADDR);

	smp_write_processors(mc);

	mptable_write_buses(mc, NULL, &bus_isa);
	xe7501devkit_register_ioapics(mc);
	xe7501devkit_register_interrupts(mc);

	/* Compute the checksums */
	mc->mpe_checksum = smp_compute_checksum(smp_next_mpc_entry(mc), mc->mpe_length);
	mc->mpc_checksum = smp_compute_checksum(mc, mc->mpc_length);
	printk(BIOS_DEBUG, "Wrote the mp table end at: %p - %p\n", mc, smp_next_mpe_entry(mc));

	return smp_next_mpe_entry(mc);
}

unsigned long write_smp_table(unsigned long addr)
{
	void *v;
	v = smp_write_floating_table(addr);
	return (unsigned long)smp_write_config_table(v);
}