/******************************************************************************
*   Module:     TaskBridge.c                                                  *
*   Revision:   1.00                                                          *
*   Date:       3/14/2000                                                     *
*   Author:     Goran Devic                                                   *
*******************************************************************************
    Module Description:

        This module contains the code for the VM interrupt handling
        including a TaskBridge function.
*******************************************************************************
*   Changes:                                                                  *
*   DATE     DESCRIPTION OF CHANGES                               AUTHOR      *
* --------   ---------------------------------------------------  ----------- *
* 3/14/2000  Original                                             Goran Devic *
* --------   ---------------------------------------------------  ----------- *
*******************************************************************************
*   Include Files                                                             *
******************************************************************************/
#include <strmini.h>                // Include driver level C functions
#include <ntddk.h>                  // Include ddk function header file
#include "Vm.h"                     // Include root header file

/******************************************************************************
*   void TaskSwitch()                                                         *
*******************************************************************************
*   Switch to a VM task.  The task will run until interrupted by either
*   an external interrupt or internal (to CPU) trap, whose number is then
*   stored in pMonitor->nInterrupt field.
******************************************************************************/
void TaskSwitch()
{
    // Make VM task TSS selector not busy so we can load it
    pMonitor->GDT[ VM_SEL_TSS >> 3 ].type = DESC_TYPE_TSS32A;

    _asm
    {
        mov     ebx, [pMonitor]                     // Get to the monitor data

        pushfd                                      // Store flags
        pushad                                      // Store all registers
        cli                                         // Disable interrupts

        ;===========================================
        ; Setting up working control registers
        ;===========================================
        mov     eax, cr0                            // Get host CR0 register
        push    eax                                 // Store it on the stack
        lea     edi, [CPU]
        mov     eax, [edi + TCPU.cr0]               // Get the VM percieved cr0 register
        and     eax, 0x0004002F                     // Let AM NE TS EM MP be settable by a vm
        or      eax, 0x80010011                     // +PE +ET +WP +PG
        mov     cr0, eax                            // Load it into the effective CR0

        mov_eax_cr4                                 // Get host CR4 register
        push    eax                                 // Store it on the stack
        mov     eax, [ebx + TMonitor.guestCR4]      // and reload faked CR4
        mov_cr4_eax

        mov     ecx, [ebx + TMonitor.offPD_Inject]  // Where to inject our bridge page
        add     ecx, NT_PAGE_DIR                    // NT 5 Page Directory
        mov     eax, [ebx + TMonitor.VMPDphysical]  // What page to inject
        xchg    eax, [ecx]                          // Insert bridge into our address space
        mov     edx, cr3
        mov     cr3, edx                            // Invalidate TLB
                                                     // try 'invlpg' instead
        // Store the host state of the processor

        sgdt    fword ptr [ebx + TMonitor.hostGDT]
        sidt    fword ptr [ebx + TMonitor.hostIDT]

        push    ecx                                 // Store PD address
        push    eax                                 // Store original PD page
        push    ebx
        call    fword ptr [ebx + TMonitor.fn_TaskBridgeToVM]
        pop     ebx

        // An active TSS in NT5 has busy bit set; we need to reset it in order for
        // ltr instruction not to gp fault

        mov     eax, NT_SEL_TSS                     // Reload task register
        mov     edx, [ebx + TMonitor.hostGDT.base]
        and     byte ptr [eax + edx + 5], Not 2     // so, make it not busy
        ltr     ax                                  // this will mark it busy again

        xor     ax, ax
        lldt    ax                                  // TODO: NT5 does not use LDT

        pop     eax                                 // Restore original page
        pop     ecx                                 // Restore address
        mov     [ecx], eax                          // restore PD entry
        mov     eax, cr3
        mov     cr3, eax                            // Invalidate TLB

        pop     eax                                 // Restore cr4 from the stack
        mov_cr4_eax

        pop     eax                                 // Restore cr0 from the stack
        mov     cr0, eax

        mov     eax, cr2                            // Get the possible page fault address
        mov     [ebx + TMonitor.guestCR2], eax      // Store it for the case of PF

        popad                                       // Restore all registers
        popfd                                       // Restore flags
    }
}

/******************************************************************************
*   TaskBridgeToVM()                                                          *
*******************************************************************************
*   This function is copied in the shared pages that are accessible both
*   within the host and VM address space at the same linear address.
*
*   The pages are injected into the host address space at the time
*   of a jump, and restored on the exit from VM, done by a caller to this
*   function - TaskSwitch().
*
*   The pages are injected into the guest address space in the RebaseMonitor()
*   function that sets the monitor position-dependent code at the init time
*   and every time when the monitor gets moved.
*
*   On entry:
*      ebx - Monitor structure in driver address space
******************************************************************************/
__declspec( naked ) TaskBridgeToVM()
{
    _asm
    {
        // Store the stack pointer for the re-entry into the driver

        mov     ds:[ebx + TMonitor.DriverESP], esp
        mov     esp, ds:[ebx + TMonitor.guestTSS.esp0]  ; Use new ESP

        lgdt    fword ptr [ebx + TMonitor.guestGDT] ; Load GDT register
        lidt    fword ptr [ebx + TMonitor.guestIDT] ; Load IDT register
        lldt    [ebx + TMonitor.guestLDT]           ; Load LDT register

        mov     eax, [ebx + TMonitor.guestCR3]      ; Get the new CR3 value
        mov     cr3, eax                            ; Bang! We are in the new address space!
        ; This move invalidated TLB cache as well   !

        mov     ax, VM_SEL_MONITOR_DS               ; Get the new DS selector value
        mov     ds, ax                              ; Reload DS within new address space
        mov     ss, ax                              ; Move SS to within monitor space, too

        mov     ax, VM_SEL_TSS                      ; Load VM TSS selector from the new GDT
        ltr     ax                                  ; this will also mark it busy (in GDT)

        test    ds:[TMonitor.guestTSS.eflags], TF_MASK + INTERNAL_RUN_NATIVE_MASK
        jnz     SkipTlbPreload

        ; The basic idea is as follows:
        ;
        ;   * Write RETF using private mapping
        ;   * Call RETF instruction using normal mapping (Loads instruction TLB entry)
        ;   * Restore RETF byte using private mapping
        ;   * Set PT entry to Ring-0 accessible
        ;   * Switch to User mode code using normal mapping

        ; Since we are still running with the NTs code selector, not the monitors one,
        ; RETF would fault unless we change the returning selector to VM_SEL_MONITOR_CS
        ;
        ; Little code stub that we set with a dword, and call it, looks like:
        ;
        ; 59  pop  ecx        ; Pop the returning address value (eip) and discard
        ; 59  pop  edx        ; Pop the returning code selector (NT_SEL_CS) and discard
        ; CB  retf            ; Use previously stored returning address and selector
        ;
        mov     eax, 090CB5959h                     ; Returning code stub

        lea     ebx, ds:[TMonitor.MonCECP]
        xchg    ds:[ebx], eax                       ; Write ret. stub via private mapping

        push    dword ptr VM_SEL_MONITOR_CS         ; Push our CS on the stack

        mov     edx, offset ReturnFromStub
        sub     edx, offset TaskBridgeToVM
        add     edx, MONITOR_BRIDGE_OFFSET
        push    edx                                 ; Push new returning offset

        call    fword ptr ds:[TMonitor.fn_CECP]     ; Call the stub via global mapping
ReturnFromStub:

        mov     ds:[ebx], eax                       ; Restore using private mapping

        mov     eax, ds:[TMonitor.offPTE_CECP]      ; Get to the PTE for that page
        and     dword ptr ds:[eax], Not 1           ; Make CECP page not present

SkipTlbPreload:
        and     ds:[TMonitor.guestTSS.eflags], Not INTERNAL_RUN_NATIVE_MASK
        test    ds:[TMonitor.guestTSS.eflags], VM_MASK
        jz      GuestRunsPM

;GuestRunsV86:
        push    dword ptr ds:[TMonitor.guestTSS.gs]
        push    dword ptr ds:[TMonitor.guestTSS.fs]
        push    dword ptr ds:[TMonitor.guestTSS.ds]
        push    dword ptr ds:[TMonitor.guestTSS.es]
        push    dword ptr ds:[TMonitor.guestTSS.ss]
        push    dword ptr ds:[TMonitor.guestTSS.esp]
        push    dword ptr ds:[TMonitor.guestTSS.eflags]
        push    dword ptr ds:[TMonitor.guestTSS.cs]
        push    dword ptr ds:[TMonitor.guestTSS.eip]

        mov     eax, ds:[TMonitor.guestTSS.eax]
        mov     ecx, ds:[TMonitor.guestTSS.ecx]
        mov     edx, ds:[TMonitor.guestTSS.edx]
        mov     ebx, ds:[TMonitor.guestTSS.ebx]

        mov     ebp, ds:[TMonitor.guestTSS.ebp]
        mov     esi, ds:[TMonitor.guestTSS.esi]
        mov     edi, ds:[TMonitor.guestTSS.edi]

        iretd

GuestRunsPM:

        push    dword ptr ds:[TMonitor.guestTSS.ss]
        push    dword ptr ds:[TMonitor.guestTSS.esp]
        push    dword ptr ds:[TMonitor.guestTSS.eflags]
        push    dword ptr ds:[TMonitor.guestTSS.cs]
        push    dword ptr ds:[TMonitor.guestTSS.eip]
        push    dword ptr ds:[TMonitor.guestTSS.ds]

        mov     ax, ds:[TMonitor.guestTSS.gs]
        mov     gs, ax
        mov     ax, ds:[TMonitor.guestTSS.fs]
        mov     fs, ax
        mov     ax, ds:[TMonitor.guestTSS.es]
        mov     es, ax

        mov     eax, ds:[TMonitor.guestTSS.eax]
        mov     ecx, ds:[TMonitor.guestTSS.ecx]
        mov     edx, ds:[TMonitor.guestTSS.edx]
        mov     ebx, ds:[TMonitor.guestTSS.ebx]

        mov     ebp, ds:[TMonitor.guestTSS.ebp]
        mov     esi, ds:[TMonitor.guestTSS.esi]
        mov     edi, ds:[TMonitor.guestTSS.edi]

        pop     ds
        iretd

        ;======================================================================
        _emit 0x55                                  ; End-of-function signature
        _emit 0xAA                                  ;  for copying
    }
}

/******************************************************************************
*   TaskBridgeToHost()                                                        *
*******************************************************************************
*   This function is copied in the shared pages that are accessible both
*   within the host and VM address space at the same linear address.
*
*   This function is jumped to from each interrupt handler.
******************************************************************************/
__declspec( naked ) TaskBridgeToHost()
{
//  The interrupt stubs looks like the following:
//
//  Interrupt stubs for ints without error code on the ring-0 stack:
//
//  IntXX:  push    eax (dummy)
//          push    eax
//          mov     al, int#
//          jmp     TaskBridgeToHost
//
//  Interrupt stubs for ints that place an error code on the ring-0 stack:
//
//  IntXX:
//          push    eax
//          mov     al, int#
//          jmp     TaskBridgeToHost
//
    // On the ring-0 stack (from current TSS):
    //
    //    (I) Running V86 code:             (II) Running PM code:
    //   ___________________________        ______________________________
    //   esp from tss * -|-
    //              ---- | VM gs
    //              ---- | VM fs
    //              ---- | VM ds
    //              ---- | VM es             esp from tss * -|-
    //              ---- | VM ss                        ---- | PM ss
    //              VM esp                              PM esp
    //              VM eflags                           PM eflags
    //              ---- | VM cs                        ---- | PM cs
    //              VM eip                              PM eip
    //              error_code or dummy                 error_code or dummy
    //   ss:esp ->  eax                       ss:esp -> eax
    //   eax = int#                           eax = int#

    _asm
    {
        mov     byte ptr ss:[TMonitor.nInterrupt], al   ; Save interrupt number
        pop     dword ptr ss:[TMonitor.guestTSS.eax]    ; Save eax
        pop     dword ptr ss:[TMonitor.nErrorCode]      ; Save error code

        pop     dword ptr ss:[TMonitor.guestTSS.eip]    ; Save return address
        pop     dword ptr ss:[TMonitor.guestTSS.cs]     ; Save guest CS
        pop     eax
        mov     ss:[TMonitor.guestTSS.eflags], eax      ; Save eflags

        ; Depending on the guest mode (V86 or PM), we do different cleanup

        test    eax, VM_MASK                            ; Running in V86 mode ?
        jz      GuestWasRunningPM

;GuestWasRunningV86:

        mov     ax, VM_SEL_MONITOR_DS                   ; Now, get us to our data
        mov     ds, ax

        pop     dword ptr ds:[TMonitor.guestTSS.esp]    ; Save the rest of registers
        pop     dword ptr ds:[TMonitor.guestTSS.ss]
        pop     dword ptr ds:[TMonitor.guestTSS.es]
        pop     dword ptr ds:[TMonitor.guestTSS.ds]
        pop     dword ptr ds:[TMonitor.guestTSS.fs]
        pop     dword ptr ds:[TMonitor.guestTSS.gs]

        jmp     GuestCommonBack

GuestWasRunningPM:
        mov     ax, ds
        mov     ss:[TMonitor.guestTSS.ds], ax

        mov     ax, VM_SEL_MONITOR_DS                   ; Now, get us to our data
        mov     ds, ax

        pop     dword ptr ds:[TMonitor.guestTSS.esp]
        pop     dword ptr ds:[TMonitor.guestTSS.ss]

        mov     ax, es
        mov     ds:[TMonitor.guestTSS.es], ax
        mov     ax, fs
        mov     ds:[TMonitor.guestTSS.fs], ax
        mov     ax, gs
        mov     ds:[TMonitor.guestTSS.gs], ax

GuestCommonBack:

        mov     ds:[TMonitor.guestTSS.ecx], ecx     ; Save the rest of GP registers
        mov     ds:[TMonitor.guestTSS.edx], edx
        mov     ds:[TMonitor.guestTSS.ebx], ebx

        mov     ds:[TMonitor.guestTSS.ebp], ebp
        mov     ds:[TMonitor.guestTSS.esi], esi
        mov     ds:[TMonitor.guestTSS.edi], edi

        // -----------------------------------------+
        // We have all the registers saved now
        // -----------------------------------------+
        mov     esp, ds:[TMonitor.DriverESP]        ; Reload driver esp

        lgdt    fword ptr ds:[TMonitor.hostGDT]     ; Load GDT descriptor registers
        lidt    fword ptr ds:[TMonitor.hostIDT]     ; Load IDT descriptor registers

        mov     eax, ds:[TMonitor.hostCR3]          ; Get host CR3 value
        mov     cr3, eax                            ; Bang! We are in the host address space!

        mov     ax, NT_SEL_GS
        mov     gs, ax
        mov     ax, NT_SEL_FS
        mov     fs, ax
        mov     ax, NT_SEL_SS
        mov     ss, ax
        mov     ax, NT_SEL_ES
        mov     es, ax
        mov     ax, NT_SEL_DS
        mov     ds, ax

        retf                                        ; Restore CS as well...

        ;======================================================================
        ; Temporary helper code
PrintDWORD:             ; edx - dword to print, edi - location
        push    eax
        push    ecx
        mov     ecx, 8
        mov     ah, 0x07
        rol     edx, 4
plp:    mov     al, dl
        and     al, 0x0F
        rol     edx, 4
        cmp     al, 9
        jle     not_ov
        add     al, 'A' - '9' - 1
not_ov: add     al, '0'
        stosw
        loop    plp
        inc     edi
        inc     edi
        pop     ecx
        pop     eax
        ret

        _emit 0x55                                  ; End-of-function signature
        _emit 0xAA                                  ;  for copying
    }
}