#include <smp/start_stop.h>
#include <arch/smp/mpspec.h>
#include <cpu/p6/apic.h>
#include <delay.h>
#include <string.h>

#ifndef START_CPU_SEG
#define START_CPU_SEG 0x90000
#endif
#if (START_CPU_SEG&0xffff) != 0
#error START_CPU_SEG must be 64k aligned
#endif

static inline void hlt(void)
{
	asm("hlt");
	return;
}

unsigned long this_processors_id(void)
{
	return apic_read(APIC_ID) >> 24;
}

int processor_index(unsigned long apicid)
{
	int i;
	for(i = 0; i < MAX_CPUS; i++) {
		if (initial_apicid[i] == apicid) {
			return i;
		}
	}
	return -1;
}

void stop_cpu(unsigned long apicid)
{
	int timeout;
	unsigned long send_status;

	/* send an APIC INIT to myself */
	apic_write_around(APIC_ICR2, SET_APIC_DEST_FIELD(apicid));
	apic_write_around(APIC_ICR, APIC_INT_LEVELTRIG | APIC_INT_ASSERT | APIC_DM_INIT);

	/* wait for the ipi send to finish */
	printk_spew("Waiting for send to finish...\n");
	timeout = 0;
	do {
		printk_spew("+");
		udelay(100);
		send_status = apic_read(APIC_ICR) & APIC_ICR_BUSY;
	} while (send_status && (timeout++ < 1000));
	if (timeout >= 1000) {
		printk_err("timed out\n");
	}
	mdelay(10);

	printk_spew("Deasserting INIT.\n");
	/* Deassert the APIC INIT */
	apic_write_around(APIC_ICR2, SET_APIC_DEST_FIELD(apicid));	
	apic_write_around(APIC_ICR, APIC_INT_LEVELTRIG | APIC_DM_INIT);

	printk_spew("Waiting for send to finish...\n");
	timeout = 0;
	do {
		printk_spew("+");
		udelay(100);
		send_status = apic_read(APIC_ICR) & APIC_ICR_BUSY;
	} while (send_status && (timeout++ < 1000));
	if (timeout >= 1000) {
		printk_err("timed out\n");
	}

	while(1) {
		hlt();
	}

}

// This is a lot more paranoid now, since Linux can NOT handle
// being told there is a CPU when none exists. So any errors 
// will return 0, meaning no CPU. 
int start_cpu(unsigned long apicid)
{
	int timeout;
	unsigned long send_status, accept_status, start_eip;
	int j, num_starts, maxlvt;
	extern char _start[], _estart[];
	
	/*
	 * Starting actual IPI sequence...
	 */

	printk_spew("Asserting INIT.\n");

	/*
	 * Turn INIT on target chip
	 */
	apic_write_around(APIC_ICR2, SET_APIC_DEST_FIELD(apicid));

	/*
	 * Send IPI
	 */
	apic_write_around(APIC_ICR, APIC_INT_LEVELTRIG | APIC_INT_ASSERT
				| APIC_DM_INIT);

	printk_spew("Waiting for send to finish...\n");
	timeout = 0;
	do {
		printk_spew("+");
		udelay(100);
		send_status = apic_read(APIC_ICR) & APIC_ICR_BUSY;
	} while (send_status && (timeout++ < 1000));
	if (timeout >= 1000) {
		printk_err("CPU %d: First apic write timed out. Disabling\n",
			 apicid);
		// too bad. 
		printk_err("ESR is 0x%x\n", apic_read(APIC_ESR));
		if (apic_read(APIC_ESR)) {
			printk_err("Try to reset ESR\n");
			apic_write_around(APIC_ESR, 0);
			printk_err("ESR is 0x%x\n", apic_read(APIC_ESR));
		}
		return 0;
	}
	mdelay(10);

	printk_spew("Deasserting INIT.\n");

	/* Target chip */
	apic_write_around(APIC_ICR2, SET_APIC_DEST_FIELD(apicid));

	/* Send IPI */
	apic_write_around(APIC_ICR, APIC_INT_LEVELTRIG | APIC_DM_INIT);
	
	printk_spew("Waiting for send to finish...\n");
	timeout = 0;
	do {
		printk_spew("+");
		udelay(100);
		send_status = apic_read(APIC_ICR) & APIC_ICR_BUSY;
	} while (send_status && (timeout++ < 1000));
	if (timeout >= 1000) {
		printk_err("CPU %d: Second apic write timed out. Disabling\n",
			 apicid);
		// too bad. 
		return 0;
	}

	start_eip = START_CPU_SEG + (((unsigned long)_start) & 0xf000);
	if ((((unsigned long)_start) & 0xfff) != 0) {
		printk_err("_start is not 4K aligned!\n");
		return 0;
	}
	memcpy((void *)start_eip, _start, _estart - _start);
	printk_spew("start_eip=0x%08lx\n", start_eip);
       
	num_starts = 2;

	/*
	 * Run STARTUP IPI loop.
	 */
	printk_spew("#startup loops: %d.\n", num_starts);

	maxlvt = 4;

	for (j = 1; j <= num_starts; j++) {
		printk_spew("Sending STARTUP #%d to %u.\n", j, apicid);
		apic_read_around(APIC_SPIV);
		apic_write(APIC_ESR, 0);
		apic_read(APIC_ESR);
		printk_spew("After apic_write.\n");

		/*
		 * STARTUP IPI
		 */

		/* Target chip */
		apic_write_around(APIC_ICR2, SET_APIC_DEST_FIELD(apicid));

		/* Boot on the stack */
		/* Kick the second */
		apic_write_around(APIC_ICR, APIC_DM_STARTUP
					| (start_eip >> 12));

		/*
		 * Give the other CPU some time to accept the IPI.
		 */
		udelay(300);

		printk_spew("Startup point 1.\n");

		printk_spew("Waiting for send to finish...\n");
		timeout = 0;
		do {
			printk_spew("+");
			udelay(100);
			send_status = apic_read(APIC_ICR) & APIC_ICR_BUSY;
		} while (send_status && (timeout++ < 1000));

		/*
		 * Give the other CPU some time to accept the IPI.
		 */
		udelay(200);
		/*
		 * Due to the Pentium erratum 3AP.
		 */
		if (maxlvt > 3) {
			apic_read_around(APIC_SPIV);
			apic_write(APIC_ESR, 0);
		}
		accept_status = (apic_read(APIC_ESR) & 0xEF);
		if (send_status || accept_status)
			break;
	}
	printk_spew("After Startup.\n");
	if (send_status)
		printk_warning("APIC never delivered???\n");
	if (accept_status)
		printk_warning("APIC delivery error (%lx).\n", accept_status);
	if (send_status || accept_status)
		return 0;
	return 1;
}


void startup_other_cpus(unsigned long *processor_map)
{
	unsigned long apicid = this_processors_id();
	int i;

	/* Assume the cpus are densly packed by apicid */
	for(i = 0; i < MAX_CPUS; i++) {
		unsigned long cpu_apicid = initial_apicid[i];
		if (cpu_apicid == apicid ) {
			continue;
		}
		if (!start_cpu(cpu_apicid)) {
			/* Put an error in processor_map[i]? */
			printk_err("CPU %d/%u would not start!\n",
				i, cpu_apicid);
		}
	}
}
