/*
 * MemTest86+ V5 Specific code (GPL V2.0)
 * By Samuel DEMEULEMEESTER, sdemeule@memtest.org
 * http://www.canardpc.com - http://www.memtest.org
 * ------------------------------------------------
 * smp.c - MemTest-86  Version 3.5
 *
 * Released under version 2 of the Gnu Public License.
 * By Chris Brady
 */

#include "stddef.h"
#include "stdint.h"
#include "cpuid.h"
#include "smp.h"
#include "test.h"

#define DELAY_FACTOR 1
unsigned num_cpus = 1; // There is at least one cpu, the BSP
int act_cpus;
unsigned found_cpus = 0;

extern void memcpy(void *dst, void *src , int len);
extern void test_start(void);
extern int run_cpus;
extern int maxcpus;
extern char cpu_mask[];
extern struct cpu_ident cpu_id;

struct barrier_s *barr;

void smp_find_cpus();

void barrier_init(int max)
{
    /* Set the adddress of the barrier structure */
    barr = (struct barrier_s *)0x9ff00;
    barr->lck.slock = 1;
    barr->mutex.slock = 1;
    barr->maxproc = max;
    barr->count = max;
    barr->st1.slock = 1;
    barr->st2.slock = 0;
}

void s_barrier_init(int max)
{
    barr->s_lck.slock = 1;
    barr->s_maxproc = max;
    barr->s_count = max;
    barr->s_st1.slock = 1;
    barr->s_st2.slock = 0;
}

void barrier()
{
    if (num_cpus == 1 || vv->fail_safe & 3) {
        return;
    }
    spin_wait(&barr->st1);     /* Wait if the barrier is active */
    spin_lock(&barr->lck);	   /* Get lock for barr struct */
    if (--barr->count == 0) {  /* Last process? */
        barr->st1.slock = 0;   /* Hold up any processes re-entering */
        barr->st2.slock = 1;   /* Release the other processes */
        barr->count++;
        spin_unlock(&barr->lck); 
    } else {
        spin_unlock(&barr->lck); 
        spin_wait(&barr->st2);	/* wait for peers to arrive */
        spin_lock(&barr->lck);   
        if (++barr->count == barr->maxproc) { 
            barr->st1.slock = 1; 
            barr->st2.slock = 0; 
        }
        spin_unlock(&barr->lck); 
    }
}

void s_barrier()
{
    if (run_cpus == 1 || vv->fail_safe & 3) {
        return;
    }
    spin_wait(&barr->s_st1);     /* Wait if the barrier is active */
    spin_lock(&barr->s_lck);     /* Get lock for barr struct */
    if (--barr->s_count == 0) {  /* Last process? */
        barr->s_st1.slock = 0;   /* Hold up any processes re-entering */
        barr->s_st2.slock = 1;   /* Release the other processes */
        barr->s_count++;
        spin_unlock(&barr->s_lck); 
    } else {
        spin_unlock(&barr->s_lck); 
        spin_wait(&barr->s_st2);	/* wait for peers to arrive */
        spin_lock(&barr->s_lck);   
        if (++barr->s_count == barr->s_maxproc) { 
            barr->s_st1.slock = 1; 
            barr->s_st2.slock = 0; 
        }
        spin_unlock(&barr->s_lck); 
    }
}

typedef struct {
    bool started;
} ap_info_t;

volatile apic_register_t *APIC = NULL;
/* CPU number to APIC ID mapping table. CPU 0 is the BSP. */
static unsigned cpu_num_to_apic_id[MAX_CPUS];
volatile ap_info_t AP[MAX_CPUS];

void PUT_MEM16(uintptr_t addr, uint16_t val)
{
    *((volatile uint16_t *)addr) = val;
}

void PUT_MEM32(uintptr_t addr, uint32_t val)
{
    *((volatile uint32_t *)addr) = val;
}

static void inline 
APIC_WRITE(unsigned reg, uint32_t val)
{
    APIC[reg][0] = val;
}

static inline uint32_t 
APIC_READ(unsigned reg)
{
    return APIC[reg][0];
}


static void 
SEND_IPI(unsigned apic_id, unsigned trigger, unsigned level, unsigned mode,
         uint8_t vector)
{
    uint32_t v;

    v = APIC_READ(APICR_ICRHI) & 0x00ffffff;
    APIC_WRITE(APICR_ICRHI, v | (apic_id << 24));

    v = APIC_READ(APICR_ICRLO) & ~0xcdfff;
    v |= (APIC_DEST_DEST << APIC_ICRLO_DEST_OFFSET) 
        | (trigger << APIC_ICRLO_TRIGGER_OFFSET)
        | (level << APIC_ICRLO_LEVEL_OFFSET)
        | (mode << APIC_ICRLO_DELMODE_OFFSET)
        | (vector);
    APIC_WRITE(APICR_ICRLO, v);
}


// Silly way of busywaiting, but we don't have a timer
void delay(unsigned us) 
{
    unsigned freq = 1000; // in MHz, assume 1GHz CPU speed
    uint64_t cycles = us * freq;
    uint64_t t0 = RDTSC();
    uint64_t t1;
    volatile unsigned k;

    do {
        for (k = 0; k < 1000; k++) continue;
        t1 = RDTSC();
    } while (t1 - t0 < cycles);
}

static inline void
memset (volatile void *dst,
        char  value,
        int   len)
{
    int i;
    for (i = 0 ; i < len ; i++ ) { 
        *((char *) dst + i) = value;
    }
}
void initialise_cpus(void)
{
    int i;

    act_cpus = 0;

    if (maxcpus > 1) {
        smp_find_cpus();
        /* The total number of CPUs may be limited */
        if (num_cpus > maxcpus) {
            num_cpus = maxcpus;
        }
        /* Determine how many cpus have been selected */
        for(i = 0; i < num_cpus; i++) {
            if (cpu_mask[i]) {
                act_cpus++;
            }
        }
    } else {
        act_cpus = found_cpus = num_cpus = 1;
    }

    /* Initialize the barrier before starting AP's */
    barrier_init(act_cpus);

    /* let the BSP initialise the APs. */
    for(i = 1; i < num_cpus; i++) {
        /* Only start this CPU if it is selected by the mask */
        if (cpu_mask[i]) {
            smp_boot_ap(i);
        }
    }

}
void kick_cpu(unsigned cpu_num)
{
    unsigned num_sipi, apic_id;
    apic_id = cpu_num_to_apic_id[cpu_num];

    // clear the APIC ESR register
    APIC_WRITE(APICR_ESR, 0);
    APIC_READ(APICR_ESR);

    // asserting the INIT IPI
    SEND_IPI(apic_id, APIC_TRIGGER_LEVEL, 1, APIC_DELMODE_INIT, 0);
    delay(100000 / DELAY_FACTOR);

    // de-assert the INIT IPI
    SEND_IPI(apic_id, APIC_TRIGGER_LEVEL, 0, APIC_DELMODE_INIT, 0);

    for (num_sipi = 0; num_sipi < 2; num_sipi++) {
        unsigned timeout;
        bool send_pending;
        unsigned err;

        APIC_WRITE(APICR_ESR, 0);

        SEND_IPI(apic_id, 0, 0, APIC_DELMODE_STARTUP, (unsigned)startup_32 >> 12);

        timeout = 0;
        do {
            delay(10);
            timeout++;
            send_pending = (APIC_READ(APICR_ICRLO) & APIC_ICRLO_STATUS_MASK) != 0;
        } while (send_pending && timeout < 1000);

        if (send_pending) {
            cprint(LINE_STATUS+3, 0, "SMP: STARTUP IPI was never sent");
        }
      
        delay(100000 / DELAY_FACTOR);

        err = APIC_READ(APICR_ESR) & 0xef;
        if (err) {
            cprint(LINE_STATUS+3, 0, "SMP: After STARTUP IPI: err = 0x");
            hprint(LINE_STATUS+3, COL_MID, err);
        }
    }
}

// These memory locations are used for the trampoline code and data.

#define BOOTCODESTART 0x9000
#define GDTPOINTERADDR 0x9100
#define GDTADDR 0x9110

void boot_ap(unsigned cpu_num)
{
    unsigned num_sipi, apic_id;
    extern uint8_t gdt; 
    extern uint8_t _ap_trampoline_start;
    extern uint8_t _ap_trampoline_protmode;
    unsigned len = &_ap_trampoline_protmode - &_ap_trampoline_start;
    apic_id = cpu_num_to_apic_id[cpu_num];


    memcpy((uint8_t*)BOOTCODESTART, &_ap_trampoline_start, len);

    // Fixup the LGDT instruction to point to GDT pointer.
    PUT_MEM16(BOOTCODESTART + 3, GDTPOINTERADDR);

    // Copy a pointer to the temporary GDT to addr GDTPOINTERADDR.
    // The temporary gdt is at addr GDTADDR
    PUT_MEM16(GDTPOINTERADDR, 4 * 8);
    PUT_MEM32(GDTPOINTERADDR + 2, GDTADDR);

    // Copy the first 4 gdt entries from the currently used GDT to the
    // temporary GDT.
    memcpy((uint8_t *)GDTADDR, &gdt, 32);

    // clear the APIC ESR register
    APIC_WRITE(APICR_ESR, 0);
    APIC_READ(APICR_ESR);

    // asserting the INIT IPI
    SEND_IPI(apic_id, APIC_TRIGGER_LEVEL, 1, APIC_DELMODE_INIT, 0);
    delay(100000 / DELAY_FACTOR);

    // de-assert the INIT IPI
    SEND_IPI(apic_id, APIC_TRIGGER_LEVEL, 0, APIC_DELMODE_INIT, 0);

    for (num_sipi = 0; num_sipi < 2; num_sipi++) {
        unsigned timeout;
        bool send_pending;
        unsigned err;

        APIC_WRITE(APICR_ESR, 0);

        SEND_IPI(apic_id, 0, 0, APIC_DELMODE_STARTUP, BOOTCODESTART >> 12);

        timeout = 0;
        do {
            delay(10);
            timeout++;
            send_pending = (APIC_READ(APICR_ICRLO) & APIC_ICRLO_STATUS_MASK) != 0;
        } while (send_pending && timeout < 1000);

        if (send_pending) {
            cprint(LINE_STATUS+3, 0, "SMP: STARTUP IPI was never sent");
        }
      
        delay(100000 / DELAY_FACTOR);

        err = APIC_READ(APICR_ESR) & 0xef;
        if (err) {
            cprint(LINE_STATUS+3, 0, "SMP: After STARTUP IPI: err = 0x");
            hprint(LINE_STATUS+3, COL_MID, err);
        }
    }
}

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

    while (len--) {
        sum += *mp++;
    }
    return (sum & 0xFF);
}

/* Parse an MP config table for CPU information */
bool read_mp_config_table(uintptr_t addr)
{
    mp_config_table_header_t *mpc = (mp_config_table_header_t*)addr;
    uint8_t *tab_entry_ptr;
    uint8_t *mpc_table_end;

    if (mpc->signature != MPCSignature) {
        return FALSE;
    }
    if (checksum((unsigned char*)mpc, mpc->length) != 0) {
        return FALSE;
    }

    /* FIXME: the uintptr_t cast here works around a compilation problem on
     * AMD64, but it ignores the real problem, which is that lapic_addr
     * is only 32 bits.  Maybe that's OK, but it should be investigated.
     */
    APIC = (volatile apic_register_t*)(uintptr_t)mpc->lapic_addr;

    tab_entry_ptr = ((uint8_t*)mpc) + sizeof(mp_config_table_header_t);
    mpc_table_end = ((uint8_t*)mpc) + mpc->length;
      
    while (tab_entry_ptr < mpc_table_end) {
        switch (*tab_entry_ptr) {
        case MP_PROCESSOR: {
            mp_processor_entry_t *pe = (mp_processor_entry_t*)tab_entry_ptr;
	
            if (pe->cpu_flag & CPU_BOOTPROCESSOR) {
                // BSP is CPU 0
                cpu_num_to_apic_id[0] = pe->apic_id;
            } else if (num_cpus < MAX_CPUS) {
                cpu_num_to_apic_id[num_cpus] = pe->apic_id;
                num_cpus++;
            }
            found_cpus++;
					    
            // we cannot handle non-local 82489DX apics
            if ((pe->apic_ver & 0xf0) != 0x10) {
                return 0;
            }
				
            tab_entry_ptr += sizeof(mp_processor_entry_t);
            break;
        }
        case MP_BUS: {
            tab_entry_ptr += sizeof(mp_bus_entry_t);
            break;
        }
        case MP_IOAPIC: {
            tab_entry_ptr += sizeof(mp_io_apic_entry_t);
            break;
        }
        case MP_INTSRC:
            tab_entry_ptr += sizeof(mp_interrupt_entry_t);
            break;
        case MP_LINTSRC:
            tab_entry_ptr += sizeof(mp_local_interrupt_entry_t);
            break;
        default: 
            return FALSE;
        }
    }
    return TRUE;
}

/* Search for a Floating Pointer structure */
floating_pointer_struct_t *
scan_for_floating_ptr_struct(uintptr_t addr, uint32_t length)
{
    floating_pointer_struct_t *fp;
    uintptr_t end = addr + length;


    while ((uintptr_t)addr < end) {
        fp = (floating_pointer_struct_t*)addr;
        if (*(unsigned int *)addr == FPSignature && fp->length == 1 && checksum((unsigned char*)addr, 16) == 0 &&	((fp->spec_rev == 1) || (fp->spec_rev == 4))) {
            return fp;
        }
        addr += 4;
    }
    return NULL;
}

/* Search for a Root System Descriptor Pointer */
rsdp_t *scan_for_rsdp(uintptr_t addr, uint32_t length)
{
    rsdp_t *rp;
    uintptr_t end = addr + length;


    while ((uintptr_t)addr < end) {
        rp = (rsdp_t*)addr;
        if (*(unsigned int *)addr == RSDPSignature && 
            checksum((unsigned char*)addr, rp->length) == 0) {
            return rp;
        }
        addr += 4;
    }
    return NULL;
}

/* Parse a MADT table for processor entries */
int parse_madt(uintptr_t addr) {

    mp_config_table_header_t *mpc = (mp_config_table_header_t*)addr;
    uint8_t *tab_entry_ptr;
    uint8_t *mpc_table_end;

    if (checksum((unsigned char*)mpc, mpc->length) != 0) {
        return FALSE;
    }

    APIC = (volatile apic_register_t*)(uintptr_t)mpc->lapic_addr;

    tab_entry_ptr = ((uint8_t*)mpc) + sizeof(mp_config_table_header_t);
    mpc_table_end = ((uint8_t*)mpc) + mpc->length;
    while (tab_entry_ptr < mpc_table_end) {
		
        madt_processor_entry_t *pe = (madt_processor_entry_t*)tab_entry_ptr;
        if (pe->type == MP_PROCESSOR) {
            if (pe->enabled) {
                if (num_cpus < MAX_CPUS) {
                    cpu_num_to_apic_id[num_cpus] = pe->apic_id;
		
                    /* the first CPU is the BSP, don't increment */
                    if (found_cpus) {
                        num_cpus++;
                    }
                }
                found_cpus++;
            }
        }
        tab_entry_ptr += pe->length;
    }
    return TRUE;
}

/* This is where we search for SMP information in the following order
 * look for a floating MP pointer
 *   found:
 *     check for a default configuration
 * 	 found:
 *	   setup config, return
 *     check for a MP config table
 *	 found:
 *	   validate:
 *           good:
 *	        parse the MP config table
 *		  good:
 *		    setup config, return
 *
 * find & validate ACPI RSDP (Root System Descriptor Pointer)
 *   found:
 *     find & validate RSDT (Root System Descriptor Table)
 *       found:
 *         find & validate MSDT
 *	     found:
 *             parse the MADT table
 *               good:
 *		   setup config, return
 */
void smp_find_cpus()
{
    floating_pointer_struct_t *fp;
    rsdp_t *rp;
    rsdt_t *rt;
    uint8_t *tab_ptr, *tab_end;
    unsigned int *ptr;
    unsigned int uiptr;

    if(vv->fail_safe & 3) { return; }

    memset(&AP, 0, sizeof AP);

    if(vv->fail_safe & 8)
    {		
        // Search for the Floating MP structure pointer
        fp = scan_for_floating_ptr_struct(0x0, 0x400);
        if (fp == NULL) {
            fp = scan_for_floating_ptr_struct(639*0x400, 0x400);
        }
        if (fp == NULL) {
            fp = scan_for_floating_ptr_struct(0xf0000, 0x10000);
        }
        if (fp == NULL) {
            // Search the BIOS ESDS area
            unsigned int address = *(unsigned short *)0x40E;
            address <<= 4;
            if (address) {
                fp = scan_for_floating_ptr_struct(address, 0x400);
            }
        }
	
        if (fp != NULL) {
            // We have a floating MP pointer
            // Is this a default configuration?
				
            if (fp->feature[0] > 0 && fp->feature[0] <=7) {
                // This is a default config so plug in the numbers
                num_cpus = 2;
                APIC = (volatile apic_register_t*)0xFEE00000;
                cpu_num_to_apic_id[0] = 0;
                cpu_num_to_apic_id[1] = 1;
                return;
            }
				
            // Do we have a pointer to a MP configuration table?
            if ( fp->phys_addr != 0) {
                if (read_mp_config_table(fp->phys_addr)) {
                    // Found a good MP table, done
                    return;
                }
            }
        }
    }


    /* No MP table so far, try to find an ACPI MADT table
     * We try to use the MP table first since there is no way to distinguish
     * real cores from hyper-threads in the MADT */

    /* Search for the RSDP */
    rp = scan_for_rsdp(0xE0000, 0x20000);
    if (rp == NULL) {
        /* Search the BIOS ESDS area */
        unsigned int address = *(unsigned short *)0x40E;
        address <<= 4;
        if (address) {
            rp = scan_for_rsdp(address, 0x400);
        }
    }
    
    if (rp == NULL) {
        /* RSDP not found, give up */
        return;
    }

    /* Found the RSDP, now get either the RSDT or XSDT */
    if (rp->revision >= 2) {
        rt = (rsdt_t *)rp->xrsdt[0];
			
        if (rt == 0) {
            return;
        }
        // Validate the XSDT 
        if (*(unsigned int *)rt != XSDTSignature) {
            return;
        }
        if ( checksum((unsigned char*)rt, rt->length) != 0) {
            return;
        }
			
    } else {
        rt = (rsdt_t *)rp->rsdt;
        if (rt == 0) {
            return;
        }
        /* Validate the RSDT */
        if (*(unsigned int *)rt != RSDTSignature) {
            return;
        }
        if ( checksum((unsigned char*)rt, rt->length) != 0) {
            return;
        }
    }

    /* Scan the RSDT or XSDT for a pointer to the MADT */
    tab_ptr = ((uint8_t*)rt) + sizeof(rsdt_t);
    tab_end = ((uint8_t*)rt) + rt->length;

    while (tab_ptr < tab_end) {

        uiptr = *((unsigned int *)tab_ptr);
        ptr = (unsigned int *)uiptr;

        /* Check for the MADT signature */
        if (ptr && *ptr == MADTSignature) {
            /* Found it, now parse it */
            if (parse_madt((uintptr_t)ptr)) {
                return;
            }
        }
        tab_ptr += 4;
    }
}
	
unsigned my_apic_id()
{
    return (APIC[APICR_ID][0]) >> 24;
}

void smp_ap_booted(unsigned cpu_num) 
{
    AP[cpu_num].started = TRUE;
}

void smp_boot_ap(unsigned cpu_num)
{
    unsigned timeout;

    boot_ap(cpu_num);
    timeout = 0;
    do {
        delay(1000 / DELAY_FACTOR);
        timeout++;
    } while (!AP[cpu_num].started && timeout < 100000 / DELAY_FACTOR);

    if (!AP[cpu_num].started) {
        cprint(LINE_STATUS+3, 0, "SMP: Boot timeout for");
        dprint(LINE_STATUS+3, COL_MID, cpu_num,2,1);
        cprint(LINE_STATUS+3, 26, "Turning off SMP");
    }
}

unsigned smp_my_cpu_num()
{
    unsigned apicid = my_apic_id();
    unsigned i;

    for (i = 0; i < MAX_CPUS; i++) {
        if (apicid == cpu_num_to_apic_id[i]) {
            break;
        }
    }
    if (i == MAX_CPUS) {
        i = 0;
    }
    return i;
}

/* A set of simple functions used to preserve assigned CPU ordinals since
 * they are lost after relocation (the stack is reloaded).
 */
int num_to_ord[MAX_CPUS];
void smp_set_ordinal(int me, int ord)
{
    num_to_ord[me] = ord;
}

int smp_my_ord_num(int me)
{
    return num_to_ord[me];
}

int smp_ord_to_cpu(int me)
{
    int i;
    for (i=0; i<MAX_CPUS; i++) {
        if (num_to_ord[i] == me) return i;
    }
    return -1;
}
