Fixed other bugs within MMIO implementation. Now, it's possible to
[wine] / loader / dos / dosvm.c
1 /*
2  * DOS Virtual Machine
3  *
4  * Copyright 1998 Ove Kåven
5  *
6  * This code hasn't been completely cleaned up yet.
7  */
8
9 #include "config.h"
10
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <string.h>
14 #include <errno.h>
15 #include <fcntl.h>
16 #include <signal.h>
17 #include <unistd.h>
18 #include <sys/time.h>
19 #include <sys/types.h>
20 #include <sys/stat.h>
21
22 #include "wine/winbase16.h"
23 #include "wine/exception.h"
24 #include "windef.h"
25 #include "wingdi.h"
26 #include "winuser.h"
27 #include "winnt.h"
28
29 #include "callback.h"
30 #include "msdos.h"
31 #include "file.h"
32 #include "miscemu.h"
33 #include "dosexe.h"
34 #include "dosmod.h"
35 #include "stackframe.h"
36 #include "debugtools.h"
37
38 DECLARE_DEBUG_CHANNEL(int)
39 DECLARE_DEBUG_CHANNEL(module)
40 DECLARE_DEBUG_CHANNEL(relay)
41
42 #ifdef MZ_SUPPORTED
43
44 #ifdef HAVE_SYS_VM86_H
45 # include <sys/vm86.h>
46 #endif
47 #ifdef HAVE_SYS_MMAN_H
48 # include <sys/mman.h>
49 #endif
50
51 #define IF_CLR(ctx) EFL_reg(ctx) &= ~VIF_MASK
52 #define IF_ENABLED(ctx) (EFL_reg(ctx) & VIF_MASK)
53 #define SET_PEND(ctx) EFL_reg(ctx) |= VIP_MASK
54 #define CLR_PEND(ctx) EFL_reg(ctx) &= ~VIP_MASK
55 #define IS_PEND(ctx) (EFL_reg(ctx) & VIP_MASK)
56
57 #undef TRY_PICRETURN
58
59 static void do_exception( int signal, CONTEXT86 *context )
60 {
61     EXCEPTION_RECORD rec;
62     if ((signal == SIGTRAP) || (signal == SIGHUP))
63     {
64         rec.ExceptionCode  = EXCEPTION_BREAKPOINT;
65         rec.ExceptionFlags = EXCEPTION_CONTINUABLE;
66     }
67     else
68     {
69         rec.ExceptionCode  = EXCEPTION_ILLEGAL_INSTRUCTION;  /* generic error */
70         rec.ExceptionFlags = EH_NONCONTINUABLE;
71     }
72     rec.ExceptionRecord  = NULL;
73     rec.ExceptionAddress = (LPVOID)EIP_reg(context);
74     rec.NumberParameters = 0;
75     EXC_RtlRaiseException( &rec, context );
76 }
77
78 static void DOSVM_Dump( LPDOSTASK lpDosTask, int fn, int sig,
79                         struct vm86plus_struct*VM86 )
80 {
81  unsigned iofs;
82  BYTE*inst;
83  int x;
84
85  switch (VM86_TYPE(fn)) {
86   case VM86_SIGNAL:
87    printf("Trapped signal %d\n",sig); break;
88   case VM86_UNKNOWN:
89    printf("Trapped unhandled GPF\n"); break;
90   case VM86_INTx:
91    printf("Trapped INT %02x\n",VM86_ARG(fn)); break;
92   case VM86_STI:
93    printf("Trapped STI\n"); break;
94   case VM86_PICRETURN:
95    printf("Trapped due to pending PIC request\n"); break;
96   case VM86_TRAP:
97    printf("Trapped debug request\n"); break;
98   default:
99    printf("Trapped unknown VM86 type %d arg %d\n",VM86_TYPE(fn),VM86_ARG(fn)); break;
100  }
101 #define REGS VM86->regs
102  fprintf(stderr,"AX=%04lX CX=%04lX DX=%04lX BX=%04lX\n",REGS.eax,REGS.ecx,REGS.edx,REGS.ebx);
103  fprintf(stderr,"SI=%04lX DI=%04lX SP=%04lX BP=%04lX\n",REGS.esi,REGS.edi,REGS.esp,REGS.ebp);
104  fprintf(stderr,"CS=%04X DS=%04X ES=%04X SS=%04X\n",REGS.cs,REGS.ds,REGS.es,REGS.ss);
105  fprintf(stderr,"IP=%04lX EFLAGS=%08lX\n",REGS.eip,REGS.eflags);
106
107  iofs=((DWORD)REGS.cs<<4)+REGS.eip;
108 #undef REGS
109  inst=(BYTE*)lpDosTask->img+iofs;
110  printf("Opcodes:");
111  for (x=0; x<8; x++) printf(" %02x",inst[x]);
112  printf("\n");
113 }
114
115 static int DOSVM_Int( int vect, CONTEXT86 *context, LPDOSTASK lpDosTask )
116 {
117  extern UINT16 DPMI_wrap_seg;
118
119  if (vect==0x31) {
120   if (CS_reg(context)==DPMI_wrap_seg) {
121    /* exit from real-mode wrapper */
122    return -1;
123   }
124   /* we could probably move some other dodgy stuff here too from dpmi.c */
125  }
126  INT_RealModeInterrupt(vect,context);
127  return 0;
128 }
129
130 static void DOSVM_SimulateInt( int vect, CONTEXT86 *context, LPDOSTASK lpDosTask )
131 {
132   FARPROC16 handler=INT_GetRMHandler(vect);
133
134   if (SELECTOROF(handler)==0xf000) {
135     /* if internal interrupt, call it directly */
136     INT_RealModeInterrupt(vect,context);
137   } else {
138     WORD*stack=(WORD*)(V86BASE(context)+(((DWORD)SS_reg(context))<<4)+LOWORD(ESP_reg(context)));
139     WORD flag=LOWORD(EFL_reg(context));
140
141     if (IF_ENABLED(context)) flag|=IF_MASK;
142     else flag&=~IF_MASK;
143
144     *(--stack)=flag;
145     *(--stack)=CS_reg(context);
146     *(--stack)=LOWORD(EIP_reg(context));
147     ESP_reg(context)-=6;
148     CS_reg(context)=SELECTOROF(handler);
149     EIP_reg(context)=OFFSETOF(handler);
150     IF_CLR(context);
151   }
152 }
153
154 #define SHOULD_PEND(x) \
155   (x && ((!lpDosTask->current) || (x->priority < lpDosTask->current->priority)))
156
157 static void DOSVM_SendQueuedEvent(CONTEXT86 *context, LPDOSTASK lpDosTask)
158 {
159   LPDOSEVENT event = lpDosTask->pending;
160
161   if (SHOULD_PEND(event)) {
162     /* remove from "pending" list */
163     lpDosTask->pending = event->next;
164     /* process event */
165     if (event->irq>=0) {
166       /* it's an IRQ, move it to "current" list */
167       event->next = lpDosTask->current;
168       lpDosTask->current = event;
169       TRACE_(int)("dispatching IRQ %d\n",event->irq);
170       /* note that if DOSVM_SimulateInt calls an internal interrupt directly,
171        * lpDosTask->current might be cleared (and event freed) in this very call! */
172       DOSVM_SimulateInt((event->irq<8)?(event->irq+8):(event->irq-8+0x70),context,lpDosTask);
173     } else {
174       /* callback event */
175       TRACE_(int)("dispatching callback event\n");
176       (*event->relay)(lpDosTask,context,event->data);
177       free(event);
178     }
179   }
180   if (!SHOULD_PEND(lpDosTask->pending)) {
181     TRACE_(int)("clearing Pending flag\n");
182     CLR_PEND(context);
183   }
184 }
185
186 static void DOSVM_SendQueuedEvents(CONTEXT86 *context, LPDOSTASK lpDosTask)
187 {
188   /* we will send all queued events as long as interrupts are enabled,
189    * but IRQ events will disable interrupts again */
190   while (IS_PEND(context) && IF_ENABLED(context))
191     DOSVM_SendQueuedEvent(context,lpDosTask);
192 }
193
194 void DOSVM_QueueEvent( int irq, int priority, void (*relay)(LPDOSTASK,CONTEXT86*,void*), void *data)
195 {
196   LPDOSTASK lpDosTask = MZ_Current();
197   LPDOSEVENT event, cur, prev;
198
199   if (lpDosTask) {
200     event = malloc(sizeof(DOSEVENT));
201     if (!event) {
202       ERR_(int)("out of memory allocating event entry\n");
203       return;
204     }
205     event->irq = irq; event->priority = priority;
206     event->relay = relay; event->data = data;
207
208     /* insert event into linked list, in order *after*
209      * all earlier events of higher or equal priority */
210     cur = lpDosTask->pending; prev = NULL;
211     while (cur && cur->priority<=priority) {
212       prev = cur;
213       cur = cur->next;
214     }
215     event->next = cur;
216     if (prev) prev->next = event;
217     else lpDosTask->pending = event;
218     
219     /* get dosmod's attention to the new event, except for irq==0 where we already have it */
220     if (irq && !lpDosTask->sig_sent) {
221       TRACE_(int)("new event queued, signalling dosmod\n");
222       kill(lpDosTask->task,SIGUSR2);
223       lpDosTask->sig_sent++;
224     } else {
225       TRACE_(int)("new event queued\n");
226     }
227   }
228 }
229
230 #define CV CP(eax,EAX); CP(ecx,ECX); CP(edx,EDX); CP(ebx,EBX); \
231            CP(esi,ESI); CP(edi,EDI); CP(esp,ESP); CP(ebp,EBP); \
232            CP(cs,CS); CP(ds,DS); CP(es,ES); \
233            CP(ss,SS); CP(fs,FS); CP(gs,GS); \
234            CP(eip,EIP); CP(eflags,EFL)
235
236 static int DOSVM_Process( LPDOSTASK lpDosTask, int fn, int sig,
237                           struct vm86plus_struct*VM86 )
238 {
239  CONTEXT86 context;
240  int ret=0;
241
242 #define CP(x,y) y##_reg(&context) = VM86->regs.x
243   CV;
244 #undef CP
245  if (VM86_TYPE(fn)==VM86_UNKNOWN) {
246   ret=INSTR_EmulateInstruction(&context);
247 #define CP(x,y) VM86->regs.x = y##_reg(&context)
248   CV;
249 #undef CP
250   if (ret) return 0;
251   ret=0;
252  }
253  (void*)V86BASE(&context)=lpDosTask->img;
254 #ifdef TRY_PICRETURN
255  if (VM86->vm86plus.force_return_for_pic) {
256    SET_PEND(&context);
257  }
258 #else
259  /* linux doesn't preserve pending flag on return */
260  if (SHOULD_PEND(lpDosTask->pending)) {
261    SET_PEND(&context);
262  }
263 #endif
264
265  switch (VM86_TYPE(fn)) {
266   case VM86_SIGNAL:
267    TRACE_(int)("DOS module caught signal %d\n",sig);
268    if ((sig==SIGALRM) || (sig==SIGUSR2)) {
269      if (sig==SIGALRM) {
270        DOSVM_QueueEvent(0,DOS_PRIORITY_REALTIME,NULL,NULL);
271      }
272      if (lpDosTask->pending) {
273        TRACE_(int)("setting Pending flag, interrupts are currently %s\n",
274                  IF_ENABLED(&context) ? "enabled" : "disabled");
275        SET_PEND(&context);
276        DOSVM_SendQueuedEvents(&context,lpDosTask);
277      } else {
278        TRACE_(int)("no events are pending, clearing Pending flag\n");
279        CLR_PEND(&context);
280      }
281      if (sig==SIGUSR2) lpDosTask->sig_sent--;
282    }
283    else if ((sig==SIGHUP) || (sig==SIGILL) || (sig==SIGSEGV)) {
284        do_exception( sig, &context );
285    } else {
286     DOSVM_Dump(lpDosTask,fn,sig,VM86);
287     ret=-1;
288    }
289    break;
290   case VM86_UNKNOWN: /* unhandled GPF */
291    DOSVM_Dump(lpDosTask,fn,sig,VM86);
292    do_exception( SIGSEGV, &context );
293    break;
294   case VM86_INTx:
295    if (TRACE_ON(relay))
296     DPRINTF("Call DOS int 0x%02x (EAX=%08lx) ret=%04lx:%04lx\n",VM86_ARG(fn),context.Eax,context.SegCs,context.Eip);
297    ret=DOSVM_Int(VM86_ARG(fn),&context,lpDosTask);
298    if (TRACE_ON(relay))
299     DPRINTF("Ret  DOS int 0x%02x (EAX=%08lx) ret=%04lx:%04lx\n",VM86_ARG(fn),context.Eax,context.SegCs,context.Eip);
300    break;
301   case VM86_STI:
302   case VM86_PICRETURN:
303     TRACE_(int)("DOS task enabled interrupts with events pending, sending events\n");
304     DOSVM_SendQueuedEvents(&context,lpDosTask);
305     break;
306   case VM86_TRAP:
307    do_exception( SIGTRAP, &context );
308    break;
309   default:
310    DOSVM_Dump(lpDosTask,fn,sig,VM86);
311    ret=-1;
312  }
313
314 #define CP(x,y) VM86->regs.x = y##_reg(&context)
315  CV;
316 #undef CP
317 #ifdef TRY_PICRETURN
318  VM86->vm86plus.force_return_for_pic = IS_PEND(&context) ? 1 : 0;
319  CLR_PEND(&context);
320 #endif
321  return ret;
322 }
323
324 void DOSVM_ProcessMessage(LPDOSTASK lpDosTask,MSG *msg)
325 {
326   BYTE scan = 0;
327
328   fprintf(stderr,"got message %04x, wparam=%08x, lparam=%08lx\n",msg->message,msg->wParam,msg->lParam);
329   if ((msg->message>=WM_MOUSEFIRST)&&
330       (msg->message<=WM_MOUSELAST)) {
331     INT_Int33Message(msg->message,msg->wParam,msg->lParam);
332   } else {
333     switch (msg->message) {
334     case WM_KEYUP:
335       scan = 0x80;
336     case WM_KEYDOWN:
337       scan |= (msg->lParam >> 16) & 0x7f;
338
339       /* check whether extended bit is set,
340        * and if so, queue the extension prefix */
341       if (msg->lParam & 0x1000000) {
342         /* FIXME: some keys (function keys) have
343          * extended bit set even when they shouldn't,
344          * should check for them */
345         INT_Int09SendScan(0xE0);
346       }
347       INT_Int09SendScan(scan);
348       break;
349     }
350   }
351 }
352
353 void DOSVM_Wait( int read_pipe, HANDLE hObject )
354 {
355   LPDOSTASK lpDosTask = MZ_Current();
356   MSG msg;
357   DWORD waitret;
358   BOOL got_msg = FALSE;
359
360   do {
361     /* check for messages (waste time before the response check below) */
362     while (Callout.PeekMessageA(&msg,0,0,0,PM_REMOVE|PM_NOYIELD)) {
363       /* got a message */
364       DOSVM_ProcessMessage(lpDosTask,&msg);
365       /* we don't need a TranslateMessage here */
366       Callout.DispatchMessageA(&msg);
367       got_msg = TRUE;
368     }
369     if (read_pipe == -1) {
370       if (got_msg) break;
371     } else {
372       fd_set readfds;
373       struct timeval timeout={0,0};
374       /* quick check for response from dosmod
375        * (faster than doing the full blocking wait, if data already available) */
376       FD_ZERO(&readfds); FD_SET(read_pipe,&readfds);
377       if (select(read_pipe+1,&readfds,NULL,NULL,&timeout)>0)
378         break;
379     }
380     /* check for data from win32 console device */
381
382     /* nothing yet, block while waiting for something to do */
383     waitret=MsgWaitForMultipleObjects(1,&hObject,FALSE,INFINITE,QS_ALLINPUT);
384     if (waitret==(DWORD)-1) {
385       ERR_(module)("dosvm wait error=%ld\n",GetLastError());
386     }
387     if (read_pipe != -1) {
388       if (waitret==WAIT_OBJECT_0) break;
389     }
390   } while (TRUE);
391 }
392
393 int DOSVM_Enter( CONTEXT86 *context )
394 {
395  LPDOSTASK lpDosTask = MZ_Current();
396  struct vm86plus_struct VM86;
397  int stat,len,sig;
398
399  if (!lpDosTask) {
400   /* MZ_CreateProcess or MZ_AllocDPMITask should have been called first */
401   ERR_(module)("dosmod has not been initialized!");
402   return -1;
403  }
404
405  if (context) {
406 #define CP(x,y) VM86.regs.x = y##_reg(context)
407   CV;
408 #undef CP
409   if (VM86.regs.eflags & IF_MASK)
410     VM86.regs.eflags |= VIF_MASK;
411  } else {
412 /* initial setup */
413   /* allocate standard DOS handles */
414   FILE_InitProcessDosHandles(); 
415   /* registers */
416   memset(&VM86,0,sizeof(VM86));
417   VM86.regs.cs=lpDosTask->init_cs;
418   VM86.regs.eip=lpDosTask->init_ip;
419   VM86.regs.ss=lpDosTask->init_ss;
420   VM86.regs.esp=lpDosTask->init_sp;
421   VM86.regs.ds=lpDosTask->psp_seg;
422   VM86.regs.es=lpDosTask->psp_seg;
423   VM86.regs.eflags=VIF_MASK;
424   /* hmm, what else do we need? */
425  }
426
427  /* main exchange loop */
428  do {
429   stat = VM86_ENTER;
430   errno = 0;
431   /* transmit VM86 structure to dosmod task */
432   if (write(lpDosTask->write_pipe,&stat,sizeof(stat))!=sizeof(stat)) {
433    ERR_(module)("dosmod sync lost, errno=%d, fd=%d, pid=%d\n",errno,lpDosTask->write_pipe,getpid());
434    return -1;
435   }
436   if (write(lpDosTask->write_pipe,&VM86,sizeof(VM86))!=sizeof(VM86)) {
437    ERR_(module)("dosmod sync lost, errno=%d\n",errno);
438    return -1;
439   }
440   /* wait for response, doing other things in the meantime */
441   DOSVM_Wait(lpDosTask->read_pipe, lpDosTask->hReadPipe);
442   /* read response */
443   while (1) {
444     if ((len=read(lpDosTask->read_pipe,&stat,sizeof(stat)))==sizeof(stat)) break;
445     if (((errno==EINTR)||(errno==EAGAIN))&&(len<=0)) {
446      WARN_(module)("rereading dosmod return code due to errno=%d, result=%d\n",errno,len);
447      continue;
448     }
449     ERR_(module)("dosmod sync lost reading return code, errno=%d, result=%d\n",errno,len);
450     return -1;
451   }
452   TRACE_(module)("dosmod return code=%d\n",stat);
453   while (1) {
454     if ((len=read(lpDosTask->read_pipe,&VM86,sizeof(VM86)))==sizeof(VM86)) break;
455     if (((errno==EINTR)||(errno==EAGAIN))&&(len<=0)) {
456      WARN_(module)("rereading dosmod VM86 structure due to errno=%d, result=%d\n",errno,len);
457      continue;
458     }
459     ERR_(module)("dosmod sync lost reading VM86 structure, errno=%d, result=%d\n",errno,len);
460     return -1;
461   }
462   if ((stat&0xff)==DOSMOD_SIGNAL) {
463     while (1) {
464       if ((len=read(lpDosTask->read_pipe,&sig,sizeof(sig)))==sizeof(sig)) break;
465       if (((errno==EINTR)||(errno==EAGAIN))&&(len<=0)) {
466         WARN_(module)("rereading dosmod signal due to errno=%d, result=%d\n",errno,len);
467         continue;
468       }
469       ERR_(module)("dosmod sync lost reading signal, errno=%d, result=%d\n",errno,len);
470       return -1;
471     } while (0);
472   } else sig=0;
473   /* got response */
474  } while (DOSVM_Process(lpDosTask,stat,sig,&VM86)>=0);
475
476  if (context) {
477 #define CP(x,y) y##_reg(context) = VM86.regs.x
478   CV;
479 #undef CP
480  }
481  return 0;
482 }
483
484 void DOSVM_PIC_ioport_out( WORD port, BYTE val)
485 {
486   LPDOSTASK lpDosTask = MZ_Current();
487   LPDOSEVENT event;
488
489   if (lpDosTask) {
490     if ((port==0x20) && (val==0x20)) {
491       if (lpDosTask->current) {
492         /* EOI (End Of Interrupt) */
493         TRACE_(int)("received EOI for current IRQ, clearing\n");
494         event = lpDosTask->current;
495         lpDosTask->current = event->next;
496         if (event->relay)
497         (*event->relay)(lpDosTask,NULL,event->data);
498         free(event);
499
500         if (lpDosTask->pending &&
501             !lpDosTask->sig_sent) {
502           /* another event is pending, which we should probably
503            * be able to process now, so tell dosmod about it */
504           TRACE_(int)("another event pending, signalling dosmod\n");
505           kill(lpDosTask->task,SIGUSR2);
506           lpDosTask->sig_sent++;
507         }
508       } else {
509         WARN_(int)("EOI without active IRQ\n");
510       }
511     } else {
512       FIXME_(int)("unrecognized PIC command %02x\n",val);
513     }
514   }
515 }
516
517 void DOSVM_SetTimer( unsigned ticks )
518 {
519   LPDOSTASK lpDosTask = MZ_Current();
520   int stat=DOSMOD_SET_TIMER;
521   struct timeval tim;
522
523   if (lpDosTask) {
524     /* the PC clocks ticks at 1193180 Hz */
525     tim.tv_sec=0;
526     tim.tv_usec=((unsigned long long)ticks*1000000)/1193180;
527     /* sanity check */
528     if (!tim.tv_usec) tim.tv_usec=1;
529
530     if (write(lpDosTask->write_pipe,&stat,sizeof(stat))!=sizeof(stat)) {
531       ERR_(module)("dosmod sync lost, errno=%d\n",errno);
532       return;
533     }
534     if (write(lpDosTask->write_pipe,&tim,sizeof(tim))!=sizeof(tim)) {
535       ERR_(module)("dosmod sync lost, errno=%d\n",errno);
536       return;
537     }
538     /* there's no return */
539   }
540 }
541
542 unsigned DOSVM_GetTimer( void )
543 {
544   LPDOSTASK lpDosTask = MZ_Current();
545   int stat=DOSMOD_GET_TIMER;
546   struct timeval tim;
547
548   if (lpDosTask) {
549     if (write(lpDosTask->write_pipe,&stat,sizeof(stat))!=sizeof(stat)) {
550       ERR_(module)("dosmod sync lost, errno=%d\n",errno);
551       return 0;
552     }
553     /* read response */
554     while (1) {
555       if (read(lpDosTask->read_pipe,&tim,sizeof(tim))==sizeof(tim)) break;
556       if ((errno==EINTR)||(errno==EAGAIN)) continue;
557       ERR_(module)("dosmod sync lost, errno=%d\n",errno);
558       return 0;
559     }
560     return ((unsigned long long)tim.tv_usec*1193180)/1000000;
561   }
562   return 0;
563 }
564
565 void DOSVM_SetSystemData( int id, void *data )
566 {
567   LPDOSTASK lpDosTask = MZ_Current();
568   DOSSYSTEM *sys, *prev;
569
570   if (lpDosTask) {
571     sys = lpDosTask->sys;
572     prev = NULL;
573     while (sys && (sys->id != id)) {
574       prev = sys;
575       sys = sys->next;
576     }
577     if (sys) {
578       free(sys->data);
579       sys->data = data;
580     } else {
581       sys = malloc(sizeof(DOSSYSTEM));
582       sys->id = id;
583       sys->data = data;
584       sys->next = NULL;
585       if (prev) prev->next = sys;
586       else lpDosTask->sys = sys;
587     }
588   } else free(data);
589 }
590
591 void* DOSVM_GetSystemData( int id )
592 {
593   LPDOSTASK lpDosTask = MZ_Current();
594   DOSSYSTEM *sys;
595
596   if (lpDosTask) {
597     sys = lpDosTask->sys;
598     while (sys && (sys->id != id))
599       sys = sys->next;
600     if (sys)
601       return sys->data;
602   }
603   return NULL;
604 }
605
606 #else /* !MZ_SUPPORTED */
607
608 int DOSVM_Enter( CONTEXT86 *context )
609 {
610  ERR_(module)("DOS realmode not supported on this architecture!\n");
611  return -1;
612 }
613
614 void DOSVM_Wait( int read_pipe, HANDLE hObject) {}
615 void DOSVM_PIC_ioport_out( WORD port, BYTE val) {}
616 void DOSVM_SetTimer( unsigned ticks ) {}
617 unsigned DOSVM_GetTimer( void ) { return 0; }
618 void DOSVM_SetSystemData( int id, void *data ) { free(data); }
619 void* DOSVM_GetSystemData( int id ) { return NULL; }
620 void DOSVM_QueueEvent( int irq, int priority, void (*relay)(LPDOSTASK,CONTEXT86*,void*), void *data) {}
621
622 #endif