/*
	applicable documents:
		Intel document: 82093AA I/O ADVANCED PROGRAMMABLE INTERRUPT CONTROLLER (IOAPIC)
		Intel document: IA-32 Intel® Architecture Software Developer’s Manual Volume 3: System Programming Guide (Chapter 8)
		Intel document: APPENDIX B MODEL-SPECIFIC REGISTERS (MSRS)
*/
#include "ntddk.h"

#pragma pack (1)
typedef struct _IOAPIC {
	UCHAR select;
	UCHAR __reserved[15]; // fill up the rest of the space
	ULONG data;
} IOAPIC, *PIOAPIC;

typedef struct _APIC {
/*000*/ struct { ULONG __reserved[4]; } __reserved_01;
/*010*/ struct { ULONG __reserved[4]; } __reserved_02;
/*020*/ struct { /* APIC ID Register */
                ULONG   __reserved_1      : 24,
                        phys_apic_id    :  4,
                        __reserved_2    :  4;
                ULONG __reserved[3];
        } id;
/*030*/ const struct { /* APIC Version Register */
                ULONG   version           :  8,
                        __reserved_1    :  8,
                        max_lvt         :  8,
                        __reserved_2    :  8;
                ULONG __reserved[3];
        } version;
/*040*/ struct { ULONG __reserved[4]; } __reserved_03;
/*050*/ struct { ULONG __reserved[4]; } __reserved_04;
/*060*/ struct { ULONG __reserved[4]; } __reserved_05;
/*070*/ struct { ULONG __reserved[4]; } __reserved_06;
/*080*/ struct { /* Task Priority Register */
                ULONG   priority  :  8,
                        __reserved_1    : 24;
                ULONG __reserved_2[3];
        } tpr;
/*090*/ const struct { /* Arbitration Priority Register */
                ULONG   priority  :  8,
                        __reserved_1    : 24;
                ULONG __reserved_2[3];
        } apr;
/*0A0*/ const struct { /* Processor Priority Register */
                ULONG   priority  :  8,
                        __reserved_1    : 24;
                ULONG __reserved_2[3];
        } ppr;
/*0B0*/ struct { /* End Of Interrupt Register */
                ULONG   eoi;
                ULONG __reserved[3];
        } eoi;
/*0C0*/ struct { ULONG __reserved[4]; } __reserved_07;
/*0D0*/ struct { /* Logical Destination Register */
                ULONG   __reserved_1      : 24,
                        logical_dest    :  8;
                ULONG __reserved_2[3];
        } ldr;
/*0E0*/ struct { /* Destination Format Register */
                ULONG   __reserved_1      : 28,
                        model           :  4;
                ULONG __reserved_2[3];
        } dfr;
/*0F0*/ struct { /* Spurious Interrupt Vector Register */
                ULONG     spurious_vector :  8,
                        apic_enabled    :  1,
                        focus_cpu       :  1,
                        __reserved_2    : 22;
                ULONG __reserved_3[3];
        } svr;
/*100*/ struct { /* In Service Register */
/*170*/         ULONG bitfield;
                ULONG __reserved[3];
        } isr [8];
/*180*/ struct { /* Trigger Mode Register */
/*1F0*/         ULONG bitfield;
                ULONG __reserved[3];
        } tmr [8];
/*200*/ struct { /* Interrupt Request Register */
/*270*/         ULONG bitfield;
                ULONG __reserved[3];
        } irr [8];
/*280*/ union { /* Error Status Register */
                struct {
                        ULONG   send_EIP_error                     :  1,
                                receive_EIP_error                :  1,
                                send_accept_error               :  1,
                                receive_accept_error            :  1,
                                __reserved_1                    :  1,
                                send_illegal_vector             :  1,
                                receive_illegal_vector          :  1,
                                illegal_register_address        :  1,
                                __reserved_2                    : 24;
                        ULONG __reserved_3[3];
                } error_bits;
                struct {
                        ULONG errors;
                        ULONG __reserved_3[3];
                } all_errors;
        } esr;
/*290*/ struct { ULONG __reserved[4]; } __reserved_08;
/*2A0*/ struct { ULONG __reserved[4]; } __reserved_09;
/*2B0*/ struct { ULONG __reserved[4]; } __reserved_10;
/*2C0*/ struct { ULONG __reserved[4]; } __reserved_11;
/*2D0*/ struct { ULONG __reserved[4]; } __reserved_12;
/*2E0*/ struct { ULONG __reserved[4]; } __reserved_13;
/*2F0*/ struct { ULONG __reserved[4]; } __reserved_14;
/*300*/ struct { /* Interrupt Command Register 1 */
                ULONG   vector                    :  8,
                        delivery_mode           :  3,
                        destination_mode        :  1,
                        delivery_status         :  1,
                        __reserved_1            :  1,
                        level                   :  1,
                        trigger                 :  1,
                        __reserved_2            :  2,
                        shorthand               :  2,
                        __reserved_3            :  12;
                ULONG __reserved_4[3];
        } icr1;
/*310*/ struct { /* Interrupt Command Register 2 */
                union {
                        ULONG   __reserved_1      : 24,
                                phys_dest       :  4,
                                __reserved_2    :  4;
                        ULONG   __reserved_3      : 24,
                                logical_dest    :  8;
                } dest;
                ULONG __reserved_4[3];
        } icr2;
/*320*/ struct { /* LVT - Timer */
                ULONG   vector            :  8,
                        __reserved_1    :  4,
                        delivery_status :  1,
                        __reserved_2    :  3,
                        mask            :  1,
                        timer_mode      :  1,
                        __reserved_3    : 14;
                ULONG __reserved_4[3];
        } lvt_timer;
/*330*/ struct { ULONG __reserved[4]; } __reserved_15;
/*340*/ struct { /* LVT - Performance Counter */
                ULONG   vector            :  8,
                        delivery_mode   :  3,
                        __reserved_1    :  1,
                        delivery_status :  1,
                        __reserved_2    :  3,
                        mask            :  1,
                        __reserved_3    : 15;
                ULONG __reserved_4[3];
        } lvt_pc;
/*350*/ struct { /* LVT - LINT0 */
                ULONG   vector            :  8,
                        delivery_mode   :  3,
                        __reserved_1    :  1,
                        delivery_status :  1,
                        polarity        :  1,
                        remote_irr      :  1,
                        trigger         :  1,
                        mask            :  1,
                        __reserved_2    : 15;
                ULONG __reserved_3[3];
        } lvt_lint0;
/*360*/ struct { /* LVT - LINT1 */
                ULONG   vector            :  8,
                        delivery_mode   :  3,
                        __reserved_1    :  1,
                        delivery_status :  1,
                        polarity        :  1,
                        remote_irr      :  1,
                        trigger         :  1,
                        mask            :  1,
                        __reserved_2    : 15;
                ULONG __reserved_3[3];
        } lvt_lint1;
/*370*/ struct { /* LVT - Error */
                ULONG   vector            :  8,
                        __reserved_1    :  4,
                        delivery_status :  1,
                        __reserved_2    :  3,
                        mask            :  1,
                        __reserved_3    : 15;
                ULONG __reserved_4[3];
        } lvt_error;
/*380*/ struct { /* Timer Initial Count Register */
                ULONG   initial_count;
                ULONG __reserved_2[3];
        } timer_icr;
/*390*/ const struct { /* Timer Current Count Register */
                ULONG   curr_count;
                ULONG __reserved_2[3];
        } timer_ccr;
/*3A0*/ struct { ULONG __reserved[4]; } __reserved_16;
/*3B0*/ struct { ULONG __reserved[4]; } __reserved_17;
/*3C0*/ struct { ULONG __reserved[4]; } __reserved_18;
/*3D0*/ struct { ULONG __reserved[4]; } __reserved_19;
/*3E0*/ struct { /* Timer Divide Configuration Register */
                ULONG   divisor           :  4,
                        __reserved_1    : 28;
                ULONG __reserved_2[3];
        } timer_dcr;
/*3F0*/ struct { ULONG __reserved[4]; } __reserved_20;
} APIC, *PAPIC;

PAPIC apic = NULL;
PIOAPIC ioapic = NULL;

ULONG ioapic_read(UCHAR select)
{
	ULONG temp = 0;

	if (ioapic)
	{
		__asm cli; // disable interrupts
		ioapic->select = select;
		temp = ioapic->data;
		__asm sti; // enable them again
	}
	return temp;
}

void ioapic_write(UCHAR select, ULONG data)
{
	if (ioapic)
	{
		__asm cli; // disable interrupts
		ioapic->select = select;
		ioapic->data = data;
		__asm sti; // enable them again
	}
}

void apic_send_eoi()
{
	if (apic)
	{
		apic->eoi.eoi = 0x00000000; // writing 0 to the apic's eoi address resets it
	}
}

void apic_cleanup()
{
	if (apic) MmUnmapIoSpace(apic,sizeof(APIC));
	if (ioapic) MmUnmapIoSpace(ioapic,sizeof(IOAPIC));
}

UCHAR ioapic_get_vector(UCHAR IRQ)
{
	 //0x10 corresponds to IRQ0, and each IRQ entry occupies 2 DWORDS
	return (UCHAR)(ioapic_read(0x10+IRQ*2) & 0xFF);
}

// returns TRUE if an IO APIC is found
BOOLEAN setup_apic()
{
	PHYSICAL_ADDRESS pa;
	ULONG hasLocal_APIC;
	BOOLEAN hasIO_APIC = 0;
	ULONG ver;

	pa.HighPart = 0; // used for >4GB

	// detect local apic
	__asm {
		mov eax, 1
		cpuid
		shr edx, 9 // if there is a local apic, then bit 9 will be set
		and edx, 0x1
		mov hasLocal_APIC, edx
	}

	if (hasLocal_APIC)
	{
		pa.LowPart = 0xFEE00000;

		__asm {
			mov ecx, 0x1B // offset into apicbase
			rdmsr //Load MSR specified by ECX into EDX:EAX
			mov edx, eax
			and edx, 0xFFFFF000 // bits 31:12 contain the APICBASE address for the local apic
			mov pa.LowPart, edx
		}

		KdPrint( ("Local APIC detected using 0x%08X\n", pa.LowPart) );

		// map the apic to our memory space
		apic = (PAPIC)MmMapIoSpace(pa,sizeof(APIC), MmNonCached);


		// TO DO, figure out a better way of detecting an IO APIC
		// need to know how to access the PIIX4 registers, fields x,y of the
		// APIC Base Address Relocation Register to determine IOAPIC's memory address
		// a.k.a. offset 80h in the PIIX4 registers (function 0)

		pa.LowPart = 0xFEC00000; // default memory address of IOAPIC
		hasIO_APIC = 1;

		if (hasIO_APIC)
		{
			ioapic = (PIOAPIC)MmMapIoSpace(pa,sizeof(IOAPIC), MmNonCached); // map physical memory as non cached memory

			// this isn't the best way of detecting if the ioapic is here because we are doing a (potentially) destructive memory write
			ver = ioapic_read(0x01);
			// if there are 24(0x17) Programmable Interrupts AND IOAPIC version is 0x11 or higher
			if ((ver & 0x00FF0000) == 0x00170000 && (ver & 0x000000FF) >= 0x11)
			{
				KdPrint( ("IO APIC detected at 0x%08X\n", pa.LowPart) );
			} else {
				KdPrint( ("IO APIC NOT detected at 0x%08X version specified: 0x%08X\n", pa.LowPart, ver) );
				MmUnmapIoSpace(ioapic,sizeof(IOAPIC));
				ioapic = NULL;
				hasIO_APIC = 0;
			}
		}
	}

	return hasIO_APIC;
}
