/*++

Copyright (c) 1991  Microsoft Corporation

Module Name:

    ldtsup.c

Abstract:

    This module implements interfaces that support manipulation of i386 Ldts.
    These entry points only exist on i386 machines.

Author:

    Bryan M. Willman (bryanwi) 14-May-1991

Environment:

    Kernel mode only.

Revision History:

--*/

#include "ki.h"

//
// Low level assembler support procedures
//

VOID
KiLoadLdtr(
    VOID
    );

VOID
KiFlushDescriptors(
    VOID
    );

//
// Local service procedures
//

VOID
Ki386LoadTargetLdtr (
    IN PKIPI_CONTEXT SignalDone,
    IN PVOID Parameter1,
    IN PVOID Parameter2,
    IN PVOID Parameter3
    );

VOID
Ki386FlushTargetDescriptors (
    IN PKIPI_CONTEXT SignalDone,
    IN PVOID Parameter1,
    IN PVOID Parameter2,
    IN PVOID Parameter3
    );

VOID
Ke386SetLdtProcess (
    IN PKPROCESS Process,
    IN PLDT_ENTRY Ldt,
    IN ULONG Limit
    )
/*++

Routine Description:

    The specified LDT (which may be null) will be made the active Ldt of
    the specified process, for all threads thereof, on whichever
    processors they are running.  The change will take effect before the
    call returns.

    An Ldt address of NULL or a Limit of 0 will cause the process to
    receive the NULL Ldt.

    This function only exists on i386 and i386 compatible processors.

    No checking is done on the validity of Ldt entries.


    N.B.

    While a single Ldt structure can be shared amoung processes, any
    edits to the Ldt of one of those processes will only be synchronized
    for that process.  Thus, processes other than the one the change is
    applied to may not see the change correctly.

Arguments:

    Process - Pointer to KPROCESS object describing the process for
        which the Ldt is to be set.

    Ldt - Pointer to an array of LDT_ENTRYs (that is, a pointer to an
        Ldt.)

    Limit - Ldt limit (must be 0 mod 8)

Return Value:

    None.

--*/

{

    KGDTENTRY LdtDescriptor;
    BOOLEAN LocalProcessor;
    KIRQL OldIrql;
    PKPRCB Prcb;
    KAFFINITY TargetProcessors;

    //
    // Compute the contents of the Ldt descriptor
    //

    if ((Ldt == NULL) || (Limit == 0)) {

        //
        //  Set up an empty descriptor
        //

        LdtDescriptor.LimitLow = 0;
        LdtDescriptor.BaseLow = 0;
        LdtDescriptor.HighWord.Bytes.BaseMid = 0;
        LdtDescriptor.HighWord.Bytes.Flags1 = 0;
        LdtDescriptor.HighWord.Bytes.Flags2 = 0;
        LdtDescriptor.HighWord.Bytes.BaseHi = 0;

    } else {

        //
        // Insure that the unfilled fields of the selector are zero
        // N.B.  If this is not done, random values appear in the high
        //       portion of the Ldt limit.
        //

        LdtDescriptor.HighWord.Bytes.Flags1 = 0;
        LdtDescriptor.HighWord.Bytes.Flags2 = 0;

        //
        //  Set the limit and base
        //

        LdtDescriptor.LimitLow = (USHORT) ((ULONG) Limit - 1);
        LdtDescriptor.BaseLow = (USHORT)  ((ULONG) Ldt & 0xffff);
        LdtDescriptor.HighWord.Bytes.BaseMid = (UCHAR) (((ULONG)Ldt & 0xff0000) >> 16);
        LdtDescriptor.HighWord.Bytes.BaseHi =  (UCHAR) (((ULONG)Ldt & 0xff000000) >> 24);

        //
        //  Type is LDT, DPL = 0
        //

        LdtDescriptor.HighWord.Bits.Type = TYPE_LDT;
        LdtDescriptor.HighWord.Bits.Dpl = DPL_SYSTEM;

        //
        // Make it present
        //

        LdtDescriptor.HighWord.Bits.Pres = 1;

    }

    //
    // Acquire the context swap lock so a context switch cannot occur.
    //

    KiLockContextSwap(&OldIrql);

    //
    // Set the Ldt fields in the process object.
    //

    Process->LdtDescriptor = LdtDescriptor;

    //
    // Tell all processors active for this process to reload their LDTs
    //

#ifdef NT_UP

    KiLoadLdtr();

#else

    Prcb = KeGetCurrentPrcb();
    TargetProcessors = Process->ActiveProcessors & ~Prcb->SetMember;
    if (TargetProcessors != 0) {
        KiIpiSendPacket(TargetProcessors,
                        Ki386LoadTargetLdtr,
                        NULL,
                        NULL,
                        NULL);
    }

    KiLoadLdtr();
    if (TargetProcessors != 0) {

        //
        //  Stall until target processor(s) release us
        //

        KiIpiStallOnPacketTargets(TargetProcessors);
    }

#endif

    //
    // Restore IRQL and release the context swap lock.
    //

    KiUnlockContextSwap(OldIrql);
    return;
}

VOID
Ki386LoadTargetLdtr (
    IN PKIPI_CONTEXT SignalDone,
    IN PVOID Parameter1,
    IN PVOID Parameter2,
    IN PVOID Parameter3
    )
/*++

Routine Description:

    Reload local Ldt register and clear signal bit in TargetProcessor mask

Arguments:

    Argument - pointer to a ipi packet structure.
    ReadyFlag - Pointer to flag to be set once LDTR has been reloaded

Return Value:

    none.

--*/
{

    //
    // Reload the LDTR register from currently active process object
    //

    KiLoadLdtr();
    KiIpiSignalPacketDone(SignalDone);
    return;
}

VOID
Ke386SetDescriptorProcess (
    IN PKPROCESS Process,
    IN ULONG Offset,
    IN LDT_ENTRY LdtEntry
    )
/*++

Routine Description:

    The specified LdtEntry (which could be 0, not present, etc) will be
    edited into the specified Offset in the Ldt of the specified Process.
    This will be synchronzied accross all the processors executing the
    process.  The edit will take affect on all processors before the call
    returns.

    N.B.

    Editing an Ldt descriptor requires stalling all processors active
    for the process, to prevent accidental loading of descriptors in
    an inconsistent state.

Arguments:

    Process - Pointer to KPROCESS object describing the process for
        which the descriptor edit is to be performed.

    Offset - Byte offset into the Ldt of the descriptor to edit.
        Must be 0 mod 8.

    LdtEntry - Value to edit into the descriptor in hardware format.
        No checking is done on the validity of this item.

Return Value:

    none.

--*/

{

    PLDT_ENTRY Ldt;
    KIRQL OldIrql;
    PKPRCB Prcb;
    KAFFINITY TargetProcessors;

    //
    // Compute address of descriptor to edit.
    //

    Ldt =
        (PLDT_ENTRY)
         ((Process->LdtDescriptor.HighWord.Bytes.BaseHi << 24) |
         ((Process->LdtDescriptor.HighWord.Bytes.BaseMid << 16) & 0xff0000) |
         (Process->LdtDescriptor.BaseLow & 0xffff));
    Offset = Offset / 8;
    KiLockContextSwap(&OldIrql);

#ifdef NT_UP

    //
    // Edit the Ldt.
    //

    Ldt[Offset] = LdtEntry;

#else

    Prcb = KeGetCurrentPrcb();
    TargetProcessors = Process->ActiveProcessors & ~Prcb->SetMember;
    if (TargetProcessors != 0) {
        KiIpiSendSynchronousPacket(
            Prcb,
            TargetProcessors,
            Ki386FlushTargetDescriptors,
            (PVOID)&Prcb->ReverseStall,
            NULL,
            NULL);

        KiIpiStallOnPacketTargets(TargetProcessors);
    }

    //
    // All target processors have flushed the segment descriptors and
    // are waiting to proceed. Edit the ldt on the current processor,
    // then continue the execution of target processors.
    //

    Ldt[Offset] = LdtEntry;
    if (TargetProcessors != 0) {
        Prcb->ReverseStall += 1;
    }

#endif

    //
    // Restore IRQL and release the context swap lock.
    //

    KiUnlockContextSwap(OldIrql);
    return;
}

VOID
Ki386FlushTargetDescriptors (
    IN PKIPI_CONTEXT SignalDone,
    IN PVOID Proceed,
    IN PVOID Parameter2,
    IN PVOID Parameter3
    )

/*++

Routine Description:

    This function flushes the segment descriptors on the current processor.

Arguments:

    Argument - pointer to a _KIPI_FLUSH_DESCRIPTOR structure.

    ReadyFlag - pointer to flag to syncroize with

Return Value:

    none.

--*/

{
    //
    // Flush the segment descriptors on the current processor and signal that
    // the descriptors have been flushed.
    //

    KiFlushDescriptors();
    KiIpiSignalPacketDoneAndStall (SignalDone, Proceed);
    return;
}