| /* |
| * DOS Virtual Machine |
| * |
| * Copyright 1998 Ove Kåven |
| * Copyright 2002 Jukka Heinonen |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA |
| * |
| * Note: This code hasn't been completely cleaned up yet. |
| */ |
| |
| #include "config.h" |
| #include "wine/port.h" |
| |
| #include <stdarg.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <signal.h> |
| #ifdef HAVE_UNISTD_H |
| # include <unistd.h> |
| #endif |
| #ifdef HAVE_SYS_TIME_H |
| # include <sys/time.h> |
| #endif |
| #include <sys/types.h> |
| |
| #include "wine/winbase16.h" |
| #include "wine/exception.h" |
| #include "windef.h" |
| #include "winbase.h" |
| #include "winternl.h" |
| #include "wingdi.h" |
| #include "winuser.h" |
| #include "wownt32.h" |
| #include "winnt.h" |
| #include "wincon.h" |
| |
| #include "dosexe.h" |
| #include "wine/debug.h" |
| #include "excpt.h" |
| |
| WINE_DEFAULT_DEBUG_CHANNEL(int); |
| #ifdef MZ_SUPPORTED |
| WINE_DECLARE_DEBUG_CHANNEL(module); |
| WINE_DECLARE_DEBUG_CHANNEL(relay); |
| #endif |
| |
| WORD DOSVM_psp = 0; |
| WORD DOSVM_retval = 0; |
| |
| /* |
| * Wine DOS memory layout above 640k: |
| * |
| * a0000 - affff : VGA graphics (vga.c) |
| * b0000 - bffff : Monochrome text (unused) |
| * b8000 - bffff : VGA text (vga.c) |
| * c0000 - cffff : EMS frame (int67.c) |
| * d0000 - effff : Free memory for UMBs (himem.c) |
| * f0000 - fffff : BIOS stuff (msdos/dosmem.c) |
| * 100000 -10ffff : High memory area (unused) |
| */ |
| |
| /* |
| * Table of real mode segments and protected mode selectors |
| * for code stubs and other miscellaneous storage. |
| */ |
| struct DPMI_segments *DOSVM_dpmi_segments = NULL; |
| |
| /* |
| * First and last address available for upper memory blocks. |
| */ |
| #define DOSVM_UMB_BOTTOM 0xd0000 |
| #define DOSVM_UMB_TOP 0xeffff |
| |
| /* |
| * First free address for upper memory blocks. |
| */ |
| static DWORD DOSVM_umb_free = DOSVM_UMB_BOTTOM; |
| |
| |
| typedef struct _DOSEVENT { |
| int irq,priority; |
| DOSRELAY relay; |
| void *data; |
| struct _DOSEVENT *next; |
| } DOSEVENT, *LPDOSEVENT; |
| |
| static struct _DOSEVENT *pending_event, *current_event; |
| static HANDLE event_notifier; |
| |
| static CRITICAL_SECTION qcrit; |
| static CRITICAL_SECTION_DEBUG critsect_debug = |
| { |
| 0, 0, &qcrit, |
| { &critsect_debug.ProcessLocksList, &critsect_debug.ProcessLocksList }, |
| 0, 0, { (DWORD_PTR)(__FILE__ ": qcrit") } |
| }; |
| static CRITICAL_SECTION qcrit = { &critsect_debug, -1, 0, 0, 0, 0 }; |
| |
| |
| /*********************************************************************** |
| * DOSVM_HasPendingEvents |
| * |
| * Return true if there are pending events that are not |
| * blocked by currently active event. |
| */ |
| static BOOL DOSVM_HasPendingEvents( void ) |
| { |
| if (!pending_event) |
| return FALSE; |
| |
| if (!current_event) |
| return TRUE; |
| |
| if (pending_event->priority < current_event->priority) |
| return TRUE; |
| |
| return FALSE; |
| } |
| |
| |
| /*********************************************************************** |
| * DOSVM_SendOneEvent |
| * |
| * Process single pending event. |
| * |
| * This function should be called with queue critical section locked. |
| * The function temporarily releases the critical section if it is |
| * possible that internal interrupt handler or user procedure will |
| * be called. This is because we may otherwise get a deadlock if |
| * another thread is waiting for the same critical section. |
| */ |
| static void DOSVM_SendOneEvent( CONTEXT *context ) |
| { |
| LPDOSEVENT event = pending_event; |
| |
| /* Remove from pending events list. */ |
| pending_event = event->next; |
| |
| /* Process active event. */ |
| if (event->irq >= 0) |
| { |
| BYTE intnum = (event->irq < 8) ? |
| (event->irq + 8) : (event->irq - 8 + 0x70); |
| |
| /* Event is an IRQ, move it to current events list. */ |
| event->next = current_event; |
| current_event = event; |
| |
| TRACE( "Dispatching IRQ %d.\n", event->irq ); |
| |
| if (ISV86(context)) |
| { |
| /* |
| * Note that if DOSVM_HardwareInterruptRM calls an internal |
| * interrupt directly, current_event might be cleared |
| * (and event freed) in this call. |
| */ |
| LeaveCriticalSection(&qcrit); |
| DOSVM_HardwareInterruptRM( context, intnum ); |
| EnterCriticalSection(&qcrit); |
| } |
| else |
| { |
| /* |
| * This routine only modifies current context so it is |
| * not necessary to release critical section. |
| */ |
| DOSVM_HardwareInterruptPM( context, intnum ); |
| } |
| } |
| else |
| { |
| /* Callback event. */ |
| TRACE( "Dispatching callback event.\n" ); |
| |
| if (ISV86(context)) |
| { |
| /* |
| * Call relay immediately in real mode. |
| */ |
| LeaveCriticalSection(&qcrit); |
| (*event->relay)( context, event->data ); |
| EnterCriticalSection(&qcrit); |
| } |
| else |
| { |
| /* |
| * Force return to relay code. We do not want to |
| * call relay directly because we may be inside a signal handler. |
| */ |
| DOSVM_BuildCallFrame( context, event->relay, event->data ); |
| } |
| |
| HeapFree(GetProcessHeap(), 0, event); |
| } |
| } |
| |
| |
| /*********************************************************************** |
| * DOSVM_SendQueuedEvents |
| * |
| * As long as context instruction pointer stays unmodified, |
| * process all pending events that are not blocked by currently |
| * active event. |
| * |
| * This routine assumes that caller has already cleared TEB.vm86_pending |
| * and checked that interrupts are enabled. |
| */ |
| void DOSVM_SendQueuedEvents( CONTEXT *context ) |
| { |
| DWORD old_cs = context->SegCs; |
| DWORD old_ip = context->Eip; |
| |
| EnterCriticalSection(&qcrit); |
| |
| TRACE( "Called in %s mode %s events pending (time=%d)\n", |
| ISV86(context) ? "real" : "protected", |
| DOSVM_HasPendingEvents() ? "with" : "without", |
| GetTickCount() ); |
| TRACE( "cs:ip=%04x:%08x, ss:sp=%04x:%08x\n", |
| context->SegCs, context->Eip, context->SegSs, context->Esp); |
| |
| while (context->SegCs == old_cs && |
| context->Eip == old_ip && |
| DOSVM_HasPendingEvents()) |
| { |
| DOSVM_SendOneEvent(context); |
| |
| /* |
| * Event handling may have turned pending events flag on. |
| * We disable it here because this prevents some |
| * unnecessary calls to this function. |
| */ |
| get_vm86_teb_info()->vm86_pending = 0; |
| } |
| |
| #ifdef MZ_SUPPORTED |
| |
| if (DOSVM_HasPendingEvents()) |
| { |
| /* |
| * Interrupts disabled, but there are still |
| * pending events, make sure that pending flag is turned on. |
| */ |
| TRACE( "Another event is pending, setting VIP flag.\n" ); |
| get_vm86_teb_info()->vm86_pending |= VIP_MASK; |
| } |
| |
| #else |
| |
| FIXME("No DOS .exe file support on this platform (yet)\n"); |
| |
| #endif /* MZ_SUPPORTED */ |
| |
| LeaveCriticalSection(&qcrit); |
| } |
| |
| |
| #ifdef MZ_SUPPORTED |
| /*********************************************************************** |
| * DOSVM_QueueEvent |
| */ |
| void DOSVM_QueueEvent( INT irq, INT priority, DOSRELAY relay, LPVOID data) |
| { |
| LPDOSEVENT event, cur, prev; |
| BOOL old_pending; |
| |
| if (MZ_Current()) { |
| event = HeapAlloc(GetProcessHeap(), 0, sizeof(DOSEVENT)); |
| if (!event) { |
| ERR("out of memory allocating event entry\n"); |
| return; |
| } |
| event->irq = irq; event->priority = priority; |
| event->relay = relay; event->data = data; |
| |
| EnterCriticalSection(&qcrit); |
| old_pending = DOSVM_HasPendingEvents(); |
| |
| /* insert event into linked list, in order *after* |
| * all earlier events of higher or equal priority */ |
| cur = pending_event; prev = NULL; |
| while (cur && cur->priority<=priority) { |
| prev = cur; |
| cur = cur->next; |
| } |
| event->next = cur; |
| if (prev) prev->next = event; |
| else pending_event = event; |
| |
| if (!old_pending && DOSVM_HasPendingEvents()) { |
| TRACE("new event queued, signalling (time=%d)\n", GetTickCount()); |
| |
| /* Alert VM86 thread about the new event. */ |
| kill(dosvm_pid,SIGUSR2); |
| |
| /* Wake up DOSVM_Wait so that it can serve pending events. */ |
| SetEvent(event_notifier); |
| } else { |
| TRACE("new event queued (time=%d)\n", GetTickCount()); |
| } |
| |
| LeaveCriticalSection(&qcrit); |
| } else { |
| /* DOS subsystem not running */ |
| /* (this probably means that we're running a win16 app |
| * which uses DPMI to thunk down to DOS services) */ |
| if (irq<0) { |
| /* callback event, perform it with dummy context */ |
| CONTEXT context; |
| memset(&context,0,sizeof(context)); |
| (*relay)(&context,data); |
| } else { |
| ERR("IRQ without DOS task: should not happen\n"); |
| } |
| } |
| } |
| |
| static void DOSVM_ProcessConsole(void) |
| { |
| INPUT_RECORD msg; |
| DWORD res; |
| BYTE scan, ascii; |
| |
| if (ReadConsoleInputA(GetStdHandle(STD_INPUT_HANDLE),&msg,1,&res)) { |
| switch (msg.EventType) { |
| case KEY_EVENT: |
| scan = msg.Event.KeyEvent.wVirtualScanCode; |
| ascii = msg.Event.KeyEvent.uChar.AsciiChar; |
| TRACE("scan %02x, ascii %02x\n", scan, ascii); |
| |
| /* set the "break" (release) flag if key released */ |
| if (!msg.Event.KeyEvent.bKeyDown) scan |= 0x80; |
| |
| /* check whether extended bit is set, |
| * and if so, queue the extension prefix */ |
| if (msg.Event.KeyEvent.dwControlKeyState & ENHANCED_KEY) { |
| DOSVM_Int09SendScan(0xE0,0); |
| } |
| DOSVM_Int09SendScan(scan, ascii); |
| break; |
| case MOUSE_EVENT: |
| DOSVM_Int33Console(&msg.Event.MouseEvent); |
| break; |
| case WINDOW_BUFFER_SIZE_EVENT: |
| FIXME("unhandled WINDOW_BUFFER_SIZE_EVENT.\n"); |
| break; |
| case MENU_EVENT: |
| FIXME("unhandled MENU_EVENT.\n"); |
| break; |
| case FOCUS_EVENT: |
| FIXME("unhandled FOCUS_EVENT.\n"); |
| break; |
| default: |
| FIXME("unknown console event: %d\n", msg.EventType); |
| } |
| } |
| } |
| |
| static void DOSVM_ProcessMessage(MSG *msg) |
| { |
| BYTE scan = 0; |
| |
| TRACE("got message %04x, wparam=%08lx, lparam=%08lx\n",msg->message,msg->wParam,msg->lParam); |
| if ((msg->message>=WM_MOUSEFIRST)&& |
| (msg->message<=WM_MOUSELAST)) { |
| DOSVM_Int33Message(msg->message,msg->wParam,msg->lParam); |
| } else { |
| switch (msg->message) { |
| case WM_KEYUP: |
| scan = 0x80; |
| case WM_KEYDOWN: |
| scan |= (msg->lParam >> 16) & 0x7f; |
| |
| /* check whether extended bit is set, |
| * and if so, queue the extension prefix */ |
| if (msg->lParam & 0x1000000) { |
| /* FIXME: some keys (function keys) have |
| * extended bit set even when they shouldn't, |
| * should check for them */ |
| DOSVM_Int09SendScan(0xE0,0); |
| } |
| DOSVM_Int09SendScan(scan,0); |
| break; |
| } |
| } |
| } |
| |
| |
| /*********************************************************************** |
| * DOSVM_Wait |
| * |
| * Wait for asynchronous events. This routine temporarily enables |
| * interrupts and waits until some asynchronous event has been |
| * processed. |
| */ |
| void DOSVM_Wait( CONTEXT *waitctx ) |
| { |
| if (DOSVM_HasPendingEvents()) |
| { |
| CONTEXT context = *waitctx; |
| |
| /* |
| * If DOSVM_Wait is called from protected mode we emulate |
| * interrupt reflection and convert context into real mode context. |
| * This is actually the correct thing to do as long as DOSVM_Wait |
| * is only called from those interrupt functions that DPMI reflects |
| * to real mode. |
| * |
| * FIXME: Need to think about where to place real mode stack. |
| * FIXME: If DOSVM_Wait calls are nested stack gets corrupted. |
| * Can this really happen? |
| */ |
| if (!ISV86(&context)) |
| { |
| context.EFlags |= V86_FLAG; |
| context.SegSs = 0xffff; |
| context.Esp = 0; |
| } |
| |
| context.EFlags |= VIF_MASK; |
| context.SegCs = 0; |
| context.Eip = 0; |
| |
| DOSVM_SendQueuedEvents(&context); |
| |
| if(context.SegCs || context.Eip) |
| DPMI_CallRMProc( &context, NULL, 0, TRUE ); |
| } |
| else |
| { |
| HANDLE objs[2]; |
| int objc = DOSVM_IsWin16() ? 2 : 1; |
| DWORD waitret; |
| |
| objs[0] = event_notifier; |
| objs[1] = GetStdHandle(STD_INPUT_HANDLE); |
| |
| waitret = MsgWaitForMultipleObjects( objc, objs, FALSE, |
| INFINITE, QS_ALLINPUT ); |
| |
| if (waitret == WAIT_OBJECT_0) |
| { |
| /* |
| * New pending event has been queued, we ignore it |
| * here because it will be processed on next call to |
| * DOSVM_Wait. |
| */ |
| } |
| else if (objc == 2 && waitret == WAIT_OBJECT_0 + 1) |
| { |
| DOSVM_ProcessConsole(); |
| } |
| else if (waitret == WAIT_OBJECT_0 + objc) |
| { |
| MSG msg; |
| while (PeekMessageA(&msg,0,0,0,PM_REMOVE|PM_NOYIELD)) |
| { |
| /* got a message */ |
| DOSVM_ProcessMessage(&msg); |
| /* we don't need a TranslateMessage here */ |
| DispatchMessageA(&msg); |
| } |
| } |
| else |
| { |
| ERR_(module)( "dosvm wait error=%d\n", GetLastError() ); |
| } |
| } |
| } |
| |
| |
| DWORD DOSVM_Loop( HANDLE hThread ) |
| { |
| HANDLE objs[2]; |
| int count = 0; |
| MSG msg; |
| DWORD waitret; |
| |
| objs[count++] = hThread; |
| if (GetConsoleMode( GetStdHandle(STD_INPUT_HANDLE), NULL )) |
| objs[count++] = GetStdHandle(STD_INPUT_HANDLE); |
| |
| for(;;) { |
| TRACE_(int)("waiting for action\n"); |
| waitret = MsgWaitForMultipleObjects(count, objs, FALSE, INFINITE, QS_ALLINPUT); |
| if (waitret == WAIT_OBJECT_0) { |
| DWORD rv; |
| if(!GetExitCodeThread(hThread, &rv)) { |
| ERR("Failed to get thread exit code!\n"); |
| rv = 0; |
| } |
| return rv; |
| } |
| else if (waitret == WAIT_OBJECT_0 + count) { |
| while (PeekMessageA(&msg,0,0,0,PM_REMOVE)) { |
| if (msg.hwnd) { |
| /* it's a window message */ |
| DOSVM_ProcessMessage(&msg); |
| DispatchMessageA(&msg); |
| } else { |
| /* it's a thread message */ |
| switch (msg.message) { |
| case WM_QUIT: |
| /* stop this madness!! */ |
| return 0; |
| case WM_USER: |
| /* run passed procedure in this thread */ |
| /* (sort of like APC, but we signal the completion) */ |
| { |
| DOS_SPC *spc = (DOS_SPC *)msg.lParam; |
| TRACE_(int)("calling %p with arg %08lx\n", spc->proc, spc->arg); |
| (spc->proc)(spc->arg); |
| TRACE_(int)("done, signalling event %lx\n", msg.wParam); |
| SetEvent( (HANDLE)msg.wParam ); |
| } |
| break; |
| default: |
| DispatchMessageA(&msg); |
| } |
| } |
| } |
| } |
| else if (waitret == WAIT_OBJECT_0 + 1) |
| { |
| DOSVM_ProcessConsole(); |
| } |
| else |
| { |
| ERR_(int)("MsgWaitForMultipleObjects returned unexpected value.\n"); |
| return 0; |
| } |
| } |
| } |
| |
| static LONG WINAPI exception_handler(EXCEPTION_POINTERS *eptr) |
| { |
| EXCEPTION_RECORD *rec = eptr->ExceptionRecord; |
| CONTEXT *context = eptr->ContextRecord; |
| int arg = rec->ExceptionInformation[0]; |
| BOOL ret; |
| |
| switch(rec->ExceptionCode) { |
| case EXCEPTION_VM86_INTx: |
| TRACE_(relay)("Call DOS int 0x%02x ret=%04x:%04x\n" |
| " eax=%08x ebx=%08x ecx=%08x edx=%08x esi=%08x edi=%08x\n" |
| " ebp=%08x esp=%08x ds=%04x es=%04x fs=%04x gs=%04x flags=%08x\n", |
| arg, context->SegCs, context->Eip, |
| context->Eax, context->Ebx, context->Ecx, context->Edx, context->Esi, context->Edi, |
| context->Ebp, context->Esp, context->SegDs, context->SegEs, context->SegFs, context->SegGs, |
| context->EFlags ); |
| ret = DOSVM_EmulateInterruptRM( context, arg ); |
| TRACE_(relay)("Ret DOS int 0x%02x ret=%04x:%04x\n" |
| " eax=%08x ebx=%08x ecx=%08x edx=%08x esi=%08x edi=%08x\n" |
| " ebp=%08x esp=%08x ds=%04x es=%04x fs=%04x gs=%04x flags=%08x\n", |
| arg, context->SegCs, context->Eip, |
| context->Eax, context->Ebx, context->Ecx, context->Edx, context->Esi, context->Edi, |
| context->Ebp, context->Esp, context->SegDs, context->SegEs, |
| context->SegFs, context->SegGs, context->EFlags ); |
| return ret ? EXCEPTION_CONTINUE_EXECUTION : EXCEPTION_EXECUTE_HANDLER; |
| |
| case EXCEPTION_VM86_STI: |
| /* case EXCEPTION_VM86_PICRETURN: */ |
| if (!ISV86(context)) |
| ERR( "Protected mode STI caught by real mode handler!\n" ); |
| DOSVM_SendQueuedEvents(context); |
| return EXCEPTION_CONTINUE_EXECUTION; |
| |
| case EXCEPTION_SINGLE_STEP: |
| ret = DOSVM_EmulateInterruptRM( context, 1 ); |
| return ret ? EXCEPTION_CONTINUE_EXECUTION : EXCEPTION_EXECUTE_HANDLER; |
| |
| case EXCEPTION_BREAKPOINT: |
| ret = DOSVM_EmulateInterruptRM( context, 3 ); |
| return ret ? EXCEPTION_CONTINUE_EXECUTION : EXCEPTION_EXECUTE_HANDLER; |
| |
| } |
| return EXCEPTION_CONTINUE_SEARCH; |
| } |
| |
| INT DOSVM_Enter( CONTEXT *context ) |
| { |
| INT ret = 0; |
| if (!ISV86(context)) |
| ERR( "Called with protected mode context!\n" ); |
| |
| __TRY |
| { |
| if (!WOWCallback16Ex( 0, WCB16_REGS, 0, NULL, (DWORD *)context )) ret = -1; |
| TRACE_(module)( "ret %d err %u\n", ret, GetLastError() ); |
| } |
| __EXCEPT(exception_handler) |
| { |
| TRACE_(module)( "leaving vm86 mode\n" ); |
| } |
| __ENDTRY |
| |
| return ret; |
| } |
| |
| /*********************************************************************** |
| * DOSVM_PIC_ioport_out |
| */ |
| void DOSVM_PIC_ioport_out( WORD port, BYTE val) |
| { |
| if (port != 0x20) |
| { |
| FIXME( "Unsupported PIC port %04x\n", port ); |
| } |
| else if (val == 0x20 || (val >= 0x60 && val <= 0x67)) |
| { |
| EnterCriticalSection(&qcrit); |
| |
| if (!current_event) |
| { |
| WARN( "%s without active IRQ\n", |
| val == 0x20 ? "EOI" : "Specific EOI" ); |
| } |
| else if (val != 0x20 && val - 0x60 != current_event->irq) |
| { |
| WARN( "Specific EOI but current IRQ %d is not %d\n", |
| current_event->irq, val - 0x60 ); |
| } |
| else |
| { |
| LPDOSEVENT event = current_event; |
| |
| TRACE( "Received %s for current IRQ %d, clearing event\n", |
| val == 0x20 ? "EOI" : "Specific EOI", event->irq ); |
| |
| current_event = event->next; |
| if (event->relay) |
| (*event->relay)(NULL,event->data); |
| HeapFree(GetProcessHeap(), 0, event); |
| |
| if (DOSVM_HasPendingEvents()) |
| { |
| TRACE( "Another event pending, setting pending flag\n" ); |
| get_vm86_teb_info()->vm86_pending |= VIP_MASK; |
| } |
| } |
| |
| LeaveCriticalSection(&qcrit); |
| } |
| else |
| { |
| FIXME( "Unrecognized PIC command %02x\n", val ); |
| } |
| } |
| |
| #else /* !MZ_SUPPORTED */ |
| |
| /*********************************************************************** |
| * DOSVM_Enter |
| */ |
| INT DOSVM_Enter( CONTEXT *context ) |
| { |
| SetLastError( ERROR_NOT_SUPPORTED ); |
| return -1; |
| } |
| |
| /*********************************************************************** |
| * DOSVM_Wait |
| */ |
| void DOSVM_Wait( CONTEXT *waitctx ) { } |
| |
| /*********************************************************************** |
| * DOSVM_PIC_ioport_out |
| */ |
| void DOSVM_PIC_ioport_out( WORD port, BYTE val) {} |
| |
| /*********************************************************************** |
| * DOSVM_QueueEvent |
| */ |
| void DOSVM_QueueEvent( INT irq, INT priority, DOSRELAY relay, LPVOID data) |
| { |
| if (irq<0) { |
| /* callback event, perform it with dummy context */ |
| CONTEXT context; |
| memset(&context,0,sizeof(context)); |
| (*relay)(&context,data); |
| } else { |
| ERR("IRQ without DOS task: should not happen\n"); |
| } |
| } |
| |
| #endif /* MZ_SUPPORTED */ |
| |
| |
| /********************************************************************** |
| * DOSVM_AcknowledgeIRQ |
| * |
| * This routine should be called by all internal IRQ handlers. |
| */ |
| void WINAPI DOSVM_AcknowledgeIRQ( CONTEXT *context ) |
| { |
| /* |
| * Send EOI to PIC. |
| */ |
| DOSVM_PIC_ioport_out( 0x20, 0x20 ); |
| |
| /* |
| * Protected mode IRQ handlers are supposed |
| * to turn VIF flag on before they return. |
| */ |
| if (!ISV86(context)) |
| get_vm86_teb_info()->dpmi_vif = 1; |
| } |
| |
| |
| /*********************************************************************** |
| * DOSVM_AllocUMB |
| * |
| * Allocate upper memory block (UMB) from upper memory. |
| * Returned pointer is aligned to 16-byte (paragraph) boundary. |
| * |
| * This routine is only for allocating static storage for |
| * Wine internal uses. Allocated memory can be accessed from |
| * real mode, memory is taken from area already mapped and reserved |
| * by Wine and the allocation has very little memory and speed |
| * overhead. Use of this routine also preserves precious DOS |
| * conventional memory. |
| */ |
| static LPVOID DOSVM_AllocUMB( DWORD size ) |
| { |
| LPVOID ptr = (LPVOID)DOSVM_umb_free; |
| |
| size = ((size + 15) >> 4) << 4; |
| |
| if(DOSVM_umb_free + size - 1 > DOSVM_UMB_TOP) { |
| ERR("Out of upper memory area.\n"); |
| return 0; |
| } |
| |
| DOSVM_umb_free += size; |
| return ptr; |
| } |
| |
| |
| /********************************************************************** |
| * alloc_selector |
| * |
| * Allocate a selector corresponding to a real mode address. |
| * size must be < 64k. |
| */ |
| static WORD alloc_selector( void *base, DWORD size, unsigned char flags ) |
| { |
| WORD sel = wine_ldt_alloc_entries( 1 ); |
| |
| if (sel) |
| { |
| LDT_ENTRY entry; |
| wine_ldt_set_base( &entry, base ); |
| wine_ldt_set_limit( &entry, size - 1 ); |
| wine_ldt_set_flags( &entry, flags ); |
| wine_ldt_set_entry( sel, &entry ); |
| } |
| return sel; |
| } |
| |
| |
| /*********************************************************************** |
| * DOSVM_AllocCodeUMB |
| * |
| * Allocate upper memory block for storing code stubs. |
| * Initializes real mode segment and 16-bit protected mode selector |
| * for the allocated code block. |
| * |
| * FIXME: should allocate a single PM selector for the whole UMB range. |
| */ |
| static LPVOID DOSVM_AllocCodeUMB( DWORD size, WORD *segment, WORD *selector ) |
| { |
| LPVOID ptr = DOSVM_AllocUMB( size ); |
| |
| if (segment) |
| *segment = (DWORD)ptr >> 4; |
| |
| if (selector) |
| *selector = alloc_selector( ptr, size, WINE_LDT_FLAGS_CODE ); |
| |
| return ptr; |
| } |
| |
| |
| /*********************************************************************** |
| * DOSVM_AllocDataUMB |
| * |
| * Allocate upper memory block for storing data. |
| * Initializes real mode segment and 16-bit protected mode selector |
| * for the allocated data block. |
| */ |
| LPVOID DOSVM_AllocDataUMB( DWORD size, WORD *segment, WORD *selector ) |
| { |
| LPVOID ptr = DOSVM_AllocUMB( size ); |
| |
| if (segment) |
| *segment = (DWORD)ptr >> 4; |
| |
| if (selector) |
| *selector = alloc_selector( ptr, size, WINE_LDT_FLAGS_DATA ); |
| |
| return ptr; |
| } |
| |
| |
| /*********************************************************************** |
| * DOSVM_InitSegments |
| * |
| * Initializes DOSVM_dpmi_segments. Allocates required memory and |
| * sets up segments and selectors for accessing the memory. |
| */ |
| void DOSVM_InitSegments(void) |
| { |
| LPSTR ptr; |
| int i; |
| |
| static const char wrap_code[]={ |
| 0xCD,0x31, /* int $0x31 */ |
| 0xCB /* lret */ |
| }; |
| |
| static const char enter_xms[]= |
| { |
| /* XMS hookable entry point */ |
| 0xEB,0x03, /* jmp entry */ |
| 0x90,0x90,0x90, /* nop;nop;nop */ |
| /* entry: */ |
| /* real entry point */ |
| /* for simplicity, we'll just use the same hook as DPMI below */ |
| 0xCD,0x31, /* int $0x31 */ |
| 0xCB /* lret */ |
| }; |
| |
| static const char enter_pm[]= |
| { |
| 0x50, /* pushw %ax */ |
| 0x52, /* pushw %dx */ |
| 0x55, /* pushw %bp */ |
| 0x89,0xE5, /* movw %sp,%bp */ |
| /* get return CS */ |
| 0x8B,0x56,0x08, /* movw 8(%bp),%dx */ |
| /* just call int 31 here to get into protected mode... */ |
| /* it'll check whether it was called from dpmi_seg... */ |
| 0xCD,0x31, /* int $0x31 */ |
| /* we are now in the context of a 16-bit relay call */ |
| /* need to fixup our stack; |
| * 16-bit relay return address will be lost, |
| * but we won't worry quite yet |
| */ |
| 0x8E,0xD0, /* movw %ax,%ss */ |
| 0x66,0x0F,0xB7,0xE5, /* movzwl %bp,%esp */ |
| /* set return CS */ |
| 0x89,0x56,0x08, /* movw %dx,8(%bp) */ |
| 0x5D, /* popw %bp */ |
| 0x5A, /* popw %dx */ |
| 0x58, /* popw %ax */ |
| 0xfb, /* sti, enable and check virtual interrupts */ |
| 0xCB /* lret */ |
| }; |
| |
| static const char relay[]= |
| { |
| 0xca, 0x04, 0x00, /* 16-bit far return and pop 4 bytes (relay void* arg) */ |
| 0xcd, 0x31, /* int 31 */ |
| 0xfb, 0x66, 0xcb /* sti and 32-bit far return */ |
| }; |
| |
| /* |
| * Allocate pointer array. |
| */ |
| DOSVM_dpmi_segments = DOSVM_AllocUMB( sizeof(struct DPMI_segments) ); |
| |
| /* |
| * RM / offset 0: Exit from real mode. |
| * RM / offset 2: Points to lret opcode. |
| */ |
| ptr = DOSVM_AllocCodeUMB( sizeof(wrap_code), |
| &DOSVM_dpmi_segments->wrap_seg, 0 ); |
| memcpy( ptr, wrap_code, sizeof(wrap_code) ); |
| |
| /* |
| * RM / offset 0: XMS driver entry. |
| */ |
| ptr = DOSVM_AllocCodeUMB( sizeof(enter_xms), |
| &DOSVM_dpmi_segments->xms_seg, 0 ); |
| memcpy( ptr, enter_xms, sizeof(enter_xms) ); |
| |
| /* |
| * RM / offset 0: Switch to DPMI. |
| * PM / offset 8: DPMI raw mode switch. |
| */ |
| ptr = DOSVM_AllocCodeUMB( sizeof(enter_pm), |
| &DOSVM_dpmi_segments->dpmi_seg, |
| &DOSVM_dpmi_segments->dpmi_sel ); |
| memcpy( ptr, enter_pm, sizeof(enter_pm) ); |
| |
| /* |
| * PM / offset N*6: Interrupt N in DPMI32. |
| */ |
| ptr = DOSVM_AllocCodeUMB( 6 * 256, |
| 0, &DOSVM_dpmi_segments->int48_sel ); |
| for(i=0; i<256; i++) { |
| /* |
| * Each 32-bit interrupt handler is 6 bytes: |
| * 0xCD,<i> = int <i> (nested 16-bit interrupt) |
| * 0x66,0xCA,0x04,0x00 = ret 4 (32-bit far return and pop 4 bytes / eflags) |
| */ |
| ptr[i * 6 + 0] = 0xCD; |
| ptr[i * 6 + 1] = i; |
| ptr[i * 6 + 2] = 0x66; |
| ptr[i * 6 + 3] = 0xCA; |
| ptr[i * 6 + 4] = 0x04; |
| ptr[i * 6 + 5] = 0x00; |
| } |
| |
| /* |
| * PM / offset N*5: Interrupt N in 16-bit protected mode. |
| */ |
| ptr = DOSVM_AllocCodeUMB( 5 * 256, |
| 0, &DOSVM_dpmi_segments->int16_sel ); |
| for(i=0; i<256; i++) { |
| /* |
| * Each 16-bit interrupt handler is 5 bytes: |
| * 0xCD,<i> = int <i> (interrupt) |
| * 0xCA,0x02,0x00 = ret 2 (16-bit far return and pop 2 bytes / eflags) |
| */ |
| ptr[i * 5 + 0] = 0xCD; |
| ptr[i * 5 + 1] = i; |
| ptr[i * 5 + 2] = 0xCA; |
| ptr[i * 5 + 3] = 0x02; |
| ptr[i * 5 + 4] = 0x00; |
| } |
| |
| /* |
| * PM / offset 0: Stub where __wine_call_from_16_regs returns. |
| * PM / offset 3: Stub which swaps back to 32-bit application code/stack. |
| * PM / offset 5: Stub which enables interrupts |
| */ |
| ptr = DOSVM_AllocCodeUMB( sizeof(relay), |
| 0, &DOSVM_dpmi_segments->relay_code_sel); |
| memcpy( ptr, relay, sizeof(relay) ); |
| |
| /* |
| * Space for 16-bit stack used by relay code. |
| */ |
| ptr = DOSVM_AllocDataUMB( DOSVM_RELAY_DATA_SIZE, |
| 0, &DOSVM_dpmi_segments->relay_data_sel); |
| memset( ptr, 0, DOSVM_RELAY_DATA_SIZE ); |
| |
| /* |
| * As we store code in UMB we should make sure it is executable |
| */ |
| VirtualProtect((void *)DOSVM_UMB_BOTTOM, DOSVM_UMB_TOP - DOSVM_UMB_BOTTOM, PAGE_EXECUTE_READWRITE, NULL); |
| |
| event_notifier = CreateEventW(NULL, FALSE, FALSE, NULL); |
| } |