/*++

Copyright (c) 1997  Microsoft Corporation
All rights reserved

Module Name:

    acpisetd.c

Abstract:

    This module detects an ACPI system.  It
    is included into setup so that setup
    can figure out which HAL to load

Author:

    Jake Oshins (jakeo) - Feb. 7, 1997.

Environment:

    Textmode setup.

Revision History:


--*/
#include "bootx86.h"

#include "stdlib.h"
#include "string.h"

VOID
BlPrint(
    PCHAR cp,
    ...
    );


#ifdef DEBUG
#undef DEBUG_PRINT
#define DEBUG_PRINT BlPrint
#else
#define DEBUG_PRINT
#endif

typedef struct _ACPI_BIOS_INSTALLATION_CHECK {
    UCHAR Signature[8];             // "RSD PTR" (ascii)
    UCHAR Checksum;
    UCHAR OemId[6];                 // An OEM-supplied string
    UCHAR reserved;                 // must be 0
    ULONG RsdtAddress;              // 32-bit physical address of RSDT
} ACPI_BIOS_INSTALLATION_CHECK, *PACPI_BIOS_INSTALLATION_CHECK;

#include "acpitabl.h"

PRSDP   BlRsdp;
PRSDT   BlRsdt;
PXSDT   BlXsdt;
BOOLEAN BlLegacyFree = FALSE;

PDESCRIPTION_HEADER
BlFindACPITable(
    IN PCHAR TableName,
    IN ULONG TableLength
    );

// from boot\detect\i386\acpibios.h
//
// Acpi BIOS Installation check
//
#define ACPI_BIOS_START            0xE0000
#define ACPI_BIOS_END              0xFFFFF
#define ACPI_BIOS_HEADER_INCREMENT 16

VOID
BlFindRsdp (
    VOID
    )
#define EBDA_SEGMENT_PTR    0x40e
{
    ULONG romAddr, romEnd;
    PACPI_BIOS_INSTALLATION_CHECK header;
    UCHAR sum, node = 0;
    USHORT i, nodeSize;
    ULONG EbdaSegmentPtr;
    ULONG EbdaPhysicalAdd = 0;
    PUCHAR EbdaVirtualAdd = 0;
    PHYSICAL_ADDRESS paddr;
    enum PASS { PASS1 = 0, PASS2, MAX_PASSES } pass;
    USHORT count = 0;

    //
    // Search on 16 byte boundaries for the signature of the
    // Root System Description Table structure.
    //
    for (pass = PASS1; pass < MAX_PASSES; pass++) {

        if (pass == PASS1) {

            //
            // On the first pass, we search the first 1K of the
            // Extended BIOS data area.  The EBDA segment address
            // is available at physical address 40:0E.
            //

            paddr.QuadPart = 0;
            EbdaSegmentPtr = (ULONG) MmMapIoSpace( paddr,
                                                   PAGE_SIZE,
                                                   TRUE);

            EbdaSegmentPtr += EBDA_SEGMENT_PTR;
            EbdaPhysicalAdd = *((PUSHORT)EbdaSegmentPtr);
            EbdaPhysicalAdd = EbdaPhysicalAdd << 4;

            if (EbdaPhysicalAdd) {
                paddr.HighPart = 0;
                paddr.LowPart = EbdaPhysicalAdd;
                EbdaVirtualAdd = MmMapIoSpace( paddr,
                                               2 * PAGE_SIZE,
                                               TRUE);
            }

            if (!EbdaVirtualAdd) {
                continue;
            }

            romAddr = (ULONG)EbdaVirtualAdd;
            romEnd  = romAddr + 1024;

        } else {
            //
            // On the second pass, we search (physical) memory 0xE0000
            // to 0xF0000.

            paddr.LowPart = ACPI_BIOS_START;
            romAddr = (ULONG)MmMapIoSpace(paddr,
                                          ACPI_BIOS_END - ACPI_BIOS_START,
                                          TRUE);

            romEnd  = romAddr + (ACPI_BIOS_END - ACPI_BIOS_START);
        }

        while (romAddr < romEnd) {

            header = (PACPI_BIOS_INSTALLATION_CHECK)romAddr;

            //
            // Signature to match is the string "RSD PTR ".
            //

            if (header->Signature[0] == 'R' && header->Signature[1] == 'S' &&
                header->Signature[2] == 'D' && header->Signature[3] == ' ' &&
                header->Signature[4] == 'P' && header->Signature[5] == 'T' &&
                header->Signature[6] == 'R' && header->Signature[7] == ' ' ) {

                sum = 0;
                for (i = 0; i < sizeof(ACPI_BIOS_INSTALLATION_CHECK); i++) {
                    sum += ((PUCHAR)romAddr)[i];
                }
                if (sum == 0) {
                    pass = MAX_PASSES; // leave 'for' loop
                    break;    // leave 'while' loop
                }
            }

            romAddr += ACPI_BIOS_HEADER_INCREMENT;
        }
    }

    if (romAddr >= romEnd) {
        BlRsdp = NULL;
        BlRsdt = NULL;
        return;
    }

    BlRsdp = (PRSDP)romAddr;
    paddr.LowPart = BlRsdp->RsdtAddress;
    BlRsdt = MmMapIoSpace(paddr, sizeof(RSDT), TRUE);
    BlRsdt = MmMapIoSpace(paddr, BlRsdt->Header.Length, TRUE);

#ifdef ACPI_20_COMPLIANT
    if (BlRsdp->Revision > 1) {

        //
        // ACPI 2.0 BIOS
        //

        BlXsdt = MmMapIoSpace(paddr, sizeof(XSDT), TRUE);
        BlXsdt = MmMapIoSpace(paddr, BlXsdt->Header.Length, TRUE);
    }
#endif
    return;
}

BOOLEAN
BlDetectLegacyFreeBios(
    VOID
    )
{
    PFADT   fadt;

    if (BlLegacyFree) {
        return TRUE;
    }

    BlFindRsdp();

    if (BlRsdt) {

        fadt = (PFADT)BlFindACPITable("FACP", sizeof(FADT));

        if (fadt == NULL) {
            return FALSE;
        }

        if ((fadt->Header.Revision < 2) ||
            (fadt->Header.Length <= 116)) {

            //
            // The BIOS is earlier than the legacy-free
            // additions.
            //
            return FALSE;
        }

        if (!(fadt->boot_arch & I8042)) {
            BlLegacyFree = TRUE;
            return TRUE;
        }
    }

    return FALSE;
}

PDESCRIPTION_HEADER
BlFindACPITable(
    IN PCHAR TableName,
    IN ULONG TableLength
    )
/*++

Routine Description:

    Given a table name, finds that table in the ACPI BIOS

Arguments:

    TableName - Supplies the table name

    TableLength - Supplies the length of the table to map

Return Value:

    Pointer to the table if found

    NULL if the table is not found

--*/

{
    ULONG Signature;
    PFADT Fadt;
    PDESCRIPTION_HEADER Header;
    ULONG TableCount;
    ULONG i;
    PHYSICAL_ADDRESS paddr = {0};

    Signature = *((ULONG UNALIGNED *)TableName);
    if (Signature == RSDT_SIGNATURE) {
        return(&BlRsdt->Header);
    } else if (Signature == XSDT_SIGNATURE) {
        return(&BlXsdt->Header);
    } else if (Signature == DSDT_SIGNATURE) {
        Fadt = (PFADT)BlFindACPITable("FACP", sizeof(PFADT));
        if (Fadt == NULL) {
            return(NULL);
        }
        if (BlXsdt) {
            paddr = Fadt->x_dsdt;
        } else {
#if defined(_X86_)
            paddr.LowPart = Fadt->dsdt;
#else
            paddr.QuadPart = Fadt->dsdt;
#endif
        }
        Header = MmMapIoSpace(paddr, TableLength, TRUE);
        return(Header);
    } else {

        //
        // Make sure...
        //
        if( !BlRsdt ) {
            BlFindRsdp();
        }

        if( BlRsdt ) {

            TableCount = BlXsdt ?
                NumTableEntriesFromXSDTPointer(BlXsdt) :
                NumTableEntriesFromRSDTPointer(BlRsdt);

            //
            // Sanity check.
            //
            if( TableCount > 0x100 ) {
                return(NULL);
            }

            for (i=0;i<TableCount;i++) {


                if (BlXsdt) {

                    paddr = BlXsdt->Tables[i];

                } else {

#if defined(_X86_)
                    paddr.HighPart = 0;
                    paddr.LowPart = BlRsdt->Tables[i];
#else
                    paddr.QuadPart = BlRsdt->Tables[i];
#endif
                }

                Header = MmMapIoSpace(paddr, sizeof(DESCRIPTION_HEADER), TRUE);
                if (Header == NULL) {
                    return(NULL);
                }
                if (Header->Signature == Signature) {
                    //
                    // if we need to map more than just the DESCRIPTION_HEADER, do that before
                    // returning.  Check to see if the end of the table lies past the page 
                    // boundary the header lies on.  If so, we will have to map it.
                    //
                    if ( ((paddr.LowPart + TableLength) & ~(PAGE_SIZE - 1)) >
                         ((paddr.LowPart + sizeof(DESCRIPTION_HEADER)) & ~(PAGE_SIZE - 1)) ) {
                        Header = MmMapIoSpace(paddr, TableLength, TRUE);
                    }
                    return(Header);
                }
            }
        }
    }

    return(NULL);
}