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