/*++

Copyright (c) Microsoft Corporation. All rights reserved. 

You may only use this code if you agree to the terms of the Windows Research Kernel Source Code License agreement (see License.txt).
If you do not agree to the terms, do not use the code.


Module Name:

    abiosc.c

Abstract:

    This module implements ABIOS support C routines for i386 NT.

Environment:

    FLAT mode.

--*/

#include "ki.h"
#pragma hdrstop
#include "abios.h"

extern PKCOMMON_DATA_AREA KiCommonDataArea;
extern BOOLEAN KiAbiosPresent;

//
// The reason of having these variables defined in here is to isolate
// ABIOS from current system.
//

//
// KiNumberFreeSelectors defines the number of available selectors for
// ABIOS specific drivers.  This number should be the same across all
// the processors.
//

static USHORT KiNumberFreeSelectors = 0;

//
// KiFreeGdtListHead points to the head of free GDT list on the processor 0.
//

static PKFREE_GDT_ENTRY KiFreeGdtListHead = 0L;

//
// Logica Id Table to control the ownership of logical Id.
//

PKLID_TABLE_ENTRY KiLogicalIdTable;

//
// KiAbiosGdt[] defines the Starting address of GDT for each processor.
//

ULONG KiAbiosGdt[MAXIMUM_PROCESSORS];

//
// SpinLock for accessing GDTs
//

KSPIN_LOCK KiAbiosGdtLock;

//
// Spinlock for accessing Logical Id Table
//

KSPIN_LOCK KiAbiosLidTableLock;

//
// KiStack16GdtEntry defines the address of the gdt entry for 16 bit stack.
//

ULONG KiStack16GdtEntry;

VOID
KiInitializeAbiosGdtEntry (
    OUT PKGDTENTRY GdtEntry,
    IN ULONG Base,
    IN ULONG Limit,
    IN USHORT Type
    )

/*++

Routine Description:

    This function initializes a GDT entry for abios specific code.  Base,
    Limit, and Type (code, data) are set according to parameters.  All other
    fields of the entry are set to match standard system values.

    N.B. The BIG and GRANULARITY are always set to 0.

Arguments:

    GdtEntry - GDT descriptor to be filled in.

    Base - Linear address of the first byte mapped by the selector.

    Limit - Size of the selector in BYTE.

    Type - Code or Data.  All code selectors are marked readable,
            all data selectors are marked writeable.

Return Value:

    Pointer to the GDT entry.

--*/

{
    GdtEntry->LimitLow = (USHORT)(Limit & 0xffff);
    GdtEntry->BaseLow = (USHORT)(Base & 0xffff);
    GdtEntry->HighWord.Bytes.BaseMid = (UCHAR)((Base & 0xff0000) >> 16);
    GdtEntry->HighWord.Bits.Type = Type;
    GdtEntry->HighWord.Bits.Dpl = 0;
    GdtEntry->HighWord.Bits.Pres = 1;
    GdtEntry->HighWord.Bits.LimitHi = (Limit & 0xf0000) >> 16;
    GdtEntry->HighWord.Bits.Sys = 0;
    GdtEntry->HighWord.Bits.Reserved_0 = 0;
    GdtEntry->HighWord.Bits.Default_Big = 0;
    GdtEntry->HighWord.Bits.Granularity = 0;
    GdtEntry->HighWord.Bytes.BaseHi = (UCHAR)((Base & 0xff000000) >> 24);
}

ULONG
KiI386SelectorBase (
    IN USHORT Selector
    )

/*++

Routine Description:

    This function returns the base address of the specified GDT selector.

Arguments:

    Selector - Supplies the desired selector.

Return Value:

    SelectorBase - Return the base address of the specified selector;
                   (return -1L if invalid selector)


--*/

{
    PKGDTENTRY GdtEntry;


    GdtEntry = (PKGDTENTRY)(KiAbiosGetGdt() + Selector);
    if (GdtEntry->HighWord.Bits.Pres) {
        return ((ULONG)GdtEntry->BaseLow |
                (ULONG)GdtEntry->HighWord.Bytes.BaseMid << 16 |
                (ULONG)GdtEntry->HighWord.Bytes.BaseHi << 24);
    } else {
        return (ULONG)(-1L);
    }
}

NTSTATUS
KeI386GetLid(
    IN USHORT DeviceId,
    IN USHORT RelativeLid,
    IN BOOLEAN SharedLid,
    IN PDRIVER_OBJECT DriverObject,
    OUT PUSHORT LogicalId
    )

/*++

Routine Description:

    This function searches Device Blocks and Common Data Area for the
    Logical Id matching the specified Device Id.

    N.B. To speed the search, this routine ASSUMES that
    the LIDs with the same Device ID always appear consecutively in the
    Common Data Area.  IBM ABIOS doc does not explicitly specify this.
    But from the way ABIOS initializes Device Block and Function Transfer
    Table, I think the assumption is true.

Arguments:

    DeviceId - Desired Device Id.

    RelativeLid - Specifies the Nth logical Id for this device Id.  A value
                  of 0 indicates the first available Lid.

    SharedLid - A boolean value indicates if it is a shared or exclusively
                owned logical Id.

    DriverObject - Supplies a 32-bit flat pointer of the requesting device
                driver's driver object.  The DriverObject is used to establish
                the ownership of the desired LID.

    LogicalId - A pointer to a variable which will receive the Lid.

Return Value:

    STATUS_SUCCESS - If the requested LID is available.

    STATUS_ABIOS_NOT_PRESENT - If there is no ABIOS support in the system.

    STATUS_ABIOS_LID_NOT_EXIST - If the specified LID does not exist.

    STATUS_ABIOS_LID_ALREADY_OWNED - If the caller requests an exclusively
                                     owned LID.

--*/

{
    PKDB_FTT_SECTION CdaPointer;
    PKDEVICE_BLOCK DeviceBlock;
    USHORT Lid, RelativeLidCount = 1;
    ULONG Owner;
    USHORT Increment;
    KIRQL OldIrql;
    NTSTATUS Status;

    if (!KiAbiosPresent) {
        return STATUS_ABIOS_NOT_PRESENT;
    }

    if (SharedLid) {
        Owner = LID_NO_SPECIFIC_OWNER;
        Increment = 1;
    } else {
        Owner = (ULONG)DriverObject;
        Increment = 0;
    }

    //
    // If the Logical Id Table hasn't been created yet, create it now.
    //
    if (KiLogicalIdTable==NULL) {
        KiLogicalIdTable = ExAllocatePoolWithTag(NonPagedPool,
                                          NUMBER_LID_TABLE_ENTRIES *
                                          sizeof(KLID_TABLE_ENTRY),
                                          '  eK');
        if (KiLogicalIdTable == NULL) {
            return(STATUS_NO_MEMORY);
        }
        RtlZeroMemory(KiLogicalIdTable, NUMBER_LID_TABLE_ENTRIES*sizeof(KLID_TABLE_ENTRY));
    }

    //
    // For each Lid defined in Common Data Area, we check if it has non
    // empty device block and function transfer table.  If yes, we proceed
    // to check the device id.  Otherwise, we skip the Lid.
    //

    CdaPointer = (PKDB_FTT_SECTION)KiCommonDataArea + 2;
    Status = STATUS_ABIOS_LID_NOT_EXIST;

    ExAcquireSpinLock(&KiAbiosLidTableLock, &OldIrql);

    for (Lid = 2; Lid < KiCommonDataArea->NumberLids; Lid++) {
        if (CdaPointer->DeviceBlock.Selector != 0 &&
            CdaPointer->FunctionTransferTable.Selector != 0) {

            DeviceBlock = (PKDEVICE_BLOCK)(KiI386SelectorBase(
                                               CdaPointer->DeviceBlock.Selector)
                                           + (CdaPointer->DeviceBlock.Offset));
            if (DeviceBlock->DeviceId == DeviceId) {
                if (RelativeLid == RelativeLidCount || RelativeLid == 0) {
                    if (KiLogicalIdTable[Lid].Owner == 0L) {
                        KiLogicalIdTable[Lid].Owner = Owner;
                        KiLogicalIdTable[Lid].OwnerCount += Increment;
                        *LogicalId = Lid;
                        Status = STATUS_SUCCESS;
                    } else if (KiLogicalIdTable[Lid].Owner == LID_NO_SPECIFIC_OWNER) {
                        if (SharedLid) {
                            *LogicalId = Lid;
                            KiLogicalIdTable[Lid].OwnerCount += Increment;
                            Status = STATUS_SUCCESS;
                        } else {
                            Status = STATUS_ABIOS_LID_ALREADY_OWNED;
                        }
                    } else if (KiLogicalIdTable[Lid].Owner == (ULONG)DriverObject) {
                        *LogicalId = Lid;
                        Status = STATUS_SUCCESS;
                    } else if (RelativeLid != 0) {
                        Status = STATUS_ABIOS_LID_ALREADY_OWNED;
                    }
                    break;
                } else {
                    RelativeLidCount++;
                }
            }
        }
        CdaPointer++;
    }

    ExReleaseSpinLock(&KiAbiosLidTableLock, OldIrql);
    return Status;
}

NTSTATUS
KeI386ReleaseLid(
    IN USHORT LogicalId,
    IN PDRIVER_OBJECT DriverObject
    )

/*++

Routine Description:

    This function releases a logical Id.  This routine is called at ABIOS
    device driver destallation or termination.

Arguments:

    LogicalId - Logical Id to be released.

    DriverObject - Supplies a 32-bit flat pointer of the requesting device
                driver's driver object.  The DriverObject is used to check
                the ownership of the specified LID.

Return Value:

    STATUS_SUCCESS - If the requested LID is released.

    STATUS_ABIOS_NOT_PRESENT - If there is no ABIOS support in the system.

    STATUS_ABIOS_NOT_LID_OWNER - If the caller does not own the LID.

--*/

{
    KIRQL OldIrql;
    NTSTATUS Status;

    if (!KiAbiosPresent) {
        return STATUS_ABIOS_NOT_PRESENT;
    }

    ExAcquireSpinLock(&KiAbiosLidTableLock, &OldIrql);

    if (KiLogicalIdTable[LogicalId].Owner == (ULONG)DriverObject) {
        KiLogicalIdTable[LogicalId].Owner = 0L;
        Status = STATUS_SUCCESS;
    } else if (KiLogicalIdTable[LogicalId].Owner == LID_NO_SPECIFIC_OWNER) {
        KiLogicalIdTable[LogicalId].OwnerCount--;
        if (KiLogicalIdTable[LogicalId].OwnerCount == 0L) {
            KiLogicalIdTable[LogicalId].Owner = 0L;
        }
        Status = STATUS_SUCCESS;
    } else {
        Status = STATUS_ABIOS_NOT_LID_OWNER;
    }

    ExReleaseSpinLock(&KiAbiosLidTableLock, OldIrql);

    return Status;
}

NTSTATUS
KeI386AbiosCall(
    IN USHORT LogicalId,
    IN PDRIVER_OBJECT DriverObject,
    IN PUCHAR RequestBlock,
    IN USHORT EntryPoint
    )

/*++

Routine Description:

    This function calls an ABIOS service routine on behave of device driver
    using Operating System Transfer Convention.

Arguments:

    LogicalId - Logical Id for the call.

    DriverObject - Supplies a 32-bit flat pointer of the requesting device
                driver's driver object.  The DriverObject is used to verify
                the ownership of the desired LID.

    RequestBlock - A 16:16 (selector:offset) pointer to the request block.

    EntryPoint - Specifies which ABIOS entry point:

                 0 - Start Routine
                 1 - Interrupt Routine
                 2 - Timeout Routine

Return Value:

    STATUS_SUCCESS - If no error.

    STATUS_ABIOS_NOT_PRESENT - If there is no ABIOS support in the system.

    STATUS_ABIOS_INVALID_COMMAND - if the specified entry point is not supported.

    STATUS_ABIOS_INVALID_LID - If the Lid specified is invalid.

    STATUS_ABIOS_NOT_LID_OWNER - If the caller does not own this Lid.

    (Note that the request specific ABIOS returned code is in RequestBlock.)

--*/

{

    KABIOS_POINTER FuncTransferTable;
    KABIOS_POINTER DeviceBlock;
    KABIOS_POINTER AbiosFunction;
    PKFUNCTION_TRANSFER_TABLE FttPointer;

    if (!KiAbiosPresent) {
        return STATUS_ABIOS_NOT_PRESENT;
    }

    if (LogicalId >= KiCommonDataArea->NumberLids) {
        return STATUS_ABIOS_INVALID_LID;
    } else if (KiLogicalIdTable[LogicalId].Owner != (ULONG)DriverObject &&
               KiLogicalIdTable[LogicalId].Owner != LID_NO_SPECIFIC_OWNER) {
        return STATUS_ABIOS_NOT_LID_OWNER;
    } else if (EntryPoint > 2) {
        return STATUS_ABIOS_INVALID_COMMAND;
    }

    FuncTransferTable = ((PKDB_FTT_SECTION)KiCommonDataArea + LogicalId)->
                                               FunctionTransferTable;
    DeviceBlock = ((PKDB_FTT_SECTION)KiCommonDataArea + LogicalId)->DeviceBlock;
    FttPointer = (PKFUNCTION_TRANSFER_TABLE)(KiI386SelectorBase(FuncTransferTable.Selector) +
                                             (ULONG)FuncTransferTable.Offset);
    AbiosFunction = FttPointer->CommonRoutine[EntryPoint];
    KiI386CallAbios(AbiosFunction,
                    DeviceBlock,
                    FuncTransferTable,
                    *(PKABIOS_POINTER)&RequestBlock
                    );

    return STATUS_SUCCESS;
}

NTSTATUS
KeI386AllocateGdtSelectors(
    OUT PUSHORT SelectorArray,
    IN USHORT NumberOfSelectors
    )

/*++

Routine Description:

    This function allocates a set of GDT selectors for a device driver to use.
    Usually this allocation is performed at device driver initialization time
    to reserve the selectors for later use.

Arguments:

    SelectorArray - Supplies a pointer to an array of USHORT to be filled
                    in with the GDT selectors allocated.

    NumberOfSelectors - Specifies the number of selectors to be allocated.

Return Value:

    STATUS_SUCCESS - If the requested selectors are allocated.

    STATUS_ABIOS_SELECTOR_NOT_AVAILABLE - if system can not allocate the number
                               of selectors requested.

--*/

{
    PKFREE_GDT_ENTRY GdtEntry;
    KIRQL OldIrql;

    if (KiNumberFreeSelectors >= NumberOfSelectors) {
        ExAcquireSpinLock(&KiAbiosGdtLock, &OldIrql);

        //
        // The Free Gdt link list is maintained on Processor 0's GDT ONLY.
        // Because the 'selector' is an offset to the beginning of GDT and
        // it should be the same across all the processors.
        //

        KiNumberFreeSelectors = KiNumberFreeSelectors - NumberOfSelectors;
        GdtEntry = KiFreeGdtListHead;
        while (NumberOfSelectors != 0) {
            *SelectorArray++ = (USHORT)((ULONG)GdtEntry - KiAbiosGdt[0]);
            GdtEntry = GdtEntry->Flink;
            NumberOfSelectors--;
        }
        KiFreeGdtListHead = GdtEntry;
        ExReleaseSpinLock(&KiAbiosGdtLock, OldIrql);
        return STATUS_SUCCESS;
    } else {
        return STATUS_ABIOS_SELECTOR_NOT_AVAILABLE;
    }
}

NTSTATUS
KeI386ReleaseGdtSelectors(
    OUT PUSHORT SelectorArray,
    IN USHORT NumberOfSelectors
    )

/*++

Routine Description:

    This function releases a set of GDT selectors for a device driver.
    Usually this function is called at device driver termination or
    deinstallation time.

Arguments:

    SelectorArray - Supplies a pointer to an array of USHORT selectors
                    to be freed.

    NumberOfSelectors - Specifies the number of selectors to be released.

Return Value:

    STATUS_SUCCESS - If the requested LID is released.

--*/
{
    PKFREE_GDT_ENTRY GdtEntry;
    KIRQL OldIrql;
    ULONG Gdt;

    ExAcquireSpinLock(&KiAbiosGdtLock, &OldIrql);

    //
    // The Free Gdt link list is maintained on Processor 0's GDT ONLY.
    // Because the 'selector' is an offset to the beginning of GDT and
    // it should be the same across all the processors.
    //

    KiNumberFreeSelectors = KiNumberFreeSelectors + NumberOfSelectors;
    Gdt = KiAbiosGdt[0];
    while (NumberOfSelectors != 0) {
        GdtEntry = (PKFREE_GDT_ENTRY)(Gdt + *SelectorArray++);
        GdtEntry->Flink = KiFreeGdtListHead;
        KiFreeGdtListHead = GdtEntry;
        NumberOfSelectors--;
    }
    ExReleaseSpinLock(&KiAbiosGdtLock, OldIrql);
    return STATUS_SUCCESS;
}

NTSTATUS
KeI386FlatToGdtSelector(
    IN ULONG SelectorBase,
    IN USHORT Length,
    IN USHORT Selector
    )

/*++

Routine Description:

    This function converts a 32-bit flat address to a GDT selector-offset
    pair.  The segment set up is always 16-bit ring 0 data segment.

Arguments:

    SelectorBase - Supplies 32 bit flat address to be set as the base address
                   of the desired selector.

    Length - Supplies the Length of the segment.  The Length is a 16 bit value
             and zero means 64KB.

    Selector - Supplies the selector to be set up.

Return Value:

    STATUS_SUCCESS - If the requested LID is released.

    STATUS_ABIOS_NOT_PRESENT - If there is no ABIOS support in the system.

    STATUS_ABIOS_INVALID_SELECTOR - If the selector supplied is invalid.


--*/

{
    PKGDTENTRY GdtEntry, GdtEntry1;
    KIRQL OldIrql;
    ULONG i;

    if (!KiAbiosPresent) {
        return STATUS_ABIOS_NOT_PRESENT;
    }
    if (Selector < RESERVED_GDT_ENTRIES * sizeof(KGDTENTRY)) {
        return STATUS_ABIOS_INVALID_SELECTOR;
    } else {
        ExAcquireSpinLock(&KiAbiosGdtLock, &OldIrql);
        GdtEntry = (PKGDTENTRY)(KiAbiosGdt[0] + Selector);
        GdtEntry->LimitLow = (USHORT)(Length - 1);
        GdtEntry->BaseLow = LOWWORD(SelectorBase);
        GdtEntry->HighWord.Bytes.BaseMid = LOWBYTE(HIGHWORD(SelectorBase));
        GdtEntry->HighWord.Bytes.BaseHi = HIGHBYTE(HIGHWORD(SelectorBase));
        GdtEntry->HighWord.Bits.Pres = 1;
        GdtEntry->HighWord.Bits.Type = TYPE_DATA;
        GdtEntry->HighWord.Bits.Dpl = DPL_SYSTEM;
        for (i = 1; i < (ULONG)KeNumberProcessors; i++) {
            GdtEntry1 = (PKGDTENTRY)(KiAbiosGdt[i] + Selector);
            *GdtEntry1 = *GdtEntry;
        }
        ExReleaseSpinLock(&KiAbiosGdtLock, OldIrql);
        return STATUS_SUCCESS;
    }
}

VOID
Ki386InitializeGdtFreeList (
    PKFREE_GDT_ENTRY EndOfGdt
    )

/*++

Routine Description:

    This function initializes gdt free list by linking all the unused gdt
    entries to a free list.

Arguments:

    EndOfGdt - Supplies the ending address of desired GDT.

Return Value:

    None.

--*/
{
    PKFREE_GDT_ENTRY GdtEntry;

    GdtEntry = EndOfGdt - 1;
    KiFreeGdtListHead = (PKFREE_GDT_ENTRY)0;
    while (GdtEntry != (PKFREE_GDT_ENTRY)KiAbiosGetGdt() +
                        RESERVED_GDT_ENTRIES - 1) {
        if (GdtEntry->Present == 0) {
            GdtEntry->Flink = KiFreeGdtListHead;
            KiFreeGdtListHead = GdtEntry;
            KiNumberFreeSelectors++;
        }
        GdtEntry--;
    }
}

VOID
KiInitializeAbios (
    IN UCHAR Processor
    )

/*++

Routine Description:

    This function initializes gdt free list and sets up selector for
    KiI386AbiosCall (16-bit code).

Arguments:

    Processor - the processor who performs the initialization.

Return Value:

    None.

--*/

{

    ULONG GdtLength;
    PKGDTENTRY AliasGdtSelectorEntry;
    PKFREE_GDT_ENTRY EndOfGdt;

    //
    // First check if abios is recognized by osloader.
    //

    KiCommonDataArea = KeLoaderBlock->u.I386.CommonDataArea;

    //
    // NOTE For now we want to disable ABIOS support on MP.
    //

    if (KiCommonDataArea == NULL || Processor != 0) {
        KiAbiosPresent = FALSE;
    } else {
        KiAbiosPresent = TRUE;
    }

    //
    // Initialize the spinlocks for accessing GDTs and Lid Table.
    //

    KeInitializeSpinLock( &KiAbiosGdtLock );
    KeInitializeSpinLock( &KiAbiosLidTableLock );

    //
    // Determine the starting and ending addresses of GDT.
    //

    KiAbiosGdt[Processor] = KiAbiosGetGdt();

    AliasGdtSelectorEntry = (PKGDTENTRY)(KiAbiosGetGdt() + KGDT_GDT_ALIAS);
    GdtLength = 1 + (ULONG)(AliasGdtSelectorEntry->LimitLow) +
                (ULONG)(AliasGdtSelectorEntry->HighWord.Bits.LimitHi << 16);
    EndOfGdt = (PKFREE_GDT_ENTRY)(KiAbiosGetGdt() + GdtLength);

    //
    // Prepare selector for 16 bit stack segment
    //

    KiStack16GdtEntry = KiAbiosGetGdt() + KGDT_STACK16;

    KiInitializeAbiosGdtEntry(
                (PKGDTENTRY)KiStack16GdtEntry,
                0L,
                0xffff,
                TYPE_DATA
                );

    //
    // Establish the addressability of Common Data Area selector.
    //

    KiInitializeAbiosGdtEntry(
                (PKGDTENTRY)(KiAbiosGetGdt() + KGDT_CDA16),
                (ULONG)KiCommonDataArea,
                0xffff,
                TYPE_DATA
                );

    //
    // Set up 16-bit code selector for KiI386AbiosCall
    //

    KiInitializeAbiosGdtEntry(
                (PKGDTENTRY)(KiAbiosGetGdt() + KGDT_CODE16),
                (ULONG)&KiI386CallAbios,
                (ULONG)&KiEndOfCode16 - (ULONG)&KiI386CallAbios - 1,
                0x18                   // TYPE_CODE
                );

    //
    // Link all the unused GDT entries to our GDT free list.
    //

    if (Processor == 0) {
        Ki386InitializeGdtFreeList(EndOfGdt);
    }
}

