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