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