/*++

Copyright (c) 1997  Microsoft Corporation

Module Name:

    pmapic.c

Abstract:

    Implements various APIC-ACPI functions.

Author:

    Jake Oshins (jakeo) 19-May-1997

Environment:

    Kernel mode only.

Revision History:

--*/

#include "halp.h"
#include "acpitabl.h"
#include "apic.inc"
#include "xxacpi.h"
#include "ixsleep.h"

#ifdef DEBUGGING
#include "string.h"
#include "stdlib.h"
#include "stdio.h"
#endif

ULONG
DetectAcpiMP (
    OUT PBOOLEAN IsConfiguredMp,
    IN  PLOADER_PARAMETER_BLOCK LoaderBlock
    );

VOID
HalpInitMpInfo (
    IN PMAPIC ApicTable,
    IN ULONG  Phase
    );

BOOLEAN
HalpVerifyIOUnit(
    IN PUCHAR BaseAddress
    );

VOID
HalpMaskAcpiInterrupt(
    VOID
    );

VOID
HalpUnmaskAcpiInterrupt(
    VOID
    );

extern UCHAR  rgzNoApicTable[];
extern UCHAR  rgzNoApic[];
extern UCHAR  rgzBadApicVersion[];
extern UCHAR  rgzApicNotVerified[];

extern ULONG HalpPicVectorRedirect[];
extern ULONG HalpPicVectorFlags[];
extern USHORT HalpMaxApicInti[];
extern UCHAR HalpIoApicId[];
extern ULONG HalpIpiClock;
extern PVOID *HalpLocalNmiSources;

ULONG HalpIOApicVersion[MAX_IOAPICS];

extern BOOLEAN HalpHiberInProgress;

BOOLEAN HalpPicStateIntact = TRUE;
UCHAR   HalpMaxProcs = 0;

#ifdef ALLOC_PRAGMA
#pragma alloc_text(INIT, DetectAcpiMP)
#pragma alloc_text(PAGELK, HalpInitMpInfo)
#pragma alloc_text(PAGELK, HalpVerifyIOUnit)
#pragma alloc_text(PAGELK, HalpSaveInterruptControllerState)
#pragma alloc_text(PAGELK, HalpRestoreInterruptControllerState)
#pragma alloc_text(PAGELK, HalpSetInterruptControllerWakeupState)
#pragma alloc_text(PAGELK, HalpAcpiPicStateIntact)
#pragma alloc_text(PAGELK, HalpGetApicVersion)
#pragma alloc_text(PAGELK, HalpMaskAcpiInterrupt)
#pragma alloc_text(PAGELK, HalpUnmaskAcpiInterrupt)
#endif


ULONG
DetectAcpiMP(
    OUT PBOOLEAN    IsConfiguredMp,
    IN  PLOADER_PARAMETER_BLOCK LoaderBlock
    )
{
    UCHAR ApicVersion, i;
    PUCHAR  LocalApic;
#ifdef DEBUGGING
    CHAR    string[100];
#endif
    PHYSICAL_ADDRESS physicalAddress;

    //
    // Initialize MpInfo table
    //

    RtlZeroMemory (&HalpMpInfoTable, sizeof(MP_INFO));

    //
    // Set the return Values to the default
    //

    *IsConfiguredMp = FALSE;

    //
    // See if there is an APIC Table
    //

    if ((HalpApicTable = HalpGetAcpiTablePhase0(LoaderBlock, APIC_SIGNATURE)) == NULL) {
        HalDisplayString(rgzNoApicTable);
        return(FALSE);
    }

    // We have an APIC table. Initialize a HAL specific MP information
    // structure that gets information from the MAPIC table.

#ifdef DEBUGGING
    sprintf(string, "Signature: %x      Length: %x\n",
            HalpApicTable->Header.Signature,
            HalpApicTable->Header.Length);
    HalDisplayString(string);
    sprintf(string, "OEMID: %s\n", HalpApicTable->Header.OEMID);
    HalDisplayString(string);
    sprintf(string, "Local Apic Address: %x\n", HalpApicTable->LocalAPICAddress);
    HalDisplayString(string);
    sprintf(string, "Flags: %x\n", HalpApicTable->Flags);
    HalDisplayString(string);
#endif

    HalpInitMpInfo(HalpApicTable, 0);

    // Verify the information in the MAPIC table as best as we can.

    if (HalpMpInfoTable.IOApicCount == 0) {
        //
        //  Someone Has a MP Table and no IO Units -- Weird
        //  We have to assume the BIOS knew what it was doing
        //  when it built the table.  so ..
        //
        HalDisplayString (rgzNoApic);

        return (FALSE);
    }

    //
    //  It's an APIC System.  It could be a UP System though.
    //

    if (HalpMpInfoTable.ProcessorCount > 1) {
        *IsConfiguredMp = TRUE;
    }

    HalpMpInfoTable.LocalApicBase = (ULONG) HalpApicTable->LocalAPICAddress;
    physicalAddress =
        HalpPtrToPhysicalAddress( (PVOID)HalpMpInfoTable.LocalApicBase );

    LocalApic = (PUCHAR) HalpMapPhysicalMemoryWriteThrough( physicalAddress,
                                                            1 );
    HalpRemapVirtualAddress (
        (PVOID) LOCALAPIC,
        physicalAddress,
        TRUE
        );

    ApicVersion = (UCHAR) *(LocalApic + LU_VERS_REGISTER);

    if (ApicVersion > 0x1f) {
        //
        //  Only known Apics are 82489dx with version 0.x and
        //  Embedded Apics with version 1.x (where x is don't care)
        //
        //  Return of 0xFF?   Can't have an MPS system without a Local Unit.
        //

#ifdef DEBUGGING
        sprintf(string, "HALMPS: apic version %x, read from %x\n",
            ApicVersion, LocalApic + LU_VERS_REGISTER);

        HalDisplayString(string);
#endif

        HalDisplayString (rgzBadApicVersion);

        return (FALSE);
    }

    for(i=0; i < HalpMpInfoTable.IOApicCount; i++)
    {
        //
        //  Verify the existance of the IO Unit
        //


        if (!(HalpVerifyIOUnit((PUCHAR)HalpMpInfoTable.IoApicBase[i]))) {
            HalDisplayString (rgzApicNotVerified);

            return (FALSE);
        }
    }

    HalDisplayString("HAL: DetectAPIC: APIC system found - Returning TRUE\n");

    return TRUE;
}

VOID
HalpInitMpInfo (
    IN PMAPIC ApicTable,
    IN ULONG  Phase
    )

/*++
Routine Description:
    This routine initializes a HAL specific data structure that is
    used by the HAL to simplify access to MP information.

Arguments:

    ApicTable - Pointer to the APIC table.

    Phase - indicates which pass we are are doing through the table.

 Return Value:
     Pointer to the HAL MP information table.

*/
{
    PUCHAR  TraversePtr;
    UCHAR   CheckSum;
    UCHAR   apicNo = 0;
    ULONG   nmiSources = 0;
#ifdef DEBUGGING
    CHAR    string[100];
#endif
    PIO_APIC_UNIT   apic;
    PHYSICAL_ADDRESS physicalAddress;
    PIOAPIC ioApic;
    UCHAR totalProcs = 0;

    union {
        ULONG        raw;
        APIC_VERSION version;
    } versionUnion;

    // Walk the MAPIC table.

    TraversePtr = (PUCHAR) ApicTable->APICTables;

    //
    // ACPI machines have embedded APICs.
    //
    HalpMpInfoTable.ApicVersion = 0x10;

#ifdef DUMP_MAPIC_TABLE

    while ((ULONG)TraversePtr <
           ((ULONG)ApicTable + ApicTable->Header.Length)) {

        sprintf(string, "%08x  %08x  %08x  %08x\n",
                *(PULONG)TraversePtr,
                *(PULONG)(TraversePtr + 4),
                *(PULONG)(TraversePtr + 8),
                *(PULONG)(TraversePtr + 12)
                );
        HalDisplayString(string);
        TraversePtr += 16;
    }

    TraversePtr = (PUCHAR) ApicTable->APICTables;
#endif

    if (!(ApicTable->Flags & PCAT_COMPAT)) {

        //
        // This HAL can't actually handle a machine without 8259's,
        // even though it doesn't use them.
        //

        KeBugCheckEx(MISMATCHED_HAL,
                        6, 0, 0, 0);

    }

    while ((ULONG)TraversePtr <
           ((ULONG)ApicTable + ApicTable->Header.Length)) {

        if ((((PPROCLOCALAPIC)(TraversePtr))->Type == PROCESSOR_LOCAL_APIC)
           && (((PPROCLOCALAPIC)(TraversePtr))->Length == PROCESSOR_LOCAL_APIC_LENGTH)) {

#ifdef DEBUGGING
            sprintf(string, "Found a processor-local APIC: %x\n", TraversePtr);
            HalDisplayString(string);
#endif

            if (Phase == 0) {

                if(((PPROCLOCALAPIC)(TraversePtr))->Flags & PLAF_ENABLED) {

                    //
                    // This processor is enabled, so keep track of useful stuff.
                    //

                    HalpProcLocalApicTable[HalpMpInfoTable.ProcessorCount].NamespaceProcID =
                        ((PPROCLOCALAPIC)(TraversePtr))->ACPIProcessorID;

                    HalpProcLocalApicTable[HalpMpInfoTable.ProcessorCount].ApicID =
                        ((PPROCLOCALAPIC)(TraversePtr))->APICID;

                    HalpMpInfoTable.ProcessorCount += 1;
                }
            }

            totalProcs++;

            HalpMaxProcs = (totalProcs > HalpMaxProcs) ? totalProcs : HalpMaxProcs;

            TraversePtr += ((PPROCLOCALAPIC)(TraversePtr))->Length;

        } else if ((((PIOAPIC)(TraversePtr))->Type == IO_APIC) &&
           (((PIOAPIC)(TraversePtr))->Length == IO_APIC_LENGTH)) {


#ifdef DEBUGGING
            sprintf(string, "Found an IO APIC: [%x] %x\n",
                    HalpMpInfoTable.IOApicCount,
                    TraversePtr);
            HalDisplayString(string);
#endif

            ioApic = (PIOAPIC)TraversePtr;

            if (Phase == 0) {
                //
                // Found an IO APIC entry.  Record the info from
                // the table.
                //

                apicNo = (UCHAR)HalpMpInfoTable.IOApicCount;

                HalpIoApicId[apicNo] = ioApic->IOAPICID;

                HalpMpInfoTable.IoApicIntiBase[apicNo] =
                    ioApic->SystemVectorBase;

                HalpMpInfoTable.IoApicPhys[apicNo] =
                    ioApic->IOAPICAddress;

                //
                // Get a virtual address for it.
                //

                physicalAddress = HalpPtrToPhysicalAddress(
                                    (PVOID)ioApic->IOAPICAddress );

                HalpMpInfoTable.IoApicBase[apicNo] =
                    HalpMapPhysicalMemoryWriteThrough( physicalAddress, 1 );

                apic = (PIO_APIC_UNIT)HalpMpInfoTable.IoApicBase[apicNo];

                if (!apic) {
#ifdef DEBUGGING
                    sprintf(string, "Couldn't map the I/O apic\n");
                    HalDisplayString(string);
#endif
                    return;
                }

                //
                // Dig the number of Intis out of the hardware.
                //

                apic->RegisterSelect = IO_VERS_REGISTER;
                apic->RegisterWindow = 0;
                versionUnion.raw = apic->RegisterWindow;

                HalpMaxApicInti[apicNo] = versionUnion.version.MaxRedirEntries + 1;

                //
                // Also store the version so that it can be retrieved by the ACPI driver
                //

                HalpIOApicVersion[apicNo] = versionUnion.raw;

#ifdef DEBUGGING
                    sprintf(string, "GSIV base: %x  PhysAddr: %x  VirtAddr: %x  Intis: %x\n",
                            HalpMpInfoTable.IoApicVectorBase[apicNo],
                            HalpMpInfoTable.IoApicPhys[apicNo],
                            HalpMpInfoTable.IoApicBase[apicNo],
                            HalpMaxApicInti[apicNo]);

                    HalDisplayString(string);
#endif

                HalpMpInfoTable.IOApicCount += 1;
            }

            TraversePtr += ioApic->Length;

        } else if ((((PISA_VECTOR)TraversePtr)->Type == ISA_VECTOR_OVERRIDE) &&
           (((PISA_VECTOR)TraversePtr)->Length == ISA_VECTOR_OVERRIDE_LENGTH)) {

#ifdef DEBUGGING
            sprintf(string, "Found an ISA VECTOR: %x, %x -> %x, flags: %x\n",
                    TraversePtr,
                    ((PISA_VECTOR)TraversePtr)->Source,
                    ((PISA_VECTOR)TraversePtr)->GlobalSystemInterruptVector,
                    ((PISA_VECTOR)TraversePtr)->Flags
                    );
            HalDisplayString(string);
#endif

            if (Phase == 0) {

                //
                // Found an ISA vector redirection entry.
                //

                HalpPicVectorRedirect[((PISA_VECTOR)TraversePtr)->Source] =
                    ((PISA_VECTOR)TraversePtr)->GlobalSystemInterruptVector;

                HalpPicVectorFlags[((PISA_VECTOR)TraversePtr)->Source] =
                    ((PISA_VECTOR)TraversePtr)->Flags;

            }

            TraversePtr += ISA_VECTOR_OVERRIDE_LENGTH;

        } else if ((((PIO_NMISOURCE)TraversePtr)->Type == IO_NMI_SOURCE) &&
           (((PIO_NMISOURCE)TraversePtr)->Length == IO_NMI_SOURCE_LENGTH)) {

            if (Phase == 1) {

                BOOLEAN found;
                USHORT  inti;

                found = HalpGetApicInterruptDesc(0,
                                                 0,
                                                 ((PIO_NMISOURCE)TraversePtr)->GlobalSystemInterruptVector,
                                                 &inti);

                if (found) {

                    HalpIntiInfo[inti].Type = INT_TYPE_NMI;
                    HalpIntiInfo[inti].Level =
                        (((((((PIO_NMISOURCE)TraversePtr)->Flags & EL_BITS) == EL_EDGE_TRIGGERED) ||
                             ((PIO_NMISOURCE)TraversePtr)->Flags & EL_BITS) == EL_CONFORMS_WITH_BUS)
                         ? CFG_EDGE : CFG_LEVEL);
                    HalpIntiInfo[inti].Polarity =
                        ((PIO_NMISOURCE)TraversePtr)->Flags & PO_BITS;
                }
            }

            TraversePtr += IO_NMI_SOURCE_LENGTH;

        } else if ((((PLOCAL_NMISOURCE)TraversePtr)->Type == LOCAL_NMI_SOURCE) &&
           (((PLOCAL_NMISOURCE)TraversePtr)->Length == LOCAL_NMI_SOURCE_LENGTH)) {

            if (Phase == 1) {

                //
                // While running through phase 1, we should catalog local NMI sources.
                //

                if (!HalpLocalNmiSources) {

                    //
                    // Allocate enough pool to point to all the possible local NMI structures.
                    // Since there are two NMI pins on each processor, this is the number of processors
                    // times two times the size of a pointer.
                    //

                    HalpLocalNmiSources = ExAllocatePoolWithTag(NonPagedPool,
                                                                sizeof(PVOID) * HalpMaxProcs * 2,
                                                                HAL_POOL_TAG);

                    RtlZeroMemory(HalpLocalNmiSources,
                                  sizeof(PVOID) * HalpMaxProcs * 2);
                }

                HalpLocalNmiSources[nmiSources++] = (PVOID)TraversePtr;

            }

            TraversePtr += LOCAL_NMI_SOURCE_LENGTH;

        } else {
#ifdef DEBUGGING
            sprintf(string, "%x: %x \n", TraversePtr, *TraversePtr);
            HalDisplayString(string);
#endif
            //
            // Found random bits in the table.  Try the next byte and
            // see if we can make sense of it.
            //

            TraversePtr += 1;
        }
    }

    return;
}

BOOLEAN
HalpVerifyIOUnit(
    IN PUCHAR BaseAddress
    )
/*++

Routine Description:

    Verify that an IO Unit exists at the specified address

 Arguments:

    BaseAddress - Virtual address of the IO Unit to test.

 Return Value:
    BOOLEAN - TRUE if a IO Unit was found at the passed address
            - FALSE otherwise

--*/

{
    union ApicUnion {
        ULONG Raw;
        struct ApicVersion Ver;
    } Temp1, Temp2;

    struct ApicIoUnit *IoUnitPtr = (struct ApicIoUnit *) BaseAddress;

    //
    //  The documented detection mechanism is to write all zeros to
    //  the Version register.  Then read it back.  The IO Unit exists if the
    //  same result is read both times and the Version is valid.
    //

    IoUnitPtr->RegisterSelect = IO_VERS_REGISTER;
    IoUnitPtr->RegisterWindow = 0;

    IoUnitPtr->RegisterSelect = IO_VERS_REGISTER;
    Temp1.Raw = IoUnitPtr->RegisterWindow;

    IoUnitPtr->RegisterSelect = IO_VERS_REGISTER;
    IoUnitPtr->RegisterWindow = 0;

    IoUnitPtr->RegisterSelect = IO_VERS_REGISTER;
    Temp2.Raw = IoUnitPtr->RegisterWindow;

    if ((Temp1.Ver.Version != Temp2.Ver.Version) ||
        (Temp1.Ver.MaxRedirEntries != Temp2.Ver.MaxRedirEntries)) {
        //
        //  No IO Unit There
        //
        return (FALSE);
    }

    return (TRUE);
}

#ifdef DEBUGGING
struct PcMpTable *PcMpTablePtr, *PcMpDefaultTablePtrs[];

void
ComputeCheckSum(UCHAR This, UCHAR That)
{
}
#endif


VOID
HalpSaveInterruptControllerState(
    VOID
    )
{

    HalpHiberInProgress = TRUE;
}

VOID
HalpRestoreInterruptControllerState(
    VOID
    )
{
    //
    // Restore the IO APIC state
    //

    HalpRestoreIoApicRedirTable();

    HalpPicStateIntact = TRUE;
}

VOID
HalpSetInterruptControllerWakeupState(
    ULONG Context
    )
{
    LOADER_PARAMETER_BLOCK LoaderBlock;
    SLEEP_STATE_CONTEXT sleepContext;
    BOOLEAN IsMpSystem;
    ULONG   flags;
    KIRQL   OldIrql;
    KPRCB   Prcb;
    ULONG   ii;
    USHORT  inti;
    ULONG   localApicId;
    ULONG   oldProcNumber, oldProcsStarted;
    ULONG   localApicBase;

    sleepContext.AsULONG = Context;

    _asm {
        pushfd
        pop eax
        mov flags, eax
        cli
    }

    if (sleepContext.bits.Flags & SLEEP_STATE_RESTART_OTHER_PROCESSORS) {

        //
        // If you are remapping local apic, io apic and ACPI MAPIC table
        // resources, you first have to unmap the current resources!!!
        // The BIOS may have created the MAPIC table at a different place or may
        // have changed values like processor local APIC IDs. Reparse it.
        //

        ASSERT(HalpApicTable);
        oldProcNumber = HalpMpInfoTable.ProcessorCount;
        oldProcsStarted = HalpMpInfoTable.NtProcessors;
        localApicBase = HalpMpInfoTable.LocalApicBase;

        HalpUnMapIOApics();

        RtlZeroMemory (&HalpMpInfoTable, sizeof(MP_INFO));
        RtlZeroMemory(HalpProcLocalApicTable,
                      sizeof(PROC_LOCAL_APIC) * MAX_PROCESSORS);

        HalpInitMpInfo(HalpApicTable, 0);

        if (HalpMpInfoTable.ProcessorCount != oldProcNumber) {

            KeBugCheckEx(HAL_INITIALIZATION_FAILED,
                         0x2000,
                         oldProcNumber,
                         HalpMpInfoTable.ProcessorCount,
                         0);
        }

        HalpMpInfoTable.NtProcessors = oldProcsStarted;
        HalpMpInfoTable.LocalApicBase = localApicBase;

        RtlZeroMemory(&LoaderBlock, sizeof(LoaderBlock));
        RtlZeroMemory(&Prcb, sizeof(Prcb));
        LoaderBlock.Prcb = (ULONG) &Prcb;
    }

    //
    // Initialize minimum global hardware state needed.
    //

    HalpIpiClock = 0;
    HalpInitializeIOUnits();
    HalpInitializePICs(FALSE);
    HalpSet8259Mask(HalpGlobal8259Mask);

    //
    // Initialize boot processor's local APIC so it can wake other processors
    //

    HalpInitializeLocalUnit ();
    KeRaiseIrql(HIGH_LEVEL, &OldIrql);

    //
    // Wake up the other processors
    //

    if (sleepContext.bits.Flags & SLEEP_STATE_RESTART_OTHER_PROCESSORS) {

        //
        // Fill in this processor's Apic ID.
        //

        localApicId = *(PVULONG)(LOCALAPIC + LU_ID_REGISTER);

        localApicId &= APIC_ID_MASK;
        localApicId >>= APIC_ID_SHIFT;

        ((PHALPRCB)KeGetPcr()->Prcb->HalReserved)->PCMPApicID = (UCHAR)localApicId;

        //
        // Mark this processor as started.
        //

        for (ii = 0; ii < HalpMpInfoTable.NtProcessors; ii++) {

            if (HalpProcLocalApicTable[ii].ApicID ==
                ((PHALPRCB)KeGetPcr()->Prcb->HalReserved)->PCMPApicID) {

                HalpProcLocalApicTable[ii].Started = TRUE;
                HalpProcLocalApicTable[ii].Enumerated = TRUE;

                break;
            }
        }

        ASSERT(ii != HalpMpInfoTable.ProcessorCount);

        for(ii = 1; ii < HalpMpInfoTable.NtProcessors; ++ii)  {

            // Set processor number in dummy loader parameter block

            Prcb.Number = (UCHAR) ii;
            CurTiledCr3LowPart = HalpTiledCr3Addresses[ii].LowPart;
            if (!HalStartNextProcessor(&LoaderBlock, &HalpHiberProcState[ii]))  {

                //
                // We could not start a processor. This is a fatal error.
                //

                KeBugCheckEx(HAL_INITIALIZATION_FAILED,
                             0x2001,
                             oldProcNumber,
                             HalpMpInfoTable.NtProcessors,
                             0);
            }
        }
    }

    //
    // Enable the clock interrupt.
    //

    HalpGetApicInterruptDesc(
            DEFAULT_PC_BUS,
            0,
            HalpPicVectorRedirect[RTC_IRQ],
            &inti
            );

    HalpSetRedirEntry((UCHAR)inti,
                      HalpIntiInfo[inti].Entry,
                      HalpIntiInfo[inti].Destinations << DESTINATION_SHIFT);

    HalpPicStateIntact = FALSE;

    _asm {
        mov     eax, flags
        push    eax
        popfd
    }
}

BOOLEAN
HalpAcpiPicStateIntact(
    VOID
    )
{
    return HalpPicStateIntact;
}


ULONG HalpGetApicVersion(ULONG ApicNo)
{
/*++
Routine Description:

   Obtains the contents of the version register
   for a particular system IO APIC unit. These contents
   are saved by the HAL in HalpInitMpInfo.

Arguments:

   ApicNo - the number of the IO APIC Unit whose version we want.


Return Value:

   The contents of the version register for the given IO APIC unit.

   A 0 is returned if no version can be obtained because the given
   APIC number is not valid.
*/

   // If this APIC has been found by the HAL ...

   if (ApicNo < HalpMpInfoTable.IOApicCount) {

      // ... return its version

      return HalpIOApicVersion[ApicNo];
   }
   else
   {
      // Otherwise, return 0.

      return 0;
   }
}

VOID
HalpMaskAcpiInterrupt(
    VOID
    )
{
    USHORT inti = 0;
    ULONG  apicEntry;

    HalpGetApicInterruptDesc(
            DEFAULT_PC_BUS,
            0,
            HalpPicVectorRedirect[HalpFixedAcpiDescTable.sci_int_vector],
            &inti
            );

    apicEntry = HalpIntiInfo[inti].Entry;
    apicEntry |= INTERRUPT_MASKED;

    HalpSetRedirEntry((UCHAR)inti,
                      apicEntry,
                      0);


}

VOID
HalpUnmaskAcpiInterrupt(
    VOID
    )
{
    USHORT inti = 0;

    HalpGetApicInterruptDesc(
            DEFAULT_PC_BUS,
            0,
            HalpPicVectorRedirect[HalpFixedAcpiDescTable.sci_int_vector],
            &inti
            );

    HalpSetRedirEntry((UCHAR)inti,
                      HalpIntiInfo[inti].Entry,
                      HalpIntiInfo[inti].Destinations << DESTINATION_SHIFT);

}