Fixed non-x86 DOSVM_Wait prototype.
[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 MZ_SUPPORTED
65
66 #ifdef HAVE_SYS_VM86_H
67 # include <sys/vm86.h>
68 #endif
69 #ifdef HAVE_SYS_MMAN_H
70 # include <sys/mman.h>
71 #endif
72
73 #define IF_CLR(ctx)     ((ctx)->EFlags &= ~VIF_MASK)
74 #define IF_SET(ctx)     ((ctx)->EFlags |= VIF_MASK)
75 #define IF_ENABLED(ctx) ((ctx)->EFlags & VIF_MASK)
76 #define SET_PEND(ctx)   ((ctx)->EFlags |= VIP_MASK)
77 #define CLR_PEND(ctx)   ((ctx)->EFlags &= ~VIP_MASK)
78 #define IS_PEND(ctx)    ((ctx)->EFlags & VIP_MASK)
79
80 #undef TRY_PICRETURN
81
82 typedef struct _DOSEVENT {
83   int irq,priority;
84   DOSRELAY relay;
85   void *data;
86   struct _DOSEVENT *next;
87 } DOSEVENT, *LPDOSEVENT;
88
89 static CRITICAL_SECTION qcrit = CRITICAL_SECTION_INIT("DOSVM");
90 static struct _DOSEVENT *pending_event, *current_event;
91 static int sig_sent;
92 static HANDLE event_notifier;
93
94 #define SHOULD_PEND(x) \
95   (x && ((!current_event) || (x->priority < current_event->priority)))
96
97 static void DOSVM_SendQueuedEvent(CONTEXT86 *context)
98 {
99   LPDOSEVENT event = pending_event;
100
101   if (SHOULD_PEND(event)) {
102     /* remove from "pending" list */
103     pending_event = event->next;
104     /* process event */
105     if (event->irq>=0) {
106       /* it's an IRQ, move it to "current" list */
107       event->next = current_event;
108       current_event = event;
109       TRACE("dispatching IRQ %d\n",event->irq);
110       /* note that if DOSVM_SimulateInt calls an internal interrupt directly,
111        * current_event might be cleared (and event freed) in this very call! */
112       DOSVM_HardwareInterruptRM( context, (event->irq < 8) ? 
113                                  (event->irq + 8) : (event->irq - 8 + 0x70) );
114     } else {
115       /* callback event */
116       TRACE("dispatching callback event\n");
117       (*event->relay)(context,event->data);
118       free(event);
119     }
120   }
121   if (!SHOULD_PEND(pending_event)) {
122     TRACE("clearing Pending flag\n");
123     CLR_PEND(context);
124   }
125 }
126
127 static void DOSVM_SendQueuedEvents(CONTEXT86 *context)
128 {
129   /* we will send all queued events as long as interrupts are enabled,
130    * but IRQ events will disable interrupts again */
131   while (IS_PEND(context) && IF_ENABLED(context))
132     DOSVM_SendQueuedEvent(context);
133 }
134
135 /***********************************************************************
136  *              QueueEvent (WINEDOS.@)
137  */
138 void WINAPI DOSVM_QueueEvent( INT irq, INT priority, DOSRELAY relay, LPVOID data)
139 {
140   LPDOSEVENT event, cur, prev;
141
142   if (MZ_Current()) {
143     event = malloc(sizeof(DOSEVENT));
144     if (!event) {
145       ERR("out of memory allocating event entry\n");
146       return;
147     }
148     event->irq = irq; event->priority = priority;
149     event->relay = relay; event->data = data;
150
151     EnterCriticalSection(&qcrit);
152     /* insert event into linked list, in order *after*
153      * all earlier events of higher or equal priority */
154     cur = pending_event; prev = NULL;
155     while (cur && cur->priority<=priority) {
156       prev = cur;
157       cur = cur->next;
158     }
159     event->next = cur;
160     if (prev) prev->next = event;
161     else pending_event = event;
162
163     /* alert the vm86 about the new event */
164     if (!sig_sent) {
165       TRACE("new event queued, signalling (time=%ld)\n", GetTickCount());
166       kill(dosvm_pid,SIGUSR2);
167       sig_sent++;
168     } else {
169       TRACE("new event queued (time=%ld)\n", GetTickCount());
170     }
171
172     /* Wake up DOSVM_Wait so that it can serve pending events. */
173     SetEvent(event_notifier);
174
175     LeaveCriticalSection(&qcrit);
176   } else {
177     /* DOS subsystem not running */
178     /* (this probably means that we're running a win16 app
179      *  which uses DPMI to thunk down to DOS services) */
180     if (irq<0) {
181       /* callback event, perform it with dummy context */
182       CONTEXT86 context;
183       memset(&context,0,sizeof(context));
184       (*relay)(&context,data);
185     } else {
186       ERR("IRQ without DOS task: should not happen\n");
187     }
188   }
189 }
190
191 static void DOSVM_ProcessConsole(void)
192 {
193   INPUT_RECORD msg;
194   DWORD res;
195   BYTE scan, ascii;
196
197   if (ReadConsoleInputA(GetStdHandle(STD_INPUT_HANDLE),&msg,1,&res)) {
198     switch (msg.EventType) {
199     case KEY_EVENT:
200       scan = msg.Event.KeyEvent.wVirtualScanCode;
201       ascii = msg.Event.KeyEvent.uChar.AsciiChar;
202       TRACE("scan %02x, ascii %02x\n", scan, ascii);
203
204       /* set the "break" (release) flag if key released */
205       if (!msg.Event.KeyEvent.bKeyDown) scan |= 0x80;
206
207       /* check whether extended bit is set,
208        * and if so, queue the extension prefix */
209       if (msg.Event.KeyEvent.dwControlKeyState & ENHANCED_KEY) {
210         DOSVM_Int09SendScan(0xE0,0);
211       }
212       DOSVM_Int09SendScan(scan, ascii);
213       break;
214     case MOUSE_EVENT:
215       DOSVM_Int33Console(&msg.Event.MouseEvent);
216       break;
217     case WINDOW_BUFFER_SIZE_EVENT:
218       FIXME("unhandled WINDOW_BUFFER_SIZE_EVENT.\n");
219       break;
220     case MENU_EVENT:
221       FIXME("unhandled MENU_EVENT.\n");
222       break;
223     case FOCUS_EVENT:
224       FIXME("unhandled FOCUS_EVENT.\n");
225       break;
226     default:
227       FIXME("unknown console event: %d\n", msg.EventType);
228     }
229   }
230 }
231
232 static void DOSVM_ProcessMessage(MSG *msg)
233 {
234   BYTE scan = 0;
235
236   TRACE("got message %04x, wparam=%08x, lparam=%08lx\n",msg->message,msg->wParam,msg->lParam);
237   if ((msg->message>=WM_MOUSEFIRST)&&
238       (msg->message<=WM_MOUSELAST)) {
239     DOSVM_Int33Message(msg->message,msg->wParam,msg->lParam);
240   } else {
241     switch (msg->message) {
242     case WM_KEYUP:
243       scan = 0x80;
244     case WM_KEYDOWN:
245       scan |= (msg->lParam >> 16) & 0x7f;
246
247       /* check whether extended bit is set,
248        * and if so, queue the extension prefix */
249       if (msg->lParam & 0x1000000) {
250         /* FIXME: some keys (function keys) have
251          * extended bit set even when they shouldn't,
252          * should check for them */
253         DOSVM_Int09SendScan(0xE0,0);
254       }
255       DOSVM_Int09SendScan(scan,0);
256       break;
257     }
258   }
259 }
260
261
262 /***********************************************************************
263  *              DOSVM_Wait
264  *
265  * Wait for asynchronous events. This routine temporarily enables
266  * interrupts and waits until some asynchronous event has been 
267  * processed.
268  */
269 void WINAPI DOSVM_Wait( CONTEXT86 *waitctx )
270 {
271     if (SHOULD_PEND(pending_event)) 
272     {
273         /*
274          * FIXME: This does not work in protected mode DOS programs.
275          * FIXME: If we have pending IRQ which has 16-bit handler,
276          *        DOSVM_SendQueuedEvents may stuck in which case application
277          *        deadlocks. This is why keyboard events must have top 
278          *        priority (default timer IRQ handler is 16-bit code).
279          * FIXME: Critical section locking is broken.
280          */
281         CONTEXT86 context = *waitctx;
282         IF_SET(&context);
283         SET_PEND(&context);
284         DOSVM_SendQueuedEvents(&context);
285     }
286     else
287     {
288         HANDLE objs[2];
289         int    objc = DOSVM_IsWin16() ? 2 : 1;
290         DWORD  waitret;
291
292         objs[0] = event_notifier;
293         objs[1] = GetStdHandle(STD_INPUT_HANDLE);
294
295         waitret = MsgWaitForMultipleObjects( objc, objs, FALSE, 
296                                              INFINITE, QS_ALLINPUT );
297         
298         if (waitret == WAIT_OBJECT_0)
299         {
300             /*
301              * New pending event has been queued, we ignore it
302              * here because it will be processed on next call to
303              * DOSVM_Wait.
304              */
305         }
306         else if (objc == 2 && waitret == WAIT_OBJECT_0 + 1)
307         {
308             DOSVM_ProcessConsole();
309         }
310         else if (waitret == WAIT_OBJECT_0 + objc)
311         {
312             MSG msg;
313             while (PeekMessageA(&msg,0,0,0,PM_REMOVE|PM_NOYIELD)) 
314             {
315                 /* got a message */
316                 DOSVM_ProcessMessage(&msg);
317                 /* we don't need a TranslateMessage here */
318                 DispatchMessageA(&msg);
319             }
320         }
321         else
322         {
323             ERR_(module)( "dosvm wait error=%ld\n", GetLastError() );
324         }
325     }
326 }
327
328
329 DWORD WINAPI DOSVM_Loop( HANDLE hThread )
330 {
331   HANDLE objs[2];
332   MSG msg;
333   DWORD waitret;
334
335   objs[0] = GetStdHandle(STD_INPUT_HANDLE);
336   objs[1] = hThread;
337
338   for(;;) {
339       TRACE_(int)("waiting for action\n");
340       waitret = MsgWaitForMultipleObjects(2, objs, FALSE, INFINITE, QS_ALLINPUT);
341       if (waitret == WAIT_OBJECT_0) {
342           DOSVM_ProcessConsole();
343       }
344       else if (waitret == WAIT_OBJECT_0 + 1) {
345          DWORD rv;
346          if(!GetExitCodeThread(hThread, &rv)) {
347              ERR("Failed to get thread exit code!\n");
348              rv = 0;
349          }
350          return rv;
351       }
352       else if (waitret == WAIT_OBJECT_0 + 2) {
353           while (PeekMessageA(&msg,0,0,0,PM_REMOVE)) {
354               if (msg.hwnd) {
355                   /* it's a window message */
356                   DOSVM_ProcessMessage(&msg);
357                   DispatchMessageA(&msg);
358               } else {
359                   /* it's a thread message */
360                   switch (msg.message) {
361                   case WM_QUIT:
362                       /* stop this madness!! */
363                       return 0;
364                   case WM_USER:
365                       /* run passed procedure in this thread */
366                       /* (sort of like APC, but we signal the completion) */
367                       {
368                           DOS_SPC *spc = (DOS_SPC *)msg.lParam;
369                           TRACE_(int)("calling %p with arg %08lx\n", spc->proc, spc->arg);
370                           (spc->proc)(spc->arg);
371                           TRACE_(int)("done, signalling event %x\n", msg.wParam);
372                           SetEvent( (HANDLE)msg.wParam );
373                       }
374                       break;
375                   }
376               }
377           }
378       }
379       else
380       {
381           ERR_(int)("MsgWaitForMultipleObjects returned unexpected value.\n");
382           return 0;
383       }
384   }
385 }
386
387 static WINE_EXCEPTION_FILTER(exception_handler)
388 {
389   EXCEPTION_RECORD *rec = GetExceptionInformation()->ExceptionRecord;
390   CONTEXT *context = GetExceptionInformation()->ContextRecord;
391   int arg = rec->ExceptionInformation[0];
392   BOOL ret;
393
394   switch(rec->ExceptionCode) {
395   case EXCEPTION_VM86_INTx:
396     if (TRACE_ON(relay)) {
397       DPRINTF("Call DOS int 0x%02x ret=%04lx:%04lx\n",
398               arg, context->SegCs, context->Eip );
399       DPRINTF(" eax=%08lx ebx=%08lx ecx=%08lx edx=%08lx esi=%08lx edi=%08lx\n",
400               context->Eax, context->Ebx, context->Ecx, context->Edx,
401               context->Esi, context->Edi );
402       DPRINTF(" ebp=%08lx esp=%08lx ds=%04lx es=%04lx fs=%04lx gs=%04lx flags=%08lx\n",
403               context->Ebp, context->Esp, context->SegDs, context->SegEs,
404               context->SegFs, context->SegGs, context->EFlags );
405       }
406     ret = DOSVM_EmulateInterruptRM( context, arg );
407     if (TRACE_ON(relay)) {
408       DPRINTF("Ret  DOS int 0x%02x ret=%04lx:%04lx\n",
409               arg, context->SegCs, context->Eip );
410       DPRINTF(" eax=%08lx ebx=%08lx ecx=%08lx edx=%08lx esi=%08lx edi=%08lx\n",
411               context->Eax, context->Ebx, context->Ecx, context->Edx,
412               context->Esi, context->Edi );
413       DPRINTF(" ebp=%08lx esp=%08lx ds=%04lx es=%04lx fs=%04lx gs=%04lx flags=%08lx\n",
414               context->Ebp, context->Esp, context->SegDs, context->SegEs,
415               context->SegFs, context->SegGs, context->EFlags );
416     }
417     return ret ? EXCEPTION_CONTINUE_EXECUTION : EXCEPTION_EXECUTE_HANDLER;
418
419   case EXCEPTION_VM86_STI:
420   /* case EXCEPTION_VM86_PICRETURN: */
421     IF_SET(context);
422     EnterCriticalSection(&qcrit);
423     sig_sent++;
424     while (NtCurrentTeb()->alarms) {
425       DOSVM_QueueEvent(0,DOS_PRIORITY_REALTIME,NULL,NULL);
426       /* hmm, instead of relying on this signal counter, we should
427        * probably check how many ticks have *really* passed, probably using
428        * QueryPerformanceCounter() or something like that */
429       InterlockedDecrement(&(NtCurrentTeb()->alarms));
430     }
431     TRACE_(int)("context=%p\n", context);
432     TRACE_(int)("cs:ip=%04lx:%04lx, ss:sp=%04lx:%04lx\n", context->SegCs, context->Eip, context->SegSs, context->Esp);
433     if (!ISV86(context)) {
434       ERR_(int)("@#&*%%, winedos signal handling is *still* messed up\n");
435     }
436     TRACE_(int)("DOS task enabled interrupts %s events pending, sending events (time=%ld)\n", IS_PEND(context)?"with":"without", GetTickCount());
437     DOSVM_SendQueuedEvents(context);
438     sig_sent=0;
439     LeaveCriticalSection(&qcrit);
440     return EXCEPTION_CONTINUE_EXECUTION;
441   }
442   return EXCEPTION_CONTINUE_SEARCH;
443 }
444
445 int WINAPI DOSVM_Enter( CONTEXT86 *context )
446 {
447   __TRY
448   {
449     __wine_enter_vm86( context );
450     TRACE_(module)( "vm86 returned: %s\n", strerror(errno) );
451   }
452   __EXCEPT(exception_handler)
453   {
454     TRACE_(module)( "leaving vm86 mode\n" );
455   }
456   __ENDTRY
457
458   return 0;
459 }
460
461 /***********************************************************************
462  *              OutPIC (WINEDOS.@)
463  */
464 void WINAPI DOSVM_PIC_ioport_out( WORD port, BYTE val)
465 {
466     LPDOSEVENT event;
467
468     if ((port==0x20) && (val==0x20)) {
469       EnterCriticalSection(&qcrit);
470       if (current_event) {
471         /* EOI (End Of Interrupt) */
472         TRACE("received EOI for current IRQ, clearing\n");
473         event = current_event;
474         current_event = event->next;
475         if (event->relay)
476         (*event->relay)(NULL,event->data);
477         free(event);
478
479         if (pending_event) {
480           /* another event is pending, which we should probably
481            * be able to process now */
482           TRACE("another event pending, setting flag\n");
483           NtCurrentTeb()->vm86_pending |= VIP_MASK;
484         }
485       } else {
486         WARN("EOI without active IRQ\n");
487       }
488       LeaveCriticalSection(&qcrit);
489     } else {
490       FIXME("unrecognized PIC command %02x\n",val);
491     }
492 }
493
494 /***********************************************************************
495  *              SetTimer (WINEDOS.@)
496  */
497 void WINAPI DOSVM_SetTimer( UINT ticks )
498 {
499   struct itimerval tim;
500
501   if (dosvm_pid) {
502     /* the PC clocks ticks at 1193180 Hz */
503     tim.it_interval.tv_sec=0;
504     tim.it_interval.tv_usec=MulDiv(ticks,1000000,1193180);
505     /* sanity check */
506     if (!tim.it_interval.tv_usec) tim.it_interval.tv_usec=1;
507     /* first tick value */
508     tim.it_value = tim.it_interval;
509     TRACE_(int)("setting timer tick delay to %ld us\n", tim.it_interval.tv_usec);
510     setitimer(ITIMER_REAL, &tim, NULL);
511   }
512 }
513
514 /***********************************************************************
515  *              GetTimer (WINEDOS.@)
516  */
517 UINT WINAPI DOSVM_GetTimer( void )
518 {
519   struct itimerval tim;
520
521   if (dosvm_pid) {
522     getitimer(ITIMER_REAL, &tim);
523     return MulDiv(tim.it_value.tv_usec,1193180,1000000);
524   }
525   return 0;
526 }
527
528 #else /* !MZ_SUPPORTED */
529
530 /***********************************************************************
531  *              Enter (WINEDOS.@)
532  */
533 INT WINAPI DOSVM_Enter( CONTEXT86 *context )
534 {
535  ERR_(module)("DOS realmode not supported on this architecture!\n");
536  return -1;
537 }
538
539 /***********************************************************************
540  *              Wait (WINEDOS.@)
541  */
542 void WINAPI DOSVM_Wait( CONTEXT86 *waitctx ) { }
543
544 /***********************************************************************
545  *              OutPIC (WINEDOS.@)
546  */
547 void WINAPI DOSVM_PIC_ioport_out( WORD port, BYTE val) {}
548
549 /***********************************************************************
550  *              SetTimer (WINEDOS.@)
551  */
552 void WINAPI DOSVM_SetTimer( UINT ticks ) {}
553
554 /***********************************************************************
555  *              GetTimer (WINEDOS.@)
556  */
557 UINT WINAPI DOSVM_GetTimer( void ) { return 0; }
558
559 /***********************************************************************
560  *              QueueEvent (WINEDOS.@)
561  */
562 void WINAPI DOSVM_QueueEvent( INT irq, INT priority, DOSRELAY relay, LPVOID data)
563 {
564   if (irq<0) {
565     /* callback event, perform it with dummy context */
566     CONTEXT86 context;
567     memset(&context,0,sizeof(context));
568     (*relay)(&context,data);
569   } else {
570     ERR("IRQ without DOS task: should not happen\n");
571   }
572 }
573
574 #endif
575
576
577 /**********************************************************************
578  *          DllMain  (DOSVM.Init)
579  */
580 BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved )
581 {
582     TRACE_(module)("(%p,%ld,%p)\n", hinstDLL, fdwReason, lpvReserved);
583
584     if (fdwReason == DLL_PROCESS_ATTACH)
585     {
586         DOSVM_InitSegments();
587
588 #ifdef MZ_SUPPORTED
589         event_notifier = CreateEventA(NULL, FALSE, FALSE, NULL);
590         if(!event_notifier)
591           ERR("Failed to create event object!\n");
592 #endif
593
594     }
595     return TRUE;
596 }