/*++ Copyright (c) 1991 Microsoft Corporation Module Name: vrinit.c Abstract: Contains Vdm Redir (Vr) 32-bit side initialization and uninitialization routines Contents: VrInitialized VrInitialize VrUninitialize VrRaiseInterrupt VrDismissInterrupt VrQueueCompletionHandler VrHandleAsyncCompletion VrCheckPmNetbiosAnr VrEoiAndDismissInterrupt VrSuspendHook VrResumeHook Author: Richard L Firth (rfirth) 13-Sep-1991 Environment: 32-bit flat address space Revision History: 13-Sep-1991 RFirth Created --*/ #include #include // ASSERT, DbgPrint #include #include #include // x86 virtual machine definitions #include #include // common Vdm Redir stuff #include #include #include #include #include #include "vrdlc.h" #include "vrdebug.h" #define BOOL // kludge for mips build #include // Required for ica.h #include // Required for ica.h #include #include // call_ica_hw_interrupt // // external functions // extern BOOL VDDInstallUserHook(HANDLE, FARPROC, FARPROC, FARPROC, FARPROC); // // prototypes // VOID VrSuspendHook( VOID ); VOID VrResumeHook( VOID ); // // data // static BOOLEAN IsVrInitialized = FALSE; // set when TSR loaded extern DWORD VrPeekNamedPipeTickCount; extern CRITICAL_SECTION VrNmpRequestQueueCritSec; extern CRITICAL_SECTION VrNamedPipeCancelCritSec; // // Async Event Disposition. The following critical sections, queue and counter // plus the routines VrRaiseInterrupt, VrDismissInterrupt, // VrQueueCompletionHandler and VrHandleAsyncCompletion comprise the async // event disposition processing. // // We employ these to dispose of the asynchronous event completions in the order // they occur. Also we keep calls to call_ica_hw_interrupt serialized: the reason // for this is that the ICA is not guaranteed to generate an interrupt. Rather // than blast the ICA with interrupt requests, we generate one only when we // know we have completed the previous disposition // CRITICAL_SECTION AsyncEventQueueCritSec; VR_ASYNC_DISPOSITION* AsyncEventQueueHead = NULL; VR_ASYNC_DISPOSITION* AsyncEventQueueTail = NULL; CRITICAL_SECTION QueuedInterruptCritSec; LONG QueuedInterrupts = -1; LONG FrozenInterrupts = 0; // // FrozenVdmContext - TRUE if the 16-bit context has been suspended. When this // happens, we need to queue any hardware interrupt requests until the 16-bit // context has been resumed // BOOLEAN FrozenVdmContext = FALSE; // // routines // BOOLEAN VrInitialized( VOID ) /*++ Routine Description: Returns whether the VdmRedir support has been initialized yet (ie redir.exe TSR loaded in DOS emulation memory). Principally here because VdmRedir is now a DLL loaded at run-time via LoadLibrary Arguments: None. Return Value: BOOLEAN TRUE VdmRedir support is active FALSE VdmRedir support inactive --*/ { return IsVrInitialized; } BOOLEAN VrInitialize( VOID ) /*++ Routine Description: Performs 32-bit side initialization when the redir TSR is loaded Arguments: None. ES:BX in VDM context is place to return computer name, CX is size of buffer in VDM context for computer name Return Value: None. --*/ { LPBYTE lpVdmVrInitialized; #if DBG DIAGNOSTIC_INFO info; VrDebugInit(); DIAGNOSTIC_ENTRY("VrInitialize", DG_NONE, &info); #endif // // if we are already initialized return TRUE. Not sure if this should // really happen? // if (IsVrInitialized) { return TRUE; } // // register our hooks // if (!VDDInstallUserHook(GetModuleHandle("VDMREDIR"), (FARPROC)NULL, // 16-bit process create hook (FARPROC)NULL, // 16-bit process terminate hook (FARPROC)VrSuspendHook, (FARPROC)VrResumeHook )) { return FALSE; } // // do the rest of the initialization - none of this can fail // InitializeCriticalSection(&VrNmpRequestQueueCritSec); InitializeCriticalSection(&AsyncEventQueueCritSec); InitializeCriticalSection(&QueuedInterruptCritSec); InitializeCriticalSection(&VrNamedPipeCancelCritSec); VrNetbios5cInitialize(); VrDlcInitialize(); IsVrInitialized = TRUE; // // deferred loading: we need to let the VDM redir know that the 32-bit // support is loaded. Set the VrInitialized flag in the VDM Redir at // the known address // lpVdmVrInitialized = LPBYTE_FROM_WORDS(getCS(), (DWORD)(&(((VDM_LOAD_INFO*)0)->VrInitialized))); *lpVdmVrInitialized = 1; // // VrPeekNamedPipe idle processing // VrPeekNamedPipeTickCount = GetTickCount(); setCF(0); // no carry == successful initialization // // inform 32-bit caller of successful initialization // return TRUE; } VOID VrUninitialize( VOID ) /*++ Routine Description: Performs 32-bit side uninitialization when the redir TSR is removed Arguments: None. Return Value: None. --*/ { IF_DEBUG(DLL) { DPUT("VrUninitialize\n"); } if (IsVrInitialized) { DeleteCriticalSection(&VrNmpRequestQueueCritSec); DeleteCriticalSection(&AsyncEventQueueCritSec); DeleteCriticalSection(&QueuedInterruptCritSec); DeleteCriticalSection(&VrNamedPipeCancelCritSec); } IsVrInitialized = FALSE; setCF(0); // no carry == successful uninitialization } VOID VrRaiseInterrupt( VOID ) /*++ Routine Description: Generates a simulated hardware interrupt by calling the ICA routine. Access to the ICA is serialized here: we maintain a count. If the count goes from -1 to 0, we call the ICA function to generate the interrupt in the VDM. Any other value just queues the interrupt by incrementing the counter. When the corresponding VrDismissInterrupt call is made, a queued interrupt will be generated. This stops us losing simulated h/w interrupts to the VDM Arguments: None. Return Value: None. --*/ { EnterCriticalSection(&QueuedInterruptCritSec); ++QueuedInterrupts; if (QueuedInterrupts == 0) { if (!FrozenVdmContext) { IF_DEBUG(CRITICAL) { CRITDUMP(("*** VrRaiseInterrupt: Interrupting VDM ***\n")); } IF_DEBUG(HW_INTERRUPTS) { DBGPRINT("VrRaiseInterrupt: interrupting VDM\n"); } call_ica_hw_interrupt(NETWORK_ICA, NETWORK_LINE, 1); } else { IF_DEBUG(HW_INTERRUPTS) { DBGPRINT("*** VrRaiseInterrupt: VDM is Frozen, not interrupting ***\n"); } } } IF_DEBUG(CRITICAL) { CRITDUMP(("*** VrRaiseInterrupt (%d) ***\n", QueuedInterrupts)); } IF_DEBUG(HW_INTERRUPTS) { DBGPRINT("*** VrRaiseInterrupt (%d) ***\n", QueuedInterrupts); } LeaveCriticalSection(&QueuedInterruptCritSec); } VOID VrDismissInterrupt( VOID ) /*++ Routine Description: Companion routine to VrRaiseInterrupt: this function is called when the async event which called VrRaiseInterrupt has been disposed. If other calls to VrRaiseInterrupt have been made in the interim then QueuedInterrupts will be >0. In this case we re-issue the call to call_ica_hw_interrupt() which will generate an new simulated h/w interrupt in the VDM. Note: This routine is called from the individual disposition routine, not from the disposition dispatch routine (VrHandleAsyncCompletion) Arguments: None. Return Value: None. --*/ { EnterCriticalSection(&QueuedInterruptCritSec); if (!FrozenVdmContext) { --QueuedInterrupts; if (QueuedInterrupts >= 0) { IF_DEBUG(CRITICAL) { CRITDUMP(("*** VrDismissInterrupt: interrupting VDM ***\n")); } IF_DEBUG(HW_INTERRUPTS) { DBGPRINT("VrDismissInterrupt: interrupting VDM\n"); } call_ica_hw_interrupt(NETWORK_ICA, NETWORK_LINE, 1); } } else { IF_DEBUG(HW_INTERRUPTS) { DBGPRINT("*** VrDismissInterrupt: VDM is Frozen??? ***\n"); } } IF_DEBUG(CRITICAL) { CRITDUMP(("*** VrDismissInterrupt (%d) ***\n", QueuedInterrupts)); } IF_DEBUG(HW_INTERRUPTS) { DBGPRINT("*** VrDismissInterrupt (%d) ***\n", QueuedInterrupts); } LeaveCriticalSection(&QueuedInterruptCritSec); } VOID VrQueueCompletionHandler( IN VOID (*AsyncDispositionRoutine)(VOID) ) /*++ Routine Description: Adds an async event disposition packet to the queue of pending completions (event waiting to be fully completed by the VDM async event ISR/BOP). We keep these in a singly-linked queue so that we avoid giving priority to one completion handler while polling Arguments: AsyncDispositionRoutine - address of routine which will dispose of the async completion event Return Value: None. --*/ { VR_ASYNC_DISPOSITION* pDisposition; pDisposition = (VR_ASYNC_DISPOSITION*)LocalAlloc(LMEM_FIXED, sizeof(VR_ASYNC_DISPOSITION) ); if (pDisposition == NULL) { IF_DEBUG(CRITICAL) { CRITDUMP(("*** VrQueueCompletionHandler: ERROR: Failed to alloc Q packet ***\n")); } IF_DEBUG(HW_INTERRUPTS) { DBGPRINT("!!! VrQueueCompletionHandler: failed to allocate memory\n"); } return; } EnterCriticalSection(&AsyncEventQueueCritSec); pDisposition->Next = NULL; pDisposition->AsyncDispositionRoutine = AsyncDispositionRoutine; if (AsyncEventQueueHead == NULL) { AsyncEventQueueHead = pDisposition; } else { AsyncEventQueueTail->Next = pDisposition; } AsyncEventQueueTail = pDisposition; LeaveCriticalSection(&AsyncEventQueueCritSec); IF_DEBUG(CRITICAL) { CRITDUMP(("VrQueueCompletionHandler: Handler %08x queued @ %08x\n", AsyncDispositionRoutine, pDisposition )); } IF_DEBUG(HW_INTERRUPTS) { DBGPRINT("VrQueueCompletionHandler: Handler %08x queued @ %08x\n", AsyncDispositionRoutine, pDisposition ); } } VOID VrHandleAsyncCompletion( VOID ) /*++ Routine Description: Called by VrDispatch for the async completion event BOP. Dequeues the disposition packet from the head of the queue and calls the disposition routine Arguments: None. Return Value: None. --*/ { VR_ASYNC_DISPOSITION* pDisposition; VOID (*AsyncDispositionRoutine)(VOID); EnterCriticalSection(&AsyncEventQueueCritSec); pDisposition = AsyncEventQueueHead; AsyncDispositionRoutine = pDisposition->AsyncDispositionRoutine; AsyncEventQueueHead = pDisposition->Next; IF_DEBUG(CRITICAL) { CRITDUMP(("VrHandleAsyncCompletion: Handler %08x dequeued @ %08x\n", AsyncDispositionRoutine, pDisposition )); } IF_DEBUG(HW_INTERRUPTS) { DBGPRINT("VrHandleAsyncCompletion: freeing @ %08x && calling handler %08x\n", pDisposition, AsyncDispositionRoutine ); } LocalFree((HLOCAL)pDisposition); LeaveCriticalSection(&AsyncEventQueueCritSec); AsyncDispositionRoutine(); } VOID VrCheckPmNetbiosAnr( VOID ) /*++ Routine Description: If the disposition routine queued at the head of the disposition list is VrNetbios5cInterrupt, indicating that the next asynchronous event to be completed is an async Netbios call, then set the 16-bit Zero Flag to TRUE if the NCB originated in 16-bit protect mode Assumes: 1. This function is called after the corresponding interrupt has been delivered 2. There is something on the AsyncEventQueue Arguments: None. Return Value: None. ZF in 16-bit context flags word: TRUE - the next thing to complete is not an NCB, OR, it originated in 16-bit real-mode FALSE - the next event to be disposed of IS an async Netbios request, the NCB for which originated in 16-bit protect mode --*/ { BOOLEAN result; EnterCriticalSection(&AsyncEventQueueCritSec); if (AsyncEventQueueHead->AsyncDispositionRoutine == VrNetbios5cInterrupt) { result = IsPmNcbAtQueueHead(); } else { result = FALSE; } IF_DEBUG(HW_INTERRUPTS) { DBGPRINT("VrCheckPmNetbiosAnr: %s\n", result ? "TRUE" : "FALSE"); } // // set ZF: TRUE means event at head of list not PM NCB completion, or no // NCB completion event on list // setZF(!result); LeaveCriticalSection(&AsyncEventQueueCritSec); } VOID VrEoiAndDismissInterrupt( VOID ) /*++ Routine Description: Performs an EOI then calls VrDismissInterrupt which checks for pending interrupt requests. Called when we handle the simulated h/w interrupt entirely in protect mode (original call was from a WOW app). In this case, the p-m interrupt handler doesn't perform out a0,20; out 20,20 (non-specific EOI to PIC 0 & PIC 1), but calls this handler which gets SoftPc to handle the EOIs to the virtual PICs. This is quicker because we don't take any restricted-opcode faults (the out instruction in real mode causes a GPF because the code doesn't have enough privilege to execute I/O instructions. SoftPc takes a look and sees that the code is trying to talk to the PIC. It then performs the necessary mangling of the PIC state) Arguments: None. Return Value: None. --*/ { int line; extern VOID SoftPcEoi(int, int*); IF_DEBUG(HW_INTERRUPTS) { DBGPRINT("VrEoiAndDismissInterrupt\n"); } line = -1; SoftPcEoi(1, &line); // non-specific EOI to slave PIC line = -1; SoftPcEoi(0, &line); // non-specific EOI to master PIC VrDismissInterrupt(); } VOID VrSuspendHook( VOID ) /*++ Routine Description: This is the hook called by NTVDM.EXE for the VDD handle owned by VDMREDIR.DLL. The hook is called when NTVDM is about to execute a 32-bit process and it suspends the 16-bit context Within the queued interrupt critical section we note that the 16-bit context has been frozen and snapshot the outstanding interrupt request count N.B. We don't expect that this function will be called again before an intervening call to VrResumeHook Arguments: None. Return Value: None. --*/ { EnterCriticalSection(&QueuedInterruptCritSec); FrozenVdmContext = TRUE; FrozenInterrupts = QueuedInterrupts; IF_DEBUG(HW_INTERRUPTS) { DBGPRINT("VrSuspendHook - FrozenInterrupts = %d\n", FrozenInterrupts); } LeaveCriticalSection(&QueuedInterruptCritSec); } VOID VrResumeHook( VOID ) /*++ Routine Description: This hook is called when NTVDM resumes the 16-bit context after executing a 32-bit process Within the queued interrupt critical section we note that the 16-bit context has been resumed and we compare the current queued interrupt request count with the snapshot value we took when the context was suspended. If during the intervening period, interrupt requests have been made, we call VrDismissInterrupt to generate the next interrupt N.B. We don't expect that this function will be called again before an intervening call to VrSuspendHook Arguments: None. Return Value: None. --*/ { EnterCriticalSection(&QueuedInterruptCritSec); IF_DEBUG(HW_INTERRUPTS) { DBGPRINT("VrResumeHook - FrozenInterrupts = %d QueuedInterrupts = %d\n", FrozenInterrupts, QueuedInterrupts ); } FrozenVdmContext = FALSE; if (QueuedInterrupts > FrozenInterrupts) { // // interrupts were queued while the 16-bit context was suspended. If // the QueuedInterrupts count was -1 when we took the snapshot then // we must interrupt the VDM. The count has already been updated to // account for the interrupt, but none was delivered. Do it here // // if (FrozenInterrupts == -1) { IF_DEBUG(HW_INTERRUPTS) { DBGPRINT("*** VrResumeHook: interrupting VDM ***\n"); } call_ica_hw_interrupt(NETWORK_ICA, NETWORK_LINE, 1); // } } LeaveCriticalSection(&QueuedInterruptCritSec); }