Various DPMI fixes and preliminary DPMI raw mode switch handler.
[wine.git] / loader / dos / dosvm.c
blob37238601fc3e7953027090ce0d8fd526505851dd
1 /*
2 * DOS Virtual Machine
4 * Copyright 1998 Ove Kåven
6 * This code hasn't been completely cleaned up yet.
7 */
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>
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"
35 DECLARE_DEBUG_CHANNEL(int)
36 DECLARE_DEBUG_CHANNEL(module)
37 DECLARE_DEBUG_CHANNEL(relay)
39 #ifdef MZ_SUPPORTED
41 #include <sys/mman.h>
42 #include <sys/vm86.h>
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)
50 #undef TRY_PICRETURN
52 static void DOSVM_Dump( LPDOSTASK lpDosTask, int fn, int sig,
53 struct vm86plus_struct*VM86 )
55 unsigned iofs;
56 BYTE*inst;
57 int x;
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;
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);
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");
89 static int DOSVM_Int( int vect, PCONTEXT context, LPDOSTASK lpDosTask )
91 extern UINT16 DPMI_wrap_seg;
93 if (vect==0x31) {
94 if (CS_reg(context)==DPMI_wrap_seg) {
95 /* exit from real-mode wrapper */
96 return -1;
98 /* we could probably move some other dodgy stuff here too from dpmi.c */
100 INT_RealModeInterrupt(vect,context);
101 return 0;
104 static void DOSVM_SimulateInt( int vect, PCONTEXT context, LPDOSTASK lpDosTask )
106 FARPROC16 handler=INT_GetRMHandler(vect);
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);
115 if (IF_ENABLED(context)) flag|=IF_MASK;
116 else flag&=~IF_MASK;
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);
128 #define SHOULD_PEND(x) \
129 (x && ((!lpDosTask->current) || (x->priority < lpDosTask->current->priority)))
131 static void DOSVM_SendQueuedEvent(PCONTEXT context, LPDOSTASK lpDosTask)
133 LPDOSEVENT event = lpDosTask->pending;
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);
154 if (!SHOULD_PEND(lpDosTask->pending)) {
155 TRACE_(int)("clearing Pending flag\n");
156 CLR_PEND(context);
160 static void DOSVM_SendQueuedEvents(PCONTEXT context, LPDOSTASK lpDosTask)
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);
168 void DOSVM_QueueEvent( int irq, int priority, void (*relay)(LPDOSTASK,PCONTEXT,void*), void *data)
170 LPDOSTASK lpDosTask = MZ_Current();
171 LPDOSEVENT event, cur, prev;
173 if (lpDosTask) {
174 event = malloc(sizeof(DOSEVENT));
175 if (!event) {
176 ERR_(int)("out of memory allocating event entry\n");
177 return;
179 event->irq = irq; event->priority = priority;
180 event->relay = relay; event->data = data;
182 /* insert event into linked list, in order *after*
183 * all earlier events of higher or equal priority */
184 cur = lpDosTask->pending; prev = NULL;
185 while (cur && cur->priority<=priority) {
186 prev = cur;
187 cur = cur->next;
189 event->next = cur;
190 if (prev) prev->next = event;
191 else lpDosTask->pending = event;
193 /* get dosmod's attention to the new event, except for irq==0 where we already have it */
194 if (irq && !lpDosTask->sig_sent) {
195 TRACE_(int)("new event queued, signalling dosmod\n");
196 kill(lpDosTask->task,SIGUSR2);
197 lpDosTask->sig_sent++;
198 } else {
199 TRACE_(int)("new event queued\n");
204 #define CV CP(eax,EAX); CP(ecx,ECX); CP(edx,EDX); CP(ebx,EBX); \
205 CP(esi,ESI); CP(edi,EDI); CP(esp,ESP); CP(ebp,EBP); \
206 CP(cs,CS); CP(ds,DS); CP(es,ES); \
207 CP(ss,SS); CP(fs,FS); CP(gs,GS); \
208 CP(eip,EIP); CP(eflags,EFL)
210 static int DOSVM_Process( LPDOSTASK lpDosTask, int fn, int sig,
211 struct vm86plus_struct*VM86 )
213 SIGCONTEXT sigcontext;
214 CONTEXT context;
215 int ret=0;
217 if (VM86_TYPE(fn)==VM86_UNKNOWN) {
218 /* INSTR_EmulateInstruction needs a SIGCONTEXT, not a CONTEXT... */
219 #define CP(x,y) y##_sig(&sigcontext) = VM86->regs.x
221 #undef CP
222 if (fnINSTR_EmulateInstruction) ret=fnINSTR_EmulateInstruction(&sigcontext);
223 else ERR_(module)("fnINSTR_EmulateInstruction is not initialized!\n");
224 #define CP(x,y) VM86->regs.x = y##_sig(&sigcontext)
226 #undef CP
227 if (ret) return 0;
228 ret=0;
230 #define CP(x,y) y##_reg(&context) = VM86->regs.x
232 #undef CP
233 (void*)V86BASE(&context)=lpDosTask->img;
234 #ifdef TRY_PICRETURN
235 if (VM86->vm86plus.force_return_for_pic) {
236 SET_PEND(&context);
238 #else
239 /* linux doesn't preserve pending flag on return */
240 if (SHOULD_PEND(lpDosTask->pending)) {
241 SET_PEND(&context);
243 #endif
245 switch (VM86_TYPE(fn)) {
246 case VM86_SIGNAL:
247 TRACE_(int)("DOS module caught signal %d\n",sig);
248 if ((sig==SIGALRM) || (sig==SIGUSR2)) {
249 if (sig==SIGALRM) {
250 DOSVM_QueueEvent(0,DOS_PRIORITY_REALTIME,NULL,NULL);
252 if (lpDosTask->pending) {
253 TRACE_(int)("setting Pending flag, interrupts are currently %s\n",
254 IF_ENABLED(&context) ? "enabled" : "disabled");
255 SET_PEND(&context);
256 DOSVM_SendQueuedEvents(&context,lpDosTask);
257 } else {
258 TRACE_(int)("no events are pending, clearing Pending flag\n");
259 CLR_PEND(&context);
261 if (sig==SIGUSR2) lpDosTask->sig_sent--;
262 } else
263 if (sig==SIGHUP) {
264 if (ctx_debug_call) ctx_debug_call(SIGTRAP,&context);
265 } else
266 if ((sig==SIGILL)||(sig==SIGSEGV)) {
267 if (ctx_debug_call) ctx_debug_call(SIGILL,&context);
268 } else {
269 DOSVM_Dump(lpDosTask,fn,sig,VM86);
270 ret=-1;
272 break;
273 case VM86_UNKNOWN: /* unhandled GPF */
274 DOSVM_Dump(lpDosTask,fn,sig,VM86);
275 if (ctx_debug_call) ctx_debug_call(SIGSEGV,&context); else ret=-1;
276 break;
277 case VM86_INTx:
278 if (TRACE_ON(relay))
279 DPRINTF("Call DOS int 0x%02x (EAX=%08lx) ret=%04lx:%04lx\n",VM86_ARG(fn),context.Eax,context.SegCs,context.Eip);
280 ret=DOSVM_Int(VM86_ARG(fn),&context,lpDosTask);
281 if (TRACE_ON(relay))
282 DPRINTF("Ret DOS int 0x%02x (EAX=%08lx) ret=%04lx:%04lx\n",VM86_ARG(fn),context.Eax,context.SegCs,context.Eip);
283 break;
284 case VM86_STI:
285 case VM86_PICRETURN:
286 TRACE_(int)("DOS task enabled interrupts with events pending, sending events\n");
287 DOSVM_SendQueuedEvents(&context,lpDosTask);
288 break;
289 case VM86_TRAP:
290 if (ctx_debug_call) ctx_debug_call(SIGTRAP,&context);
291 break;
292 default:
293 DOSVM_Dump(lpDosTask,fn,sig,VM86);
294 ret=-1;
297 #define CP(x,y) VM86->regs.x = y##_reg(&context)
299 #undef CP
300 #ifdef TRY_PICRETURN
301 VM86->vm86plus.force_return_for_pic = IS_PEND(&context) ? 1 : 0;
302 CLR_PEND(&context);
303 #endif
304 return ret;
307 void DOSVM_ProcessMessage(LPDOSTASK lpDosTask,MSG *msg)
309 BYTE scan = 0;
311 fprintf(stderr,"got message %04x, wparam=%08x, lparam=%08lx\n",msg->message,msg->wParam,msg->lParam);
312 if ((msg->message>=WM_MOUSEFIRST)&&
313 (msg->message<=WM_MOUSELAST)) {
314 INT_Int33Message(msg->message,msg->wParam,msg->lParam);
315 } else {
316 switch (msg->message) {
317 case WM_KEYUP:
318 scan = 0x80;
319 case WM_KEYDOWN:
320 scan |= (msg->lParam >> 16) & 0x7f;
322 /* check whether extended bit is set,
323 * and if so, queue the extension prefix */
324 if (msg->lParam & 0x1000000) {
325 /* FIXME: some keys (function keys) have
326 * extended bit set even when they shouldn't,
327 * should check for them */
328 INT_Int09SendScan(0xE0);
330 INT_Int09SendScan(scan);
331 break;
336 void DOSVM_Wait( int read_pipe, HANDLE hObject )
338 LPDOSTASK lpDosTask = MZ_Current();
339 MSG msg;
340 DWORD waitret;
341 BOOL got_msg = FALSE;
343 do {
344 /* check for messages (waste time before the response check below) */
345 while (PeekMessageA(&msg,0,0,0,PM_REMOVE|PM_NOYIELD)) {
346 /* got a message */
347 DOSVM_ProcessMessage(lpDosTask,&msg);
348 /* we don't need a TranslateMessage here */
349 DispatchMessageA(&msg);
350 got_msg = TRUE;
352 if (read_pipe == -1) {
353 if (got_msg) break;
354 } else {
355 fd_set readfds;
356 struct timeval timeout={0,0};
357 /* quick check for response from dosmod
358 * (faster than doing the full blocking wait, if data already available) */
359 FD_ZERO(&readfds); FD_SET(read_pipe,&readfds);
360 if (select(read_pipe+1,&readfds,NULL,NULL,&timeout)>0)
361 break;
363 /* check for data from win32 console device */
365 /* nothing yet, block while waiting for something to do */
366 waitret=MsgWaitForMultipleObjects(1,&hObject,FALSE,INFINITE,QS_ALLINPUT);
367 if (waitret==(DWORD)-1) {
368 ERR_(module)("dosvm wait error=%ld\n",GetLastError());
370 if (read_pipe != -1) {
371 if (waitret==WAIT_OBJECT_0) break;
373 } while (TRUE);
376 int DOSVM_Enter( PCONTEXT context )
378 LPDOSTASK lpDosTask = MZ_Current();
379 struct vm86plus_struct VM86;
380 int stat,len,sig;
382 if (!lpDosTask) {
383 /* MZ_CreateProcess or MZ_AllocDPMITask should have been called first */
384 ERR_(module)("dosmod has not been initialized!");
385 return -1;
388 if (context) {
389 #define CP(x,y) VM86.regs.x = y##_reg(context)
391 #undef CP
392 if (VM86.regs.eflags & IF_MASK)
393 VM86.regs.eflags |= VIF_MASK;
394 } else {
395 /* initial setup */
396 /* allocate standard DOS handles */
397 FILE_InitProcessDosHandles();
398 /* registers */
399 memset(&VM86,0,sizeof(VM86));
400 VM86.regs.cs=lpDosTask->init_cs;
401 VM86.regs.eip=lpDosTask->init_ip;
402 VM86.regs.ss=lpDosTask->init_ss;
403 VM86.regs.esp=lpDosTask->init_sp;
404 VM86.regs.ds=lpDosTask->psp_seg;
405 VM86.regs.es=lpDosTask->psp_seg;
406 VM86.regs.eflags=VIF_MASK;
407 /* hmm, what else do we need? */
410 /* main exchange loop */
411 do {
412 stat = VM86_ENTER;
413 errno = 0;
414 /* transmit VM86 structure to dosmod task */
415 if (write(lpDosTask->write_pipe,&stat,sizeof(stat))!=sizeof(stat)) {
416 ERR_(module)("dosmod sync lost, errno=%d, fd=%d, pid=%d\n",errno,lpDosTask->write_pipe,getpid());
417 return -1;
419 if (write(lpDosTask->write_pipe,&VM86,sizeof(VM86))!=sizeof(VM86)) {
420 ERR_(module)("dosmod sync lost, errno=%d\n",errno);
421 return -1;
423 /* wait for response, doing other things in the meantime */
424 DOSVM_Wait(lpDosTask->read_pipe, lpDosTask->hReadPipe);
425 /* read response */
426 while (1) {
427 if ((len=read(lpDosTask->read_pipe,&stat,sizeof(stat)))==sizeof(stat)) break;
428 if (((errno==EINTR)||(errno==EAGAIN))&&(len<=0)) {
429 WARN_(module)("rereading dosmod return code due to errno=%d, result=%d\n",errno,len);
430 continue;
432 ERR_(module)("dosmod sync lost reading return code, errno=%d, result=%d\n",errno,len);
433 return -1;
435 TRACE_(module)("dosmod return code=%d\n",stat);
436 while (1) {
437 if ((len=read(lpDosTask->read_pipe,&VM86,sizeof(VM86)))==sizeof(VM86)) break;
438 if (((errno==EINTR)||(errno==EAGAIN))&&(len<=0)) {
439 WARN_(module)("rereading dosmod VM86 structure due to errno=%d, result=%d\n",errno,len);
440 continue;
442 ERR_(module)("dosmod sync lost reading VM86 structure, errno=%d, result=%d\n",errno,len);
443 return -1;
445 if ((stat&0xff)==DOSMOD_SIGNAL) {
446 while (1) {
447 if ((len=read(lpDosTask->read_pipe,&sig,sizeof(sig)))==sizeof(sig)) break;
448 if (((errno==EINTR)||(errno==EAGAIN))&&(len<=0)) {
449 WARN_(module)("rereading dosmod signal due to errno=%d, result=%d\n",errno,len);
450 continue;
452 ERR_(module)("dosmod sync lost reading signal, errno=%d, result=%d\n",errno,len);
453 return -1;
454 } while (0);
455 } else sig=0;
456 /* got response */
457 } while (DOSVM_Process(lpDosTask,stat,sig,&VM86)>=0);
459 if (context) {
460 #define CP(x,y) y##_reg(context) = VM86.regs.x
462 #undef CP
464 return 0;
467 void DOSVM_PIC_ioport_out( WORD port, BYTE val)
469 LPDOSTASK lpDosTask = MZ_Current();
470 LPDOSEVENT event;
472 if (lpDosTask) {
473 if ((port==0x20) && (val==0x20)) {
474 if (lpDosTask->current) {
475 /* EOI (End Of Interrupt) */
476 TRACE_(int)("received EOI for current IRQ, clearing\n");
477 event = lpDosTask->current;
478 lpDosTask->current = event->next;
479 if (event->relay)
480 (*event->relay)(lpDosTask,NULL,event->data);
481 free(event);
483 if (lpDosTask->pending &&
484 !lpDosTask->sig_sent) {
485 /* another event is pending, which we should probably
486 * be able to process now, so tell dosmod about it */
487 TRACE_(int)("another event pending, signalling dosmod\n");
488 kill(lpDosTask->task,SIGUSR2);
489 lpDosTask->sig_sent++;
491 } else {
492 WARN_(int)("EOI without active IRQ\n");
494 } else {
495 FIXME_(int)("unrecognized PIC command %02x\n",val);
500 void DOSVM_SetTimer( unsigned ticks )
502 LPDOSTASK lpDosTask = MZ_Current();
503 int stat=DOSMOD_SET_TIMER;
504 struct timeval tim;
506 if (lpDosTask) {
507 /* the PC clocks ticks at 1193180 Hz */
508 tim.tv_sec=0;
509 tim.tv_usec=((unsigned long long)ticks*1000000)/1193180;
510 /* sanity check */
511 if (!tim.tv_usec) tim.tv_usec=1;
513 if (write(lpDosTask->write_pipe,&stat,sizeof(stat))!=sizeof(stat)) {
514 ERR_(module)("dosmod sync lost, errno=%d\n",errno);
515 return;
517 if (write(lpDosTask->write_pipe,&tim,sizeof(tim))!=sizeof(tim)) {
518 ERR_(module)("dosmod sync lost, errno=%d\n",errno);
519 return;
521 /* there's no return */
525 unsigned DOSVM_GetTimer( void )
527 LPDOSTASK lpDosTask = MZ_Current();
528 int stat=DOSMOD_GET_TIMER;
529 struct timeval tim;
531 if (lpDosTask) {
532 if (write(lpDosTask->write_pipe,&stat,sizeof(stat))!=sizeof(stat)) {
533 ERR_(module)("dosmod sync lost, errno=%d\n",errno);
534 return 0;
536 /* read response */
537 while (1) {
538 if (read(lpDosTask->read_pipe,&tim,sizeof(tim))==sizeof(tim)) break;
539 if ((errno==EINTR)||(errno==EAGAIN)) continue;
540 ERR_(module)("dosmod sync lost, errno=%d\n",errno);
541 return 0;
543 return ((unsigned long long)tim.tv_usec*1193180)/1000000;
545 return 0;
548 void DOSVM_SetSystemData( int id, void *data )
550 LPDOSTASK lpDosTask = MZ_Current();
551 DOSSYSTEM *sys, *prev;
553 if (lpDosTask) {
554 sys = lpDosTask->sys;
555 prev = NULL;
556 while (sys && (sys->id != id)) {
557 prev = sys;
558 sys = sys->next;
560 if (sys) {
561 free(sys->data);
562 sys->data = data;
563 } else {
564 sys = malloc(sizeof(DOSSYSTEM));
565 sys->id = id;
566 sys->data = data;
567 sys->next = NULL;
568 if (prev) prev->next = sys;
569 else lpDosTask->sys = sys;
571 } else free(data);
574 void* DOSVM_GetSystemData( int id )
576 LPDOSTASK lpDosTask = MZ_Current();
577 DOSSYSTEM *sys;
579 if (lpDosTask) {
580 sys = lpDosTask->sys;
581 while (sys && (sys->id != id))
582 sys = sys->next;
583 if (sys)
584 return sys->data;
586 return NULL;
589 #else /* !MZ_SUPPORTED */
591 int DOSVM_Enter( PCONTEXT context )
593 ERR_(module)("DOS realmode not supported on this architecture!\n");
594 return -1;
597 void DOSVM_Wait( int read_pipe, HANDLE hObject) {}
598 void DOSVM_PIC_ioport_out( WORD port, BYTE val) {}
599 void DOSVM_SetTimer( unsigned ticks ) {}
600 unsigned DOSVM_GetTimer( void ) { return 0; }
601 void DOSVM_SetSystemData( int id, void *data ) { free(data); }
602 void* DOSVM_GetSystemData( int id ) { return NULL; }
603 void DOSVM_QueueEvent( int irq, int priority, void (*relay)(LPDOSTASK,PCONTEXT,void*), void *data) {}
605 #endif