4 * Copyright 1995 Alexandre Julliard
10 #include "wine/winbase16.h"
16 #include "debugtools.h"
18 DEFAULT_DEBUG_CHANNEL(int31
);
20 /* Structure for real-mode callbacks */
42 typedef struct tagRMCB
{
44 DWORD proc_ofs
,proc_sel
;
45 DWORD regs_ofs
,regs_sel
;
49 static RMCB
*FirstRMCB
= NULL
;
50 static WORD dpmi_flag
;
52 /**********************************************************************
53 * INT_GetRealModeContext
55 static void INT_GetRealModeContext( REALMODECALL
*call
, CONTEXT86
*context
)
57 context
->Eax
= call
->eax
;
58 context
->Ebx
= call
->ebx
;
59 context
->Ecx
= call
->ecx
;
60 context
->Edx
= call
->edx
;
61 context
->Esi
= call
->esi
;
62 context
->Edi
= call
->edi
;
63 context
->Ebp
= call
->ebp
;
64 context
->EFlags
= call
->fl
| V86_FLAG
;
65 context
->Eip
= call
->ip
;
66 context
->Esp
= call
->sp
;
67 context
->SegCs
= call
->cs
;
68 context
->SegDs
= call
->ds
;
69 context
->SegEs
= call
->es
;
70 context
->SegFs
= call
->fs
;
71 context
->SegGs
= call
->gs
;
72 context
->SegSs
= call
->ss
;
76 /**********************************************************************
77 * INT_SetRealModeContext
79 static void INT_SetRealModeContext( REALMODECALL
*call
, CONTEXT86
*context
)
81 call
->eax
= context
->Eax
;
82 call
->ebx
= context
->Ebx
;
83 call
->ecx
= context
->Ecx
;
84 call
->edx
= context
->Edx
;
85 call
->esi
= context
->Esi
;
86 call
->edi
= context
->Edi
;
87 call
->ebp
= context
->Ebp
;
88 call
->fl
= LOWORD(context
->EFlags
);
89 call
->ip
= LOWORD(context
->Eip
);
90 call
->sp
= LOWORD(context
->Esp
);
91 call
->cs
= context
->SegCs
;
92 call
->ds
= context
->SegDs
;
93 call
->es
= context
->SegEs
;
94 call
->fs
= context
->SegFs
;
95 call
->gs
= context
->SegGs
;
96 call
->ss
= context
->SegSs
;
102 void DPMI_CallRMCB32(RMCB
*rmcb
, UINT16 ss
, DWORD esp
, UINT16
*es
, DWORD
*edi
)
103 #if 0 /* original code, which early gccs puke on */
106 __asm__
__volatile__(
114 ".byte 0x36, 0xff, 0x18\n" /* lcall *%ss:(%eax) */
120 : "=d" (*es
), "=D" (*edi
), "=S" (_clobber
), "=a" (_clobber
), "=c" (_clobber
)
121 : "0" (ss
), "2" (esp
),
122 "4" (rmcb
->regs_sel
), "1" (rmcb
->regs_ofs
),
123 "3" (&rmcb
->proc_ofs
) );
125 #else /* code generated by a gcc new enough */
127 __ASM_GLOBAL_FUNC(DPMI_CallRMCB32
,
132 "movl 0x8(%ebp),%eax\n\t"
133 "movl 0x10(%ebp),%esi\n\t"
134 "movl 0xc(%ebp),%edx\n\t"
135 "movl 0x10(%eax),%ecx\n\t"
136 "movl 0xc(%eax),%edi\n\t"
145 ".byte 0x36, 0xff, 0x18\n\t" /* lcall *%ss:(%eax) */
151 "movl 0x14(%ebp),%eax\n\t"
152 "movw %dx,(%eax)\n\t"
153 "movl 0x18(%ebp),%edx\n\t"
154 "movl %edi,(%edx)\n\t"
161 #endif /* __i386__ */
163 /**********************************************************************
166 * This routine does the hard work of calling a callback procedure.
168 static void DPMI_CallRMCBProc( CONTEXT86
*context
, RMCB
*rmcb
, WORD flag
)
170 if (IS_SELECTOR_SYSTEM( rmcb
->proc_sel
)) {
171 /* Wine-internal RMCB, call directly */
172 ((RMCBPROC
)rmcb
->proc_ofs
)(context
);
178 INT_SetRealModeContext(MapSL(MAKESEGPTR( rmcb
->regs_sel
, rmcb
->regs_ofs
)), context
);
179 ss
= SELECTOR_AllocBlock( (void *)(context
->SegSs
<<4), 0x10000, WINE_LDT_FLAGS_DATA
);
182 FIXME("untested!\n");
184 /* The called proc ends with an IRET, and takes these parameters:
185 * DS:ESI = pointer to real-mode SS:SP
186 * ES:EDI = pointer to real-mode call structure
188 * ES:EDI = pointer to real-mode call structure (may be a copy)
189 * It is the proc's responsibility to change the return CS:IP in the
190 * real-mode call structure. */
192 /* 32-bit DPMI client */
193 DPMI_CallRMCB32(rmcb
, ss
, esp
, &es
, &edi
);
195 /* 16-bit DPMI client */
196 CONTEXT86 ctx
= *context
;
197 ctx
.SegCs
= rmcb
->proc_sel
;
198 ctx
.Eip
= rmcb
->proc_ofs
;
201 ctx
.SegEs
= rmcb
->regs_sel
;
202 ctx
.Edi
= rmcb
->regs_ofs
;
203 /* FIXME: I'm pretty sure this isn't right - should push flags first */
204 wine_call_to_16_regs_short(&ctx
, 0);
209 INT_GetRealModeContext( MapSL( MAKESEGPTR( es
, edi
)), context
);
211 ERR("RMCBs only implemented for i386\n");
217 /**********************************************************************
220 * This routine does the hard work of calling a real mode procedure.
222 int DPMI_CallRMProc( CONTEXT86
*context
, LPWORD stack
, int args
, int iret
)
225 LPVOID addr
= NULL
; /* avoid gcc warning */
227 int alloc
= 0, already
= 0;
230 TRACE("EAX=%08lx EBX=%08lx ECX=%08lx EDX=%08lx\n",
231 context
->Eax
, context
->Ebx
, context
->Ecx
, context
->Edx
);
232 TRACE("ESI=%08lx EDI=%08lx ES=%04lx DS=%04lx CS:IP=%04lx:%04x, %d WORD arguments, %s\n",
233 context
->Esi
, context
->Edi
, context
->SegEs
, context
->SegDs
,
234 context
->SegCs
, LOWORD(context
->Eip
), args
, iret
?"IRET":"FAR" );
238 /* there might be some code that just jumps to RMCBs or the like,
239 in which case following the jumps here might get us to a shortcut */
240 code
= CTX_SEG_OFF_TO_LIN(context
, context
->SegCs
, context
->Eip
);
242 case 0xe9: /* JMP NEAR */
243 context
->Eip
+= 3 + *(WORD
*)(code
+1);
244 /* yeah, I know these gotos don't look good... */
245 goto callrmproc_again
;
246 case 0xea: /* JMP FAR */
247 context
->Eip
= *(WORD
*)(code
+1);
248 context
->SegCs
= *(WORD
*)(code
+3);
249 /* ...but since the label is there anyway... */
250 goto callrmproc_again
;
251 case 0xeb: /* JMP SHORT */
252 context
->Eip
+= 2 + *(signed char *)(code
+1);
253 /* ...because of other gotos below, so... */
254 goto callrmproc_again
;
257 /* shortcut for chaining to internal interrupt handlers */
258 if ((context
->SegCs
== 0xF000) && iret
)
260 DOSVM_RealModeInterrupt( LOWORD(context
->Eip
)/4, context
);
264 /* shortcut for RMCBs */
265 CurrRMCB
= FirstRMCB
;
267 while (CurrRMCB
&& (HIWORD(CurrRMCB
->address
) != context
->SegCs
))
268 CurrRMCB
= CurrRMCB
->next
;
270 if (!CurrRMCB
&& !MZ_Current())
272 FIXME("DPMI real-mode call using DOS VM task system, not fully tested!\n");
273 TRACE("creating VM86 task\n");
277 if (!context
->SegSs
) {
278 alloc
= 1; /* allocate default stack */
279 stack16
= addr
= DOSMEM_GetBlock( 64, (UINT16
*)&(context
->SegSs
) );
283 ERR("could not allocate default stack\n");
287 stack16
= CTX_SEG_OFF_TO_LIN(context
, context
->SegSs
, context
->Esp
);
289 context
->Esp
-= (args
+ (iret
?1:0)) * sizeof(WORD
);
291 if (args
) memcpy(stack16
, stack
, args
*sizeof(WORD
) );
292 /* push flags if iret */
295 *stack16
= LOWORD(context
->EFlags
);
297 /* push return address (return to interrupt wrapper) */
298 *(--stack16
) = DOSMEM_wrap_seg
;
301 context
->Esp
-= 2*sizeof(WORD
);
306 /* RMCB call, invoke protected-mode handler directly */
307 DPMI_CallRMCBProc(context
, CurrRMCB
, dpmi_flag
);
308 /* check if we returned to where we thought we would */
309 if ((context
->SegCs
!= DOSMEM_wrap_seg
) ||
310 (LOWORD(context
->Eip
) != 0)) {
311 /* we need to continue at different address in real-mode space,
312 so we need to set it all up for real mode again */
313 goto callrmproc_again
;
316 TRACE("entering real mode...\n");
317 DOSVM_Enter( context
);
318 TRACE("returned from real-mode call\n");
320 if (alloc
) DOSMEM_FreeBlock( addr
);
325 /**********************************************************************
326 * DOSVM_CallRMInt (WINEDOS.@)
328 void WINAPI
DOSVM_CallRMInt( CONTEXT86
*context
)
330 CONTEXT86 realmode_ctx
;
331 FARPROC16 rm_int
= DOSVM_GetRMHandler( BL_reg(context
) );
332 REALMODECALL
*call
= MapSL( MAKESEGPTR( context
->SegEs
, DI_reg(context
) ));
333 INT_GetRealModeContext( call
, &realmode_ctx
);
335 /* we need to check if a real-mode program has hooked the interrupt */
336 if (HIWORD(rm_int
)!=0xF000) {
337 /* yup, which means we need to switch to real mode... */
338 realmode_ctx
.SegCs
= HIWORD(rm_int
);
339 realmode_ctx
.Eip
= LOWORD(rm_int
);
340 if (DPMI_CallRMProc( &realmode_ctx
, NULL
, 0, TRUE
))
343 RESET_CFLAG(context
);
344 /* use the IP we have instead of BL_reg, in case some apps
345 decide to move interrupts around for whatever reason... */
346 DOSVM_RealModeInterrupt( LOWORD(rm_int
)/4, &realmode_ctx
);
348 INT_SetRealModeContext( call
, &realmode_ctx
);
352 /**********************************************************************
353 * DOSVM_CallRMProc (WINEDOS.@)
355 void WINAPI
DOSVM_CallRMProc( CONTEXT86
*context
, int iret
)
357 REALMODECALL
*p
= MapSL( MAKESEGPTR( context
->SegEs
, DI_reg(context
) ));
360 TRACE("RealModeCall: EAX=%08lx EBX=%08lx ECX=%08lx EDX=%08lx\n",
361 p
->eax
, p
->ebx
, p
->ecx
, p
->edx
);
362 TRACE(" ESI=%08lx EDI=%08lx ES=%04x DS=%04x CS:IP=%04x:%04x, %d WORD arguments, %s\n",
363 p
->esi
, p
->edi
, p
->es
, p
->ds
, p
->cs
, p
->ip
, CX_reg(context
), iret
?"IRET":"FAR" );
365 if (!(p
->cs
) && !(p
->ip
)) { /* remove this check
366 if Int21/6501 case map function
367 has been implemented */
371 INT_GetRealModeContext(p
, &context16
);
372 DPMI_CallRMProc( &context16
, ((LPWORD
)MapSL(MAKESEGPTR(context
->SegSs
, LOWORD(context
->Esp
))))+3,
373 CX_reg(context
), iret
);
374 INT_SetRealModeContext(p
, &context16
);
378 /* (see dosmem.c, function DOSMEM_InitDPMI) */
379 static void StartPM( CONTEXT86
*context
)
381 UINT16 cs
, ss
, ds
, es
;
383 DWORD psp_ofs
= (DWORD
)(DOSVM_psp
<<4);
384 PDB16
*psp
= (PDB16
*)psp_ofs
;
385 HANDLE16 env_seg
= psp
->environment
;
386 unsigned char selflags
= WINE_LDT_FLAGS_DATA
;
388 RESET_CFLAG(context
);
389 dpmi_flag
= AX_reg(context
);
390 /* our mode switch wrapper have placed the desired CS into DX */
391 cs
= SELECTOR_AllocBlock( (void *)(DX_reg(context
)<<4), 0x10000, WINE_LDT_FLAGS_CODE
);
392 /* due to a flaw in some CPUs (at least mine), it is best to mark stack segments as 32-bit if they
393 can be used in 32-bit code. Otherwise, these CPUs may not set the high word of esp during a
394 ring transition (from kernel code) to the 16-bit stack, and this causes trouble if executing
395 32-bit code using this stack. */
396 if (dpmi_flag
& 1) selflags
|= WINE_LDT_FLAGS_32BIT
;
397 ss
= SELECTOR_AllocBlock( (void *)(context
->SegSs
<<4), 0x10000, selflags
);
398 /* do the same for the data segments, just in case */
399 if (context
->SegDs
== context
->SegSs
) ds
= ss
;
400 else ds
= SELECTOR_AllocBlock( (void *)(context
->SegDs
<<4), 0x10000, selflags
);
401 es
= SELECTOR_AllocBlock( psp
, 0x100, selflags
);
402 /* convert environment pointer, as the spec says, but we're a bit lazy about the size here... */
403 psp
->environment
= SELECTOR_AllocBlock( (void *)(env_seg
<<4), 0x10000, WINE_LDT_FLAGS_DATA
);
406 pm_ctx
.SegCs
= DOSMEM_dpmi_sel
;
407 /* our mode switch wrapper expects the new CS in DX, and the new SS in AX */
415 TRACE("DOS program is now entering protected mode\n");
416 wine_call_to_16_regs_short(&pm_ctx
, 0);
418 /* in the current state of affairs, we won't ever actually return here... */
419 /* we should have int21/ah=4c do it someday, though... */
421 FreeSelector16(psp
->environment
);
422 psp
->environment
= env_seg
;
424 if (ds
!= ss
) FreeSelector16(ds
);
429 static RMCB
*DPMI_AllocRMCB( void )
431 RMCB
*NewRMCB
= HeapAlloc(GetProcessHeap(), 0, sizeof(RMCB
));
436 LPVOID RMCBmem
= DOSMEM_GetBlock(4, &uParagraph
);
439 *p
++ = 0xcd; /* RMCB: */
440 *p
++ = 0x31; /* int $0x31 */
441 /* it is the called procedure's task to change the return CS:EIP
442 the DPMI 0.9 spec states that if it doesn't, it will be called again */
444 *p
++ = 0xfc; /* jmp RMCB */
445 NewRMCB
->address
= MAKELONG(0, uParagraph
);
446 NewRMCB
->next
= FirstRMCB
;
453 FARPROC16 WINAPI
DPMI_AllocInternalRMCB( RMCBPROC proc
)
455 RMCB
*NewRMCB
= DPMI_AllocRMCB();
458 NewRMCB
->proc_ofs
= (DWORD
)proc
;
459 NewRMCB
->proc_sel
= 0;
460 NewRMCB
->regs_ofs
= 0;
461 NewRMCB
->regs_sel
= 0;
462 return (FARPROC16
)(NewRMCB
->address
);
468 static int DPMI_FreeRMCB( DWORD address
)
470 RMCB
*CurrRMCB
= FirstRMCB
;
471 RMCB
*PrevRMCB
= NULL
;
473 while (CurrRMCB
&& (CurrRMCB
->address
!= address
))
476 CurrRMCB
= CurrRMCB
->next
;
481 PrevRMCB
->next
= CurrRMCB
->next
;
483 FirstRMCB
= CurrRMCB
->next
;
484 DOSMEM_FreeBlock(DOSMEM_MapRealToLinear(CurrRMCB
->address
));
485 HeapFree(GetProcessHeap(), 0, CurrRMCB
);
492 void WINAPI
DPMI_FreeInternalRMCB( FARPROC16 proc
)
494 DPMI_FreeRMCB( (DWORD
)proc
);
498 /* DPMI Raw Mode Switch handler */
501 void WINAPI
DPMI_RawModeSwitch( SIGCONTEXT
*context
)
503 LPDOSTASK lpDosTask
= MZ_Current();
508 /* we could probably start a DPMI-only dosmod task here, but I doubt
509 anything other than real DOS apps want to call raw mode switch */
510 ERR("attempting raw mode switch without DOS task!\n");
513 /* initialize real-mode context as per spec */
514 memset(&rm_ctx
, 0, sizeof(rm_ctx
));
515 rm_ctx
.SegDs
= AX_sig(context
);
516 rm_ctx
.SegEs
= CX_sig(context
);
517 rm_ctx
.SegSs
= DX_sig(context
);
518 rm_ctx
.Esp
= EBX_sig(context
);
519 rm_ctx
.SegCs
= SI_sig(context
);
520 rm_ctx
.Eip
= EDI_sig(context
);
521 rm_ctx
.Ebp
= EBP_sig(context
);
524 rm_ctx
.EFlags
= EFL_sig(context
); /* at least we need the IF flag */
526 /* enter real mode again */
527 TRACE("re-entering real mode at %04lx:%04lx\n",rm_ctx
.SegCs
,rm_ctx
.Eip
);
528 ret
= DOSVM_Enter( &rm_ctx
);
529 /* when the real-mode stuff call its mode switch address,
530 DOSVM_Enter will return and we will continue here */
533 /* if the sync was lost, there's no way to recover */
537 /* alter protected-mode context as per spec */
538 DS_sig(context
) = LOWORD(rm_ctx
.Eax
);
539 ES_sig(context
) = LOWORD(rm_ctx
.Ecx
);
540 SS_sig(context
) = LOWORD(rm_ctx
.Edx
);
541 ESP_sig(context
) = rm_ctx
.Ebx
;
542 CS_sig(context
) = LOWORD(rm_ctx
.Esi
);
543 EIP_sig(context
) = rm_ctx
.Edi
;
544 EBP_sig(context
) = rm_ctx
.Ebp
;
548 /* Return to new address and hope that we didn't mess up */
549 TRACE("re-entering protected mode at %04x:%08lx\n",
550 CS_sig(context
), EIP_sig(context
));
555 /**********************************************************************
556 * DOSVM_AllocRMCB (WINEDOS.@)
558 void WINAPI
DOSVM_AllocRMCB( CONTEXT86
*context
)
560 RMCB
*NewRMCB
= DPMI_AllocRMCB();
562 TRACE("Function to call: %04x:%04x\n", (WORD
)context
->SegDs
, SI_reg(context
) );
566 /* FIXME: if 32-bit DPMI client, use ESI and EDI */
567 NewRMCB
->proc_ofs
= LOWORD(context
->Esi
);
568 NewRMCB
->proc_sel
= context
->SegDs
;
569 NewRMCB
->regs_ofs
= LOWORD(context
->Edi
);
570 NewRMCB
->regs_sel
= context
->SegEs
;
571 SET_LOWORD( context
->Ecx
, HIWORD(NewRMCB
->address
) );
572 SET_LOWORD( context
->Edx
, LOWORD(NewRMCB
->address
) );
576 SET_LOWORD( context
->Eax
, 0x8015 ); /* callback unavailable */
582 /**********************************************************************
583 * DOSVM_FreeRMCB (WINEDOS.@)
585 void WINAPI
DOSVM_FreeRMCB( CONTEXT86
*context
)
587 FIXME("callback address: %04x:%04x\n",
588 CX_reg(context
), DX_reg(context
));
590 if (DPMI_FreeRMCB(MAKELONG(DX_reg(context
), CX_reg(context
)))) {
591 SET_LOWORD( context
->Eax
, 0x8024 ); /* invalid callback address */
597 /**********************************************************************
600 * Handler for real-mode int 31h (DPMI).
602 void WINAPI
DOSVM_Int31Handler( CONTEXT86
*context
)
604 /* check if it's our wrapper */
605 TRACE("called from real mode\n");
606 if (context
->SegCs
==DOSMEM_dpmi_seg
) {
607 /* This is the protected mode switch */
611 else if (context
->SegCs
==DOSMEM_xms_seg
)
613 /* This is the XMS driver entry point */
614 XMS_Handler(context
);
620 RMCB
*CurrRMCB
= FirstRMCB
;
622 while (CurrRMCB
&& (HIWORD(CurrRMCB
->address
) != context
->SegCs
))
623 CurrRMCB
= CurrRMCB
->next
;
626 /* RMCB call, propagate to protected-mode handler */
627 DPMI_CallRMCBProc(context
, CurrRMCB
, dpmi_flag
);
632 /* chain to protected mode handler */
633 INT_Int31Handler( context
);