wine/dlls/winedos/dosvm.c
Jukka Heinonen 4ef7ba7247 Real mode context should always have V86 flag turned on.
Entering protected mode uses simpler and more robust virtual interrupt
flag check.
Mouse events are now received in graphics modes.
Improved tracing output of int33 handler.
2003-07-08 21:11:52 +00:00

703 lines
20 KiB
C

/*
* DOS Virtual Machine
*
* Copyright 1998 Ove Kåven
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Note: This code hasn't been completely cleaned up yet.
*/
#include "config.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 "wingdi.h"
#include "winuser.h"
#include "winnt.h"
#include "wincon.h"
#include "msdos.h"
#include "file.h"
#include "miscemu.h"
#include "dosexe.h"
#include "dosvm.h"
#include "stackframe.h"
#include "wine/debug.h"
#include "excpt.h"
WINE_DEFAULT_DEBUG_CHANNEL(int);
WINE_DECLARE_DEBUG_CHANNEL(module);
WINE_DECLARE_DEBUG_CHANNEL(relay);
WORD DOSVM_psp = 0;
WORD DOSVM_retval = 0;
#ifdef HAVE_SYS_VM86_H
# include <sys/vm86.h>
#endif
#ifdef HAVE_SYS_MMAN_H
# include <sys/mman.h>
#endif
typedef struct _DOSEVENT {
int irq,priority;
DOSRELAY relay;
void *data;
struct _DOSEVENT *next;
} DOSEVENT, *LPDOSEVENT;
static CRITICAL_SECTION qcrit = CRITICAL_SECTION_INIT("DOSVM");
static struct _DOSEVENT *pending_event, *current_event;
static HANDLE event_notifier;
/***********************************************************************
* 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( CONTEXT86 *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 );
}
free(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( CONTEXT86 *context )
{
DWORD old_cs = context->SegCs;
DWORD old_ip = context->Eip;
EnterCriticalSection(&qcrit);
TRACE( "Called in %s mode %s events pending (time=%ld)\n",
ISV86(context) ? "real" : "protected",
DOSVM_HasPendingEvents() ? "with" : "without",
GetTickCount() );
TRACE( "cs:ip=%04lx:%08lx, ss:sp=%04lx:%08lx\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.
*/
NtCurrentTeb()->vm86_pending = 0;
}
#ifdef MZ_SUPPORTED
if (!ISV86(context) && context->SegCs == old_cs && context->Eip == old_ip)
{
/*
* Routine was called from DPMI but there was nothing to do.
* We force a dummy relay call here so that we don't get a race
* if signals are unblocked when we return to DPMI application.
*/
TRACE( "Called but there was nothing to do, calling NULL relay.\n" );
DOSVM_BuildCallFrame( context, NULL, NULL );
}
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" );
NtCurrentTeb()->vm86_pending |= VIP_MASK;
}
#else
FIXME("No DOS .exe file support on this platform (yet)\n");
#endif /* MZ_SUPPORTED */
LeaveCriticalSection(&qcrit);
}
#ifdef MZ_SUPPORTED
/***********************************************************************
* QueueEvent (WINEDOS.@)
*/
void WINAPI DOSVM_QueueEvent( INT irq, INT priority, DOSRELAY relay, LPVOID data)
{
LPDOSEVENT event, cur, prev;
BOOL old_pending;
if (MZ_Current()) {
event = malloc(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=%ld)\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=%ld)\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 */
CONTEXT86 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=%08x, 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 WINAPI DOSVM_Wait( CONTEXT86 *waitctx )
{
if (DOSVM_HasPendingEvents())
{
CONTEXT86 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 |= 0x00020000;
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=%ld\n", GetLastError() );
}
}
}
DWORD WINAPI DOSVM_Loop( HANDLE hThread )
{
HANDLE objs[2];
MSG msg;
DWORD waitret;
objs[0] = GetStdHandle(STD_INPUT_HANDLE);
objs[1] = hThread;
for(;;) {
TRACE_(int)("waiting for action\n");
waitret = MsgWaitForMultipleObjects(2, objs, FALSE, INFINITE, QS_ALLINPUT);
if (waitret == WAIT_OBJECT_0) {
DOSVM_ProcessConsole();
}
else if (waitret == WAIT_OBJECT_0 + 1) {
DWORD rv;
if(!GetExitCodeThread(hThread, &rv)) {
ERR("Failed to get thread exit code!\n");
rv = 0;
}
return rv;
}
else if (waitret == WAIT_OBJECT_0 + 2) {
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 %x\n", msg.wParam);
SetEvent( (HANDLE)msg.wParam );
}
break;
default:
DispatchMessageA(&msg);
}
}
}
}
else
{
ERR_(int)("MsgWaitForMultipleObjects returned unexpected value.\n");
return 0;
}
}
}
static WINE_EXCEPTION_FILTER(exception_handler)
{
EXCEPTION_RECORD *rec = GetExceptionInformation()->ExceptionRecord;
CONTEXT *context = GetExceptionInformation()->ContextRecord;
int arg = rec->ExceptionInformation[0];
BOOL ret;
switch(rec->ExceptionCode) {
case EXCEPTION_VM86_INTx:
if (TRACE_ON(relay)) {
DPRINTF("Call DOS int 0x%02x ret=%04lx:%04lx\n",
arg, context->SegCs, context->Eip );
DPRINTF(" eax=%08lx ebx=%08lx ecx=%08lx edx=%08lx esi=%08lx edi=%08lx\n",
context->Eax, context->Ebx, context->Ecx, context->Edx,
context->Esi, context->Edi );
DPRINTF(" ebp=%08lx esp=%08lx ds=%04lx es=%04lx fs=%04lx gs=%04lx flags=%08lx\n",
context->Ebp, context->Esp, context->SegDs, context->SegEs,
context->SegFs, context->SegGs, context->EFlags );
}
ret = DOSVM_EmulateInterruptRM( context, arg );
if (TRACE_ON(relay)) {
DPRINTF("Ret DOS int 0x%02x ret=%04lx:%04lx\n",
arg, context->SegCs, context->Eip );
DPRINTF(" eax=%08lx ebx=%08lx ecx=%08lx edx=%08lx esi=%08lx edi=%08lx\n",
context->Eax, context->Ebx, context->Ecx, context->Edx,
context->Esi, context->Edi );
DPRINTF(" ebp=%08lx esp=%08lx ds=%04lx es=%04lx fs=%04lx gs=%04lx flags=%08lx\n",
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" );
context->EFlags |= VIF_MASK;
context->EFlags &= ~VIP_MASK;
DOSVM_SendQueuedEvents(context);
return EXCEPTION_CONTINUE_EXECUTION;
}
return EXCEPTION_CONTINUE_SEARCH;
}
int WINAPI DOSVM_Enter( CONTEXT86 *context )
{
/* Some callers forget to turn V86_FLAG on. */
context->EFlags |= V86_FLAG;
__TRY
{
__wine_enter_vm86( context );
TRACE_(module)( "vm86 returned: %s\n", strerror(errno) );
}
__EXCEPT(exception_handler)
{
TRACE_(module)( "leaving vm86 mode\n" );
}
__ENDTRY
return 0;
}
/***********************************************************************
* OutPIC (WINEDOS.@)
*/
void WINAPI DOSVM_PIC_ioport_out( WORD port, BYTE val)
{
LPDOSEVENT event;
if ((port==0x20) && (val==0x20)) {
EnterCriticalSection(&qcrit);
if (current_event) {
/* EOI (End Of Interrupt) */
TRACE("received EOI for current IRQ, clearing\n");
event = current_event;
current_event = event->next;
if (event->relay)
(*event->relay)(NULL,event->data);
free(event);
if (DOSVM_HasPendingEvents()) {
/* another event is pending, which we should probably
* be able to process now */
TRACE("another event pending, setting flag\n");
NtCurrentTeb()->vm86_pending |= VIP_MASK;
}
} else {
WARN("EOI without active IRQ\n");
}
LeaveCriticalSection(&qcrit);
} else {
FIXME("unrecognized PIC command %02x\n",val);
}
}
#else /* !MZ_SUPPORTED */
/***********************************************************************
* Enter (WINEDOS.@)
*/
INT WINAPI DOSVM_Enter( CONTEXT86 *context )
{
ERR_(module)("DOS realmode not supported on this architecture!\n");
return -1;
}
/***********************************************************************
* Wait (WINEDOS.@)
*/
void WINAPI DOSVM_Wait( CONTEXT86 *waitctx ) { }
/***********************************************************************
* OutPIC (WINEDOS.@)
*/
void WINAPI DOSVM_PIC_ioport_out( WORD port, BYTE val) {}
/***********************************************************************
* QueueEvent (WINEDOS.@)
*/
void WINAPI DOSVM_QueueEvent( INT irq, INT priority, DOSRELAY relay, LPVOID data)
{
if (irq<0) {
/* callback event, perform it with dummy context */
CONTEXT86 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( CONTEXT86 *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))
NtCurrentTeb()->dpmi_vif = 1;
}
/**********************************************************************
* DllMain (DOSVM.Init)
*/
BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved )
{
TRACE_(module)("(%p,%ld,%p)\n", hinstDLL, fdwReason, lpvReserved);
if (fdwReason == DLL_PROCESS_ATTACH)
{
DisableThreadLibraryCalls(hinstDLL);
DOSVM_InitSegments();
event_notifier = CreateEventA(NULL, FALSE, FALSE, NULL);
if(!event_notifier)
ERR("Failed to create event object!\n");
}
return TRUE;
}