/*++

Module Name:

    context.c

Abstract:

    This module implements user-mode callable context manipulation routines.
    The interfaces exported from this module are portable, but they must
    be re-implemented for each architecture.

Author:


Revision History:

    Ported to the IA64

    27-Feb-1996   Revised to pass arguments to target thread by injecting
                  arguments into the backing store.

--*/

#include "ntrtlp.h"
#include "kxia64.h"

#if defined(ALLOC_PRAGMA) && defined(NTOS_KERNEL_RUNTIME)
#pragma alloc_text(PAGE,RtlInitializeContext)
#pragma alloc_text(PAGE,RtlRemoteCall)
#endif


VOID
RtlInitializeContext(
    IN HANDLE Process,
    OUT PCONTEXT Context,
    IN PVOID Parameter OPTIONAL,
    IN PVOID InitialPc OPTIONAL,
    IN PVOID InitialSp OPTIONAL
    )

/*++

Routine Description:

    This function initializes a context structure so that it can
    be used in a subsequent call to NtCreateThread.

Arguments:

    Context - Supplies a context buffer to be initialized by this routine.

    InitialPc - Supplies an initial program counter value.

    InitialSp - Supplies an initial stack pointer value.

Return Value:

    Raises STATUS_BAD_INITIAL_STACK if the value of InitialSp is not properly
           aligned.

    Raises STATUS_BAD_INITIAL_PC if the value of InitialPc is not properly
           aligned.

--*/

{
    ULONGLONG Argument;
    ULONG_PTR Wow64Info;
    NTSTATUS Status;
    
    RTL_PAGED_CODE();

    //
    // Check for proper initial stack (0 mod 16).
    //

    if (((ULONG_PTR)InitialSp & 0xf) != 0) {
        RtlRaiseStatus(STATUS_BAD_INITIAL_STACK);
    }

    //
    // Check for proper plabel address alignment.
    // Assumes InitialPc points to a plabel that must be 8-byte aligned.
    //
    if (((ULONG_PTR)InitialPc & 0x7) != 0) {
        //
        // Misaligned, See if we are running in a Wow64 process
        //
        Status = ZwQueryInformationProcess(Process,
                                           ProcessWow64Information,
                                           &Wow64Info,
                                           sizeof(Wow64Info),
                                           NULL);

        if (NT_SUCCESS(Status) && (Wow64Info == 0))
        {
            //
            // Native IA64 process must not be misaligned.
            //
            RtlRaiseStatus(STATUS_BAD_INITIAL_PC);
        }
    }


    //
    // Initialize the integer and floating registers to contain zeroes.
    //

    RtlZeroMemory(Context, sizeof(CONTEXT));

    //
    // Setup integer and control context.
    //

    Context->ContextFlags = CONTEXT_INTEGER | CONTEXT_CONTROL;

    Context->RsBSPSTORE = Context->IntSp = (ULONG_PTR)InitialSp;
    Context->IntSp -= STACK_SCRATCH_AREA;

    //
    // InitialPc is the module entry point which is a function pointer
    // in IA64. StIIP and IntGp are initiailized with actual IP and GP
    // from the plabel in LdrInitializeThunk after the loader runs.
    //

    Context->IntS1 = Context->IntS0 = Context->StIIP = (ULONG_PTR)InitialPc;
    Context->IntGp = 0;

    //
    // Setup FPSR, PSR, and DCR
    // N.B. Values to be determined.
    //

    Context->StFPSR = USER_FPSR_INITIAL;
    Context->StIPSR = USER_PSR_INITIAL;
    Context->ApDCR = USER_DCR_INITIAL;

    //
    // Set the initial context of the thread in a machine specific way.
    // ie, pass the initial parameter to the RSE by saving it at the
    // bottom of the backing store.
    //
    //  Setup Frame Marker after RFI
    //  And other RSE states.
    //

    Argument = (ULONGLONG)Parameter;
    ZwWriteVirtualMemory(Process,
             (PVOID)((ULONG_PTR)Context->RsBSPSTORE),
             (PVOID)&Argument,
             sizeof(Argument),
             NULL);
//
// N.b. The IFS must be reinitialized in LdrInitializeThunk
//

    Context->StIFS = 0x8000000000000081ULL;            // Valid, 1 local register, 0 output register
    Context->RsBSP = Context->RsBSPSTORE;
    Context->RsRSC = USER_RSC_INITIAL;
    Context->ApUNAT = 0xFFFFFFFFFFFFFFFF;
}


NTSTATUS
RtlRemoteCall(
    HANDLE Process,
    HANDLE Thread,
    PVOID CallSite,
    ULONG ArgumentCount,
    PULONG_PTR Arguments,
    BOOLEAN PassContext,
    BOOLEAN AlreadySuspended
    )

/*++

Routine Description:

    This function calls a procedure in another thread/process, using
    NtGetContext and NtSetContext.  Parameters are passed to the
    target procedure via its stack.

Arguments:

    Process - Handle of the target process

    Thread - Handle of the target thread within that process

    CallSite - Address of the procedure to call in the target process.

    ArgumentCount - Number of parameters to pass to the target
                    procedure.

    Arguments - Pointer to the array of parameters to pass.

    PassContext - TRUE if an additional parameter is to be passed that
        points to a context record.

    AlreadySuspended - TRUE if the target thread is already in a suspended
                       or waiting state.

Return Value:

    Status - Status value

--*/

{
    NTSTATUS Status;
    CONTEXT Context;
    ULONG_PTR ContextAddress;
    ULONG_PTR NewSp;
    ULONG_PTR NewBsp;
    ULONGLONG ArgumentsCopy[9];
    PVOID ptr;
    ULONG ShiftCount;
    SHORT RNatSaveIndex, TotalFrameSize, Temp;
    BOOLEAN RnatSaved = FALSE;
    ULONG Count = 0;


    RTL_PAGED_CODE();

    if ((ArgumentCount > 8) || (PassContext && (ArgumentCount > 7))) {
        return STATUS_INVALID_PARAMETER;
    }

    //
    // If necessary, suspend the guy before with we mess with his stack.
    //

    if (AlreadySuspended == FALSE) {
        Status = NtSuspendThread(Thread, NULL);
        if (NT_SUCCESS(Status) == FALSE) {
            return(Status);
        }
    }

    //
    // Get the context record of the target thread.
    //

    Context.ContextFlags = CONTEXT_FULL;
    Status = NtGetContextThread(Thread, &Context);
    if (NT_SUCCESS(Status) == FALSE) {
        if (AlreadySuspended == FALSE) {
            NtResumeThread(Thread, NULL);
        }
        return(Status);
    }

    if (AlreadySuspended) {
        Context.IntV0 = STATUS_ALERTED;
    }

    //
    // Pass the parameters to the other thread via the backing store (r32-r39).
    // The context record is passed on the stack of the target thread.
    // N.B. Align the context record address, stack pointer, and allocate
    //      stack scratch area.
    //

    ContextAddress = (((ULONG_PTR)Context.IntSp + 0xf) & ~0xfi64) - sizeof(CONTEXT);
    NewSp = ContextAddress - STACK_SCRATCH_AREA;
    Status = NtWriteVirtualMemory(Process, (PVOID)ContextAddress, &Context,
                  sizeof(CONTEXT), NULL);

    if (NT_SUCCESS(Status) == FALSE) {
        if (AlreadySuspended == FALSE) {
            NtResumeThread(Thread, NULL);
        }
        return(Status);
    }

    RtlZeroMemory((PVOID)ArgumentsCopy, sizeof(ArgumentsCopy));

    TotalFrameSize = (SHORT)(Context.StIFS & PFS_SIZE_MASK);
    RNatSaveIndex = (SHORT)((Context.RsBSP >> 3) & NAT_BITS_PER_RNAT_REG);
    Temp = RNatSaveIndex + TotalFrameSize - NAT_BITS_PER_RNAT_REG;
    while (Temp >= 0) {
        TotalFrameSize++;
        Temp -= NAT_BITS_PER_RNAT_REG;
    }
    NewBsp = Context.RsBSP + TotalFrameSize * sizeof(ULONGLONG);
    Context.RsBSP = NewBsp;

    if (PassContext) {
        ShiftCount = (ULONG) (NewBsp & 0x1F8) >> 3;
        Context.RsRNAT &= ~(0x1i64 << ShiftCount);
        ArgumentsCopy[Count++] = ContextAddress;
        NewBsp += sizeof(ULONGLONG);
    }

    for (; ArgumentCount != 0 ; ArgumentCount--) {
        if ((NewBsp & 0x1F8) == 0x1F8) {
            ArgumentsCopy[Count++] = Context.RsRNAT;
            Context.RsRNAT = -1i64;
            NewBsp += sizeof(ULONGLONG);
        }
        ShiftCount = (ULONG)(NewBsp & 0x1F8) >> 3;
        Context.RsRNAT &= ~(0x1i64 << ShiftCount);
        ArgumentsCopy[Count++] = (ULONGLONG)(*Arguments++);
        NewBsp += sizeof(ULONGLONG);
    }

    if ((NewBsp & 0x1F8) == 0x1F8) {
        ArgumentsCopy[Count++] = Context.RsRNAT;
        Context.RsRNAT = -1i64;
        NewBsp += sizeof(ULONGLONG);
    }

    //
    //  Copy the arguments onto the target backing store.
    //

    if (Count) {
        Status = NtWriteVirtualMemory(Process,
                                      (PVOID)Context.RsBSP,
                                      ArgumentsCopy,
                                      Count * sizeof(ULONGLONG),
                                      NULL
                                      );

        if (NT_SUCCESS(Status) == FALSE) {
            if (AlreadySuspended == FALSE) {
                NtResumeThread(Thread, NULL);
            }
            return(Status);
        }
    }

    //
    // set up RSE
    //

    Context.RsRSC = (RSC_MODE_LY<<RSC_MODE)
                   | (RSC_BE_LITTLE<<RSC_BE)
                   | (0x3<<RSC_PL);

    Count = ArgumentCount + (PassContext ? 1 : 0);

    //
    // Inject all arguments as local stack registers in the target RSE frame
    //

    Context.StIFS = (0x3i64 << 62) | Count | (Count << PFS_SIZE_SHIFT);

    //
    // Set the address of the target code into IIP, the new target stack
    // into sp, setup ap, and reload context to make it happen.
    //

    Context.IntSp = (ULONG_PTR)NewSp;

    //
    // Set IP to the target call site PLABEL and GP to zero.  IIP and GP
    // will be computed inside NtSetContextThread.
    //

    Context.StIIP = (ULONGLONG)CallSite;
    Context.IntGp = 0;

    //
    // sanitize the floating pointer status register
    //

    SANITIZE_FSR(Context.StFPSR, UserMode);

    Status = NtSetContextThread(Thread, &Context);
    if (!AlreadySuspended) {
        NtResumeThread(Thread, NULL);
    }

    return( Status );
}