Small resources fixes.
[wine] / dlls / winedos / dosvm.c
1 /*
2  * DOS Virtual Machine
3  *
4  * Copyright 1998 Ove Kåven
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  *
20  * Note: This code hasn't been completely cleaned up yet.
21  */
22
23 #include "config.h"
24
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <errno.h>
29 #include <fcntl.h>
30 #include <signal.h>
31 #ifdef HAVE_UNISTD_H
32 # include <unistd.h>
33 #endif
34 #ifdef HAVE_SYS_TIME_H
35 # include <sys/time.h>
36 #endif
37 #include <sys/types.h>
38
39 #include "wine/winbase16.h"
40 #include "wine/exception.h"
41 #include "windef.h"
42 #include "winbase.h"
43 #include "wingdi.h"
44 #include "winuser.h"
45 #include "winnt.h"
46 #include "wincon.h"
47
48 #include "msdos.h"
49 #include "file.h"
50 #include "miscemu.h"
51 #include "dosexe.h"
52 #include "dosvm.h"
53 #include "stackframe.h"
54 #include "wine/debug.h"
55 #include "excpt.h"
56
57 WINE_DEFAULT_DEBUG_CHANNEL(int);
58 WINE_DECLARE_DEBUG_CHANNEL(module);
59 WINE_DECLARE_DEBUG_CHANNEL(relay);
60
61 WORD DOSVM_psp = 0;
62 WORD DOSVM_retval = 0;
63
64 #ifdef HAVE_SYS_VM86_H
65 # include <sys/vm86.h>
66 #endif
67 #ifdef HAVE_SYS_MMAN_H
68 # include <sys/mman.h>
69 #endif
70
71
72 typedef struct _DOSEVENT {
73   int irq,priority;
74   DOSRELAY relay;
75   void *data;
76   struct _DOSEVENT *next;
77 } DOSEVENT, *LPDOSEVENT;
78
79 static struct _DOSEVENT *pending_event, *current_event;
80 static HANDLE event_notifier;
81
82 static CRITICAL_SECTION qcrit;
83 static CRITICAL_SECTION_DEBUG critsect_debug =
84 {
85     0, 0, &qcrit,
86     { &critsect_debug.ProcessLocksList, &critsect_debug.ProcessLocksList },
87       0, 0, { 0, (DWORD)(__FILE__ ": qcrit") }
88 };
89 static CRITICAL_SECTION qcrit = { &critsect_debug, -1, 0, 0, 0, 0 };
90
91
92 /***********************************************************************
93  *              DOSVM_HasPendingEvents
94  *
95  * Return true if there are pending events that are not
96  * blocked by currently active event.
97  */
98 static BOOL DOSVM_HasPendingEvents( void )
99 {   
100     if (!pending_event)
101         return FALSE;
102
103     if (!current_event)
104         return TRUE;
105
106     if (pending_event->priority < current_event->priority)
107         return TRUE;
108
109     return FALSE;
110 }
111
112
113 /***********************************************************************
114  *              DOSVM_SendOneEvent
115  *
116  * Process single pending event.
117  *
118  * This function should be called with queue critical section locked. 
119  * The function temporarily releases the critical section if it is 
120  * possible that internal interrupt handler or user procedure will 
121  * be called. This is because we may otherwise get a deadlock if
122  * another thread is waiting for the same critical section.
123  */
124 static void DOSVM_SendOneEvent( CONTEXT86 *context )
125 {
126     LPDOSEVENT event = pending_event;
127
128     /* Remove from pending events list. */
129     pending_event = event->next;
130
131     /* Process active event. */
132     if (event->irq >= 0) 
133     {
134         BYTE intnum = (event->irq < 8) ?
135             (event->irq + 8) : (event->irq - 8 + 0x70);
136             
137         /* Event is an IRQ, move it to current events list. */
138         event->next = current_event;
139         current_event = event;
140
141         TRACE( "Dispatching IRQ %d.\n", event->irq );
142
143         if (ISV86(context))
144         {
145             /* 
146              * Note that if DOSVM_HardwareInterruptRM calls an internal 
147              * interrupt directly, current_event might be cleared 
148              * (and event freed) in this call.
149              */
150             LeaveCriticalSection(&qcrit);
151             DOSVM_HardwareInterruptRM( context, intnum );
152             EnterCriticalSection(&qcrit);
153         }
154         else
155         {
156             /*
157              * This routine only modifies current context so it is
158              * not necessary to release critical section.
159              */
160             DOSVM_HardwareInterruptPM( context, intnum );
161         }
162     } 
163     else 
164     {
165         /* Callback event. */
166         TRACE( "Dispatching callback event.\n" );
167
168         if (ISV86(context))
169         {
170             /*
171              * Call relay immediately in real mode.
172              */
173             LeaveCriticalSection(&qcrit);
174             (*event->relay)( context, event->data );
175             EnterCriticalSection(&qcrit);
176         }
177         else
178         {
179             /*
180              * Force return to relay code. We do not want to
181              * call relay directly because we may be inside a signal handler.
182              */
183             DOSVM_BuildCallFrame( context, event->relay, event->data );
184         }
185
186         free(event);
187     }
188 }
189
190
191 /***********************************************************************
192  *              DOSVM_SendQueuedEvents
193  *
194  * As long as context instruction pointer stays unmodified,
195  * process all pending events that are not blocked by currently
196  * active event.
197  *
198  * This routine assumes that caller has already cleared TEB.vm86_pending 
199  * and checked that interrupts are enabled.
200  */
201 void DOSVM_SendQueuedEvents( CONTEXT86 *context )
202 {   
203     DWORD old_cs = context->SegCs;
204     DWORD old_ip = context->Eip;
205
206     EnterCriticalSection(&qcrit);
207
208     TRACE( "Called in %s mode %s events pending (time=%ld)\n",
209            ISV86(context) ? "real" : "protected",
210            DOSVM_HasPendingEvents() ? "with" : "without",
211            GetTickCount() );
212     TRACE( "cs:ip=%04lx:%08lx, ss:sp=%04lx:%08lx\n",
213            context->SegCs, context->Eip, context->SegSs, context->Esp);
214
215     while (context->SegCs == old_cs &&
216            context->Eip == old_ip &&
217            DOSVM_HasPendingEvents())
218     {
219         DOSVM_SendOneEvent(context);
220
221         /*
222          * Event handling may have turned pending events flag on.
223          * We disable it here because this prevents some
224          * unnecessary calls to this function.
225          */
226         NtCurrentTeb()->vm86_pending = 0;
227     }
228
229 #ifdef MZ_SUPPORTED
230
231     if (!ISV86(context) && context->SegCs == old_cs && context->Eip == old_ip)
232     {
233         /*
234          * Routine was called from DPMI but there was nothing to do.
235          * We force a dummy relay call here so that we don't get a race
236          * if signals are unblocked when we return to DPMI application.
237          */
238         TRACE( "Called but there was nothing to do, calling NULL relay.\n" );
239         DOSVM_BuildCallFrame( context, NULL, NULL );
240     }
241
242     if (DOSVM_HasPendingEvents())
243     {
244         /*
245          * Interrupts disabled, but there are still
246          * pending events, make sure that pending flag is turned on.
247          */
248         TRACE( "Another event is pending, setting VIP flag.\n" );
249         NtCurrentTeb()->vm86_pending |= VIP_MASK;
250     }
251
252 #else
253
254     FIXME("No DOS .exe file support on this platform (yet)\n");
255
256 #endif /* MZ_SUPPORTED */
257
258     LeaveCriticalSection(&qcrit);
259 }
260
261
262 #ifdef MZ_SUPPORTED
263 /***********************************************************************
264  *              QueueEvent (WINEDOS.@)
265  */
266 void WINAPI DOSVM_QueueEvent( INT irq, INT priority, DOSRELAY relay, LPVOID data)
267 {
268   LPDOSEVENT event, cur, prev;
269   BOOL       old_pending;
270
271   if (MZ_Current()) {
272     event = malloc(sizeof(DOSEVENT));
273     if (!event) {
274       ERR("out of memory allocating event entry\n");
275       return;
276     }
277     event->irq = irq; event->priority = priority;
278     event->relay = relay; event->data = data;
279
280     EnterCriticalSection(&qcrit);
281     old_pending = DOSVM_HasPendingEvents();
282
283     /* insert event into linked list, in order *after*
284      * all earlier events of higher or equal priority */
285     cur = pending_event; prev = NULL;
286     while (cur && cur->priority<=priority) {
287       prev = cur;
288       cur = cur->next;
289     }
290     event->next = cur;
291     if (prev) prev->next = event;
292     else pending_event = event;
293
294     if (!old_pending && DOSVM_HasPendingEvents()) {
295       TRACE("new event queued, signalling (time=%ld)\n", GetTickCount());
296       
297       /* Alert VM86 thread about the new event. */
298       kill(dosvm_pid,SIGUSR2);
299
300       /* Wake up DOSVM_Wait so that it can serve pending events. */
301       SetEvent(event_notifier);
302     } else {
303       TRACE("new event queued (time=%ld)\n", GetTickCount());
304     }
305
306     LeaveCriticalSection(&qcrit);
307   } else {
308     /* DOS subsystem not running */
309     /* (this probably means that we're running a win16 app
310      *  which uses DPMI to thunk down to DOS services) */
311     if (irq<0) {
312       /* callback event, perform it with dummy context */
313       CONTEXT86 context;
314       memset(&context,0,sizeof(context));
315       (*relay)(&context,data);
316     } else {
317       ERR("IRQ without DOS task: should not happen\n");
318     }
319   }
320 }
321
322 static void DOSVM_ProcessConsole(void)
323 {
324   INPUT_RECORD msg;
325   DWORD res;
326   BYTE scan, ascii;
327
328   if (ReadConsoleInputA(GetStdHandle(STD_INPUT_HANDLE),&msg,1,&res)) {
329     switch (msg.EventType) {
330     case KEY_EVENT:
331       scan = msg.Event.KeyEvent.wVirtualScanCode;
332       ascii = msg.Event.KeyEvent.uChar.AsciiChar;
333       TRACE("scan %02x, ascii %02x\n", scan, ascii);
334
335       /* set the "break" (release) flag if key released */
336       if (!msg.Event.KeyEvent.bKeyDown) scan |= 0x80;
337
338       /* check whether extended bit is set,
339        * and if so, queue the extension prefix */
340       if (msg.Event.KeyEvent.dwControlKeyState & ENHANCED_KEY) {
341         DOSVM_Int09SendScan(0xE0,0);
342       }
343       DOSVM_Int09SendScan(scan, ascii);
344       break;
345     case MOUSE_EVENT:
346       DOSVM_Int33Console(&msg.Event.MouseEvent);
347       break;
348     case WINDOW_BUFFER_SIZE_EVENT:
349       FIXME("unhandled WINDOW_BUFFER_SIZE_EVENT.\n");
350       break;
351     case MENU_EVENT:
352       FIXME("unhandled MENU_EVENT.\n");
353       break;
354     case FOCUS_EVENT:
355       FIXME("unhandled FOCUS_EVENT.\n");
356       break;
357     default:
358       FIXME("unknown console event: %d\n", msg.EventType);
359     }
360   }
361 }
362
363 static void DOSVM_ProcessMessage(MSG *msg)
364 {
365   BYTE scan = 0;
366
367   TRACE("got message %04x, wparam=%08x, lparam=%08lx\n",msg->message,msg->wParam,msg->lParam);
368   if ((msg->message>=WM_MOUSEFIRST)&&
369       (msg->message<=WM_MOUSELAST)) {
370     DOSVM_Int33Message(msg->message,msg->wParam,msg->lParam);
371   } else {
372     switch (msg->message) {
373     case WM_KEYUP:
374       scan = 0x80;
375     case WM_KEYDOWN:
376       scan |= (msg->lParam >> 16) & 0x7f;
377
378       /* check whether extended bit is set,
379        * and if so, queue the extension prefix */
380       if (msg->lParam & 0x1000000) {
381         /* FIXME: some keys (function keys) have
382          * extended bit set even when they shouldn't,
383          * should check for them */
384         DOSVM_Int09SendScan(0xE0,0);
385       }
386       DOSVM_Int09SendScan(scan,0);
387       break;
388     }
389   }
390 }
391
392
393 /***********************************************************************
394  *              DOSVM_Wait
395  *
396  * Wait for asynchronous events. This routine temporarily enables
397  * interrupts and waits until some asynchronous event has been 
398  * processed.
399  */
400 void WINAPI DOSVM_Wait( CONTEXT86 *waitctx )
401 {
402     if (DOSVM_HasPendingEvents())
403     {
404         CONTEXT86 context = *waitctx;
405         
406         /*
407          * If DOSVM_Wait is called from protected mode we emulate
408          * interrupt reflection and convert context into real mode context.
409          * This is actually the correct thing to do as long as DOSVM_Wait
410          * is only called from those interrupt functions that DPMI reflects
411          * to real mode.
412          *
413          * FIXME: Need to think about where to place real mode stack.
414          * FIXME: If DOSVM_Wait calls are nested stack gets corrupted.
415          *        Can this really happen?
416          */
417         if (!ISV86(&context))
418         {
419             context.EFlags |= 0x00020000;
420             context.SegSs = 0xffff;
421             context.Esp = 0;
422         }
423
424         context.EFlags |= VIF_MASK;
425         context.SegCs = 0;
426         context.Eip = 0;
427
428         DOSVM_SendQueuedEvents(&context);
429
430         if(context.SegCs || context.Eip)
431             DPMI_CallRMProc( &context, NULL, 0, TRUE );
432     }
433     else
434     {
435         HANDLE objs[2];
436         int    objc = DOSVM_IsWin16() ? 2 : 1;
437         DWORD  waitret;
438
439         objs[0] = event_notifier;
440         objs[1] = GetStdHandle(STD_INPUT_HANDLE);
441
442         waitret = MsgWaitForMultipleObjects( objc, objs, FALSE, 
443                                              INFINITE, QS_ALLINPUT );
444         
445         if (waitret == WAIT_OBJECT_0)
446         {
447             /*
448              * New pending event has been queued, we ignore it
449              * here because it will be processed on next call to
450              * DOSVM_Wait.
451              */
452         }
453         else if (objc == 2 && waitret == WAIT_OBJECT_0 + 1)
454         {
455             DOSVM_ProcessConsole();
456         }
457         else if (waitret == WAIT_OBJECT_0 + objc)
458         {
459             MSG msg;
460             while (PeekMessageA(&msg,0,0,0,PM_REMOVE|PM_NOYIELD)) 
461             {
462                 /* got a message */
463                 DOSVM_ProcessMessage(&msg);
464                 /* we don't need a TranslateMessage here */
465                 DispatchMessageA(&msg);
466             }
467         }
468         else
469         {
470             ERR_(module)( "dosvm wait error=%ld\n", GetLastError() );
471         }
472     }
473 }
474
475
476 DWORD WINAPI DOSVM_Loop( HANDLE hThread )
477 {
478   HANDLE objs[2];
479   MSG msg;
480   DWORD waitret;
481
482   objs[0] = GetStdHandle(STD_INPUT_HANDLE);
483   objs[1] = hThread;
484
485   for(;;) {
486       TRACE_(int)("waiting for action\n");
487       waitret = MsgWaitForMultipleObjects(2, objs, FALSE, INFINITE, QS_ALLINPUT);
488       if (waitret == WAIT_OBJECT_0) {
489           DOSVM_ProcessConsole();
490       }
491       else if (waitret == WAIT_OBJECT_0 + 1) {
492          DWORD rv;
493          if(!GetExitCodeThread(hThread, &rv)) {
494              ERR("Failed to get thread exit code!\n");
495              rv = 0;
496          }
497          return rv;
498       }
499       else if (waitret == WAIT_OBJECT_0 + 2) {
500           while (PeekMessageA(&msg,0,0,0,PM_REMOVE)) {
501               if (msg.hwnd) {
502                   /* it's a window message */
503                   DOSVM_ProcessMessage(&msg);
504                   DispatchMessageA(&msg);
505               } else {
506                   /* it's a thread message */
507                   switch (msg.message) {
508                   case WM_QUIT:
509                       /* stop this madness!! */
510                       return 0;
511                   case WM_USER:
512                       /* run passed procedure in this thread */
513                       /* (sort of like APC, but we signal the completion) */
514                       {
515                           DOS_SPC *spc = (DOS_SPC *)msg.lParam;
516                           TRACE_(int)("calling %p with arg %08lx\n", spc->proc, spc->arg);
517                           (spc->proc)(spc->arg);
518                           TRACE_(int)("done, signalling event %x\n", msg.wParam);
519                           SetEvent( (HANDLE)msg.wParam );
520                       }
521                       break;
522                   default:
523                       DispatchMessageA(&msg);
524                   }
525               }
526           }
527       }
528       else
529       {
530           ERR_(int)("MsgWaitForMultipleObjects returned unexpected value.\n");
531           return 0;
532       }
533   }
534 }
535
536 static WINE_EXCEPTION_FILTER(exception_handler)
537 {
538   EXCEPTION_RECORD *rec = GetExceptionInformation()->ExceptionRecord;
539   CONTEXT *context = GetExceptionInformation()->ContextRecord;
540   int arg = rec->ExceptionInformation[0];
541   BOOL ret;
542
543   switch(rec->ExceptionCode) {
544   case EXCEPTION_VM86_INTx:
545     if (TRACE_ON(relay)) {
546       DPRINTF("Call DOS int 0x%02x ret=%04lx:%04lx\n",
547               arg, context->SegCs, context->Eip );
548       DPRINTF(" eax=%08lx ebx=%08lx ecx=%08lx edx=%08lx esi=%08lx edi=%08lx\n",
549               context->Eax, context->Ebx, context->Ecx, context->Edx,
550               context->Esi, context->Edi );
551       DPRINTF(" ebp=%08lx esp=%08lx ds=%04lx es=%04lx fs=%04lx gs=%04lx flags=%08lx\n",
552               context->Ebp, context->Esp, context->SegDs, context->SegEs,
553               context->SegFs, context->SegGs, context->EFlags );
554       }
555     ret = DOSVM_EmulateInterruptRM( context, arg );
556     if (TRACE_ON(relay)) {
557       DPRINTF("Ret  DOS int 0x%02x ret=%04lx:%04lx\n",
558               arg, context->SegCs, context->Eip );
559       DPRINTF(" eax=%08lx ebx=%08lx ecx=%08lx edx=%08lx esi=%08lx edi=%08lx\n",
560               context->Eax, context->Ebx, context->Ecx, context->Edx,
561               context->Esi, context->Edi );
562       DPRINTF(" ebp=%08lx esp=%08lx ds=%04lx es=%04lx fs=%04lx gs=%04lx flags=%08lx\n",
563               context->Ebp, context->Esp, context->SegDs, context->SegEs,
564               context->SegFs, context->SegGs, context->EFlags );
565     }
566     return ret ? EXCEPTION_CONTINUE_EXECUTION : EXCEPTION_EXECUTE_HANDLER;
567
568   case EXCEPTION_VM86_STI:
569   /* case EXCEPTION_VM86_PICRETURN: */
570     if (!ISV86(context))
571       ERR( "Protected mode STI caught by real mode handler!\n" );
572
573     context->EFlags |= VIF_MASK;
574     context->EFlags &= ~VIP_MASK;
575     DOSVM_SendQueuedEvents(context);
576     return EXCEPTION_CONTINUE_EXECUTION;
577   }
578   return EXCEPTION_CONTINUE_SEARCH;
579 }
580
581 int WINAPI DOSVM_Enter( CONTEXT86 *context )
582 {
583   /* Some callers forget to turn V86_FLAG on. */
584   context->EFlags |= V86_FLAG;
585
586   __TRY
587   {
588     __wine_enter_vm86( context );
589     TRACE_(module)( "vm86 returned: %s\n", strerror(errno) );
590   }
591   __EXCEPT(exception_handler)
592   {
593     TRACE_(module)( "leaving vm86 mode\n" );
594   }
595   __ENDTRY
596
597   return 0;
598 }
599
600 /***********************************************************************
601  *              OutPIC (WINEDOS.@)
602  */
603 void WINAPI DOSVM_PIC_ioport_out( WORD port, BYTE val)
604 {
605     LPDOSEVENT event;
606
607     if ((port==0x20) && (val==0x20)) {
608       EnterCriticalSection(&qcrit);
609       if (current_event) {
610         /* EOI (End Of Interrupt) */
611         TRACE("received EOI for current IRQ, clearing\n");
612         event = current_event;
613         current_event = event->next;
614         if (event->relay)
615         (*event->relay)(NULL,event->data);
616         free(event);
617
618         if (DOSVM_HasPendingEvents()) {
619           /* another event is pending, which we should probably
620            * be able to process now */
621           TRACE("another event pending, setting flag\n");
622           NtCurrentTeb()->vm86_pending |= VIP_MASK;
623         }
624       } else {
625         WARN("EOI without active IRQ\n");
626       }
627       LeaveCriticalSection(&qcrit);
628     } else {
629       FIXME("unrecognized PIC command %02x\n",val);
630     }
631 }
632
633 #else /* !MZ_SUPPORTED */
634
635 /***********************************************************************
636  *              Enter (WINEDOS.@)
637  */
638 INT WINAPI DOSVM_Enter( CONTEXT86 *context )
639 {
640  ERR_(module)("DOS realmode not supported on this architecture!\n");
641  return -1;
642 }
643
644 /***********************************************************************
645  *              Wait (WINEDOS.@)
646  */
647 void WINAPI DOSVM_Wait( CONTEXT86 *waitctx ) { }
648
649 /***********************************************************************
650  *              OutPIC (WINEDOS.@)
651  */
652 void WINAPI DOSVM_PIC_ioport_out( WORD port, BYTE val) {}
653
654 /***********************************************************************
655  *              QueueEvent (WINEDOS.@)
656  */
657 void WINAPI DOSVM_QueueEvent( INT irq, INT priority, DOSRELAY relay, LPVOID data)
658 {
659   if (irq<0) {
660     /* callback event, perform it with dummy context */
661     CONTEXT86 context;
662     memset(&context,0,sizeof(context));
663     (*relay)(&context,data);
664   } else {
665     ERR("IRQ without DOS task: should not happen\n");
666   }
667 }
668
669 #endif /* MZ_SUPPORTED */
670
671
672 /**********************************************************************
673  *         DOSVM_AcknowledgeIRQ
674  *
675  * This routine should be called by all internal IRQ handlers.
676  */
677 void WINAPI DOSVM_AcknowledgeIRQ( CONTEXT86 *context )
678 {
679     /*
680      * Send EOI to PIC.
681      */
682     DOSVM_PIC_ioport_out( 0x20, 0x20 );
683
684     /*
685      * Protected mode IRQ handlers are supposed
686      * to turn VIF flag on before they return.
687      */
688     if (!ISV86(context))
689         NtCurrentTeb()->dpmi_vif = 1;
690 }
691
692
693 /**********************************************************************
694  *          DllMain  (DOSVM.Init)
695  */
696 BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved )
697 {
698     TRACE_(module)("(%p,%ld,%p)\n", hinstDLL, fdwReason, lpvReserved);
699
700     if (fdwReason == DLL_PROCESS_ATTACH)
701     {
702         DisableThreadLibraryCalls(hinstDLL);
703         DOSVM_InitSegments();
704
705         event_notifier = CreateEventA(NULL, FALSE, FALSE, NULL);
706         if(!event_notifier)
707           ERR("Failed to create event object!\n");
708     }
709     return TRUE;
710 }