4 * Copyright 1995 Alexandre Julliard
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.
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.
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
22 #include "wine/port.h"
25 #include "wine/winbase16.h"
31 #include "wine/debug.h"
33 WINE_DEFAULT_DEBUG_CHANNEL(int31
);
35 /* Structure for real-mode callbacks */
57 typedef struct tagRMCB
{
59 DWORD proc_ofs
,proc_sel
;
60 DWORD regs_ofs
,regs_sel
;
64 static RMCB
*FirstRMCB
= NULL
;
65 static WORD dpmi_flag
;
67 /**********************************************************************
70 * Return TRUE if we are in 32-bit protected mode DOS process.
72 BOOL
DOSVM_IsDos32(void)
74 return (dpmi_flag
& 1) ? TRUE
: FALSE
;
78 /**********************************************************************
79 * INT_GetRealModeContext
81 static void INT_GetRealModeContext( REALMODECALL
*call
, CONTEXT86
*context
)
83 context
->Eax
= call
->eax
;
84 context
->Ebx
= call
->ebx
;
85 context
->Ecx
= call
->ecx
;
86 context
->Edx
= call
->edx
;
87 context
->Esi
= call
->esi
;
88 context
->Edi
= call
->edi
;
89 context
->Ebp
= call
->ebp
;
90 context
->EFlags
= call
->fl
| V86_FLAG
;
91 context
->Eip
= call
->ip
;
92 context
->Esp
= call
->sp
;
93 context
->SegCs
= call
->cs
;
94 context
->SegDs
= call
->ds
;
95 context
->SegEs
= call
->es
;
96 context
->SegFs
= call
->fs
;
97 context
->SegGs
= call
->gs
;
98 context
->SegSs
= call
->ss
;
102 /**********************************************************************
103 * INT_SetRealModeContext
105 static void INT_SetRealModeContext( REALMODECALL
*call
, CONTEXT86
*context
)
107 call
->eax
= context
->Eax
;
108 call
->ebx
= context
->Ebx
;
109 call
->ecx
= context
->Ecx
;
110 call
->edx
= context
->Edx
;
111 call
->esi
= context
->Esi
;
112 call
->edi
= context
->Edi
;
113 call
->ebp
= context
->Ebp
;
114 call
->fl
= LOWORD(context
->EFlags
);
115 call
->ip
= LOWORD(context
->Eip
);
116 call
->sp
= LOWORD(context
->Esp
);
117 call
->cs
= context
->SegCs
;
118 call
->ds
= context
->SegDs
;
119 call
->es
= context
->SegEs
;
120 call
->fs
= context
->SegFs
;
121 call
->gs
= context
->SegGs
;
122 call
->ss
= context
->SegSs
;
128 void DPMI_CallRMCB32(RMCB
*rmcb
, UINT16 ss
, DWORD esp
, UINT16
*es
, DWORD
*edi
)
129 #if 0 /* original code, which early gccs puke on */
132 __asm__
__volatile__(
140 ".byte 0x36, 0xff, 0x18\n" /* lcall *%ss:(%eax) */
146 : "=d" (*es
), "=D" (*edi
), "=S" (_clobber
), "=a" (_clobber
), "=c" (_clobber
)
147 : "0" (ss
), "2" (esp
),
148 "4" (rmcb
->regs_sel
), "1" (rmcb
->regs_ofs
),
149 "3" (&rmcb
->proc_ofs
) );
151 #else /* code generated by a gcc new enough */
153 __ASM_GLOBAL_FUNC(DPMI_CallRMCB32
,
158 "movl 0x8(%ebp),%eax\n\t"
159 "movl 0x10(%ebp),%esi\n\t"
160 "movl 0xc(%ebp),%edx\n\t"
161 "movl 0x10(%eax),%ecx\n\t"
162 "movl 0xc(%eax),%edi\n\t"
171 ".byte 0x36, 0xff, 0x18\n\t" /* lcall *%ss:(%eax) */
177 "movl 0x14(%ebp),%eax\n\t"
178 "movw %dx,(%eax)\n\t"
179 "movl 0x18(%ebp),%edx\n\t"
180 "movl %edi,(%edx)\n\t"
187 #endif /* __i386__ */
189 /**********************************************************************
192 * This routine does the hard work of calling a callback procedure.
194 static void DPMI_CallRMCBProc( CONTEXT86
*context
, RMCB
*rmcb
, WORD flag
)
196 if (IS_SELECTOR_SYSTEM( rmcb
->proc_sel
)) {
197 /* Wine-internal RMCB, call directly */
198 ((RMCBPROC
)rmcb
->proc_ofs
)(context
);
204 INT_SetRealModeContext(MapSL(MAKESEGPTR( rmcb
->regs_sel
, rmcb
->regs_ofs
)), context
);
205 ss
= SELECTOR_AllocBlock( (void *)(context
->SegSs
<<4), 0x10000, WINE_LDT_FLAGS_DATA
);
208 FIXME("untested!\n");
210 /* The called proc ends with an IRET, and takes these parameters:
211 * DS:ESI = pointer to real-mode SS:SP
212 * ES:EDI = pointer to real-mode call structure
214 * ES:EDI = pointer to real-mode call structure (may be a copy)
215 * It is the proc's responsibility to change the return CS:IP in the
216 * real-mode call structure. */
218 /* 32-bit DPMI client */
219 DPMI_CallRMCB32(rmcb
, ss
, esp
, &es
, &edi
);
221 /* 16-bit DPMI client */
222 CONTEXT86 ctx
= *context
;
223 ctx
.SegCs
= rmcb
->proc_sel
;
224 ctx
.Eip
= rmcb
->proc_ofs
;
227 ctx
.SegEs
= rmcb
->regs_sel
;
228 ctx
.Edi
= rmcb
->regs_ofs
;
229 /* FIXME: I'm pretty sure this isn't right - should push flags first */
230 wine_call_to_16_regs_short(&ctx
, 0);
235 INT_GetRealModeContext( MapSL( MAKESEGPTR( es
, edi
)), context
);
237 ERR("RMCBs only implemented for i386\n");
243 /**********************************************************************
246 * This routine does the hard work of calling a real mode procedure.
248 int DPMI_CallRMProc( CONTEXT86
*context
, LPWORD stack
, int args
, int iret
)
251 LPVOID addr
= NULL
; /* avoid gcc warning */
253 int alloc
= 0, already
= 0;
256 TRACE("EAX=%08lx EBX=%08lx ECX=%08lx EDX=%08lx\n",
257 context
->Eax
, context
->Ebx
, context
->Ecx
, context
->Edx
);
258 TRACE("ESI=%08lx EDI=%08lx ES=%04lx DS=%04lx CS:IP=%04lx:%04x, %d WORD arguments, %s\n",
259 context
->Esi
, context
->Edi
, context
->SegEs
, context
->SegDs
,
260 context
->SegCs
, LOWORD(context
->Eip
), args
, iret
?"IRET":"FAR" );
264 /* there might be some code that just jumps to RMCBs or the like,
265 in which case following the jumps here might get us to a shortcut */
266 code
= CTX_SEG_OFF_TO_LIN(context
, context
->SegCs
, context
->Eip
);
268 case 0xe9: /* JMP NEAR */
269 context
->Eip
+= 3 + *(WORD
*)(code
+1);
270 /* yeah, I know these gotos don't look good... */
271 goto callrmproc_again
;
272 case 0xea: /* JMP FAR */
273 context
->Eip
= *(WORD
*)(code
+1);
274 context
->SegCs
= *(WORD
*)(code
+3);
275 /* ...but since the label is there anyway... */
276 goto callrmproc_again
;
277 case 0xeb: /* JMP SHORT */
278 context
->Eip
+= 2 + *(signed char *)(code
+1);
279 /* ...because of other gotos below, so... */
280 goto callrmproc_again
;
283 /* shortcut for chaining to internal interrupt handlers */
284 if ((context
->SegCs
== 0xF000) && iret
)
286 DOSVM_RealModeInterrupt( LOWORD(context
->Eip
)/4, context
);
290 /* shortcut for RMCBs */
291 CurrRMCB
= FirstRMCB
;
293 while (CurrRMCB
&& (HIWORD(CurrRMCB
->address
) != context
->SegCs
))
294 CurrRMCB
= CurrRMCB
->next
;
296 if (!CurrRMCB
&& !MZ_Current())
298 FIXME("DPMI real-mode call using DOS VM task system, not fully tested!\n");
299 TRACE("creating VM86 task\n");
303 if (!context
->SegSs
) {
304 alloc
= 1; /* allocate default stack */
305 stack16
= addr
= DOSMEM_GetBlock( 64, (UINT16
*)&(context
->SegSs
) );
309 ERR("could not allocate default stack\n");
313 stack16
= CTX_SEG_OFF_TO_LIN(context
, context
->SegSs
, context
->Esp
);
315 context
->Esp
-= (args
+ (iret
?1:0)) * sizeof(WORD
);
317 if (args
) memcpy(stack16
, stack
, args
*sizeof(WORD
) );
318 /* push flags if iret */
321 *stack16
= LOWORD(context
->EFlags
);
323 /* push return address (return to interrupt wrapper) */
324 *(--stack16
) = DOSVM_dpmi_segments
->wrap_seg
;
327 context
->Esp
-= 2*sizeof(WORD
);
332 /* RMCB call, invoke protected-mode handler directly */
333 DPMI_CallRMCBProc(context
, CurrRMCB
, dpmi_flag
);
334 /* check if we returned to where we thought we would */
335 if ((context
->SegCs
!= DOSVM_dpmi_segments
->wrap_seg
) ||
336 (LOWORD(context
->Eip
) != 0)) {
337 /* we need to continue at different address in real-mode space,
338 so we need to set it all up for real mode again */
339 goto callrmproc_again
;
342 TRACE("entering real mode...\n");
343 DOSVM_Enter( context
);
344 TRACE("returned from real-mode call\n");
346 if (alloc
) DOSMEM_FreeBlock( addr
);
351 /**********************************************************************
352 * CallRMInt (WINEDOS.@)
354 void WINAPI
DOSVM_CallRMInt( CONTEXT86
*context
)
356 CONTEXT86 realmode_ctx
;
357 FARPROC16 rm_int
= DOSVM_GetRMHandler( BL_reg(context
) );
358 REALMODECALL
*call
= MapSL( MAKESEGPTR( context
->SegEs
, DI_reg(context
) ));
359 INT_GetRealModeContext( call
, &realmode_ctx
);
361 /* we need to check if a real-mode program has hooked the interrupt */
362 if (HIWORD(rm_int
)!=0xF000) {
363 /* yup, which means we need to switch to real mode... */
364 realmode_ctx
.SegCs
= HIWORD(rm_int
);
365 realmode_ctx
.Eip
= LOWORD(rm_int
);
366 if (DPMI_CallRMProc( &realmode_ctx
, NULL
, 0, TRUE
))
369 RESET_CFLAG(context
);
370 /* use the IP we have instead of BL_reg, in case some apps
371 decide to move interrupts around for whatever reason... */
372 DOSVM_RealModeInterrupt( LOWORD(rm_int
)/4, &realmode_ctx
);
374 INT_SetRealModeContext( call
, &realmode_ctx
);
378 /**********************************************************************
379 * CallRMProc (WINEDOS.@)
381 void WINAPI
DOSVM_CallRMProc( CONTEXT86
*context
, int iret
)
383 REALMODECALL
*p
= MapSL( MAKESEGPTR( context
->SegEs
, DI_reg(context
) ));
386 TRACE("RealModeCall: EAX=%08lx EBX=%08lx ECX=%08lx EDX=%08lx\n",
387 p
->eax
, p
->ebx
, p
->ecx
, p
->edx
);
388 TRACE(" ESI=%08lx EDI=%08lx ES=%04x DS=%04x CS:IP=%04x:%04x, %d WORD arguments, %s\n",
389 p
->esi
, p
->edi
, p
->es
, p
->ds
, p
->cs
, p
->ip
, CX_reg(context
), iret
?"IRET":"FAR" );
391 if (!(p
->cs
) && !(p
->ip
)) { /* remove this check
392 if Int21/6501 case map function
393 has been implemented */
397 INT_GetRealModeContext(p
, &context16
);
398 DPMI_CallRMProc( &context16
, ((LPWORD
)MapSL(MAKESEGPTR(context
->SegSs
, LOWORD(context
->Esp
))))+3,
399 CX_reg(context
), iret
);
400 INT_SetRealModeContext(p
, &context16
);
404 /* (see dosmem.c, function DOSMEM_InitDPMI) */
405 static void StartPM( CONTEXT86
*context
)
407 UINT16 cs
, ss
, ds
, es
;
409 DWORD psp_ofs
= (DWORD
)(DOSVM_psp
<<4);
410 PDB16
*psp
= (PDB16
*)psp_ofs
;
411 HANDLE16 env_seg
= psp
->environment
;
412 unsigned char selflags
= WINE_LDT_FLAGS_DATA
;
414 RESET_CFLAG(context
);
415 dpmi_flag
= AX_reg(context
);
416 /* our mode switch wrapper have placed the desired CS into DX */
417 cs
= SELECTOR_AllocBlock( (void *)(DX_reg(context
)<<4), 0x10000, WINE_LDT_FLAGS_CODE
);
418 /* due to a flaw in some CPUs (at least mine), it is best to mark stack segments as 32-bit if they
419 can be used in 32-bit code. Otherwise, these CPUs may not set the high word of esp during a
420 ring transition (from kernel code) to the 16-bit stack, and this causes trouble if executing
421 32-bit code using this stack. */
422 if (dpmi_flag
& 1) selflags
|= WINE_LDT_FLAGS_32BIT
;
423 ss
= SELECTOR_AllocBlock( (void *)(context
->SegSs
<<4), 0x10000, selflags
);
424 /* do the same for the data segments, just in case */
425 if (context
->SegDs
== context
->SegSs
) ds
= ss
;
426 else ds
= SELECTOR_AllocBlock( (void *)(context
->SegDs
<<4), 0x10000, selflags
);
427 es
= SELECTOR_AllocBlock( psp
, 0x100, selflags
);
428 /* convert environment pointer, as the spec says, but we're a bit lazy about the size here... */
429 psp
->environment
= SELECTOR_AllocBlock( (void *)(env_seg
<<4), 0x10000, WINE_LDT_FLAGS_DATA
);
432 pm_ctx
.SegCs
= DOSVM_dpmi_segments
->dpmi_sel
;
433 /* our mode switch wrapper expects the new CS in DX, and the new SS in AX */
441 TRACE("DOS program is now entering protected mode\n");
442 wine_call_to_16_regs_short(&pm_ctx
, 0);
444 /* in the current state of affairs, we won't ever actually return here... */
445 /* we should have int21/ah=4c do it someday, though... */
447 FreeSelector16(psp
->environment
);
448 psp
->environment
= env_seg
;
450 if (ds
!= ss
) FreeSelector16(ds
);
455 static RMCB
*DPMI_AllocRMCB( void )
457 RMCB
*NewRMCB
= HeapAlloc(GetProcessHeap(), 0, sizeof(RMCB
));
462 LPVOID RMCBmem
= DOSMEM_GetBlock(4, &uParagraph
);
465 *p
++ = 0xcd; /* RMCB: */
466 *p
++ = 0x31; /* int $0x31 */
467 /* it is the called procedure's task to change the return CS:EIP
468 the DPMI 0.9 spec states that if it doesn't, it will be called again */
470 *p
++ = 0xfc; /* jmp RMCB */
471 NewRMCB
->address
= MAKELONG(0, uParagraph
);
472 NewRMCB
->next
= FirstRMCB
;
479 FARPROC16 WINAPI
DPMI_AllocInternalRMCB( RMCBPROC proc
)
481 RMCB
*NewRMCB
= DPMI_AllocRMCB();
484 NewRMCB
->proc_ofs
= (DWORD
)proc
;
485 NewRMCB
->proc_sel
= 0;
486 NewRMCB
->regs_ofs
= 0;
487 NewRMCB
->regs_sel
= 0;
488 return (FARPROC16
)(NewRMCB
->address
);
494 static int DPMI_FreeRMCB( DWORD address
)
496 RMCB
*CurrRMCB
= FirstRMCB
;
497 RMCB
*PrevRMCB
= NULL
;
499 while (CurrRMCB
&& (CurrRMCB
->address
!= address
))
502 CurrRMCB
= CurrRMCB
->next
;
507 PrevRMCB
->next
= CurrRMCB
->next
;
509 FirstRMCB
= CurrRMCB
->next
;
510 DOSMEM_FreeBlock(PTR_REAL_TO_LIN(SELECTOROF(CurrRMCB
->address
),OFFSETOF(CurrRMCB
->address
)));
511 HeapFree(GetProcessHeap(), 0, CurrRMCB
);
518 void WINAPI
DPMI_FreeInternalRMCB( FARPROC16 proc
)
520 DPMI_FreeRMCB( (DWORD
)proc
);
524 /**********************************************************************
525 * RawModeSwitch (WINEDOS.@)
527 * DPMI Raw Mode Switch handler
529 void WINAPI
DOSVM_RawModeSwitch( CONTEXT86
*context
)
534 /* initialize real-mode context as per spec */
535 memset(&rm_ctx
, 0, sizeof(rm_ctx
));
536 rm_ctx
.SegDs
= AX_reg(context
);
537 rm_ctx
.SegEs
= CX_reg(context
);
538 rm_ctx
.SegSs
= DX_reg(context
);
539 rm_ctx
.Esp
= context
->Ebx
;
540 rm_ctx
.SegCs
= SI_reg(context
);
541 rm_ctx
.Eip
= context
->Edi
;
542 rm_ctx
.Ebp
= context
->Ebp
;
545 rm_ctx
.EFlags
= context
->EFlags
; /* at least we need the IF flag */
547 /* enter real mode again */
548 TRACE("re-entering real mode at %04lx:%04lx\n",rm_ctx
.SegCs
,rm_ctx
.Eip
);
549 ret
= DOSVM_Enter( &rm_ctx
);
550 /* when the real-mode stuff call its mode switch address,
551 DOSVM_Enter will return and we will continue here */
555 /* if the sync was lost, there's no way to recover */
559 /* alter protected-mode context as per spec */
560 context
->SegDs
= LOWORD(rm_ctx
.Eax
);
561 context
->SegEs
= LOWORD(rm_ctx
.Ecx
);
562 context
->SegSs
= LOWORD(rm_ctx
.Edx
);
563 context
->Esp
= rm_ctx
.Ebx
;
564 context
->SegCs
= LOWORD(rm_ctx
.Esi
);
565 context
->Eip
= rm_ctx
.Edi
;
566 context
->Ebp
= rm_ctx
.Ebp
;
570 /* Return to new address and hope that we didn't mess up */
571 TRACE("re-entering protected mode at %04lx:%08lx\n",
572 context
->SegCs
, context
->Eip
);
576 /**********************************************************************
577 * AllocRMCB (WINEDOS.@)
579 void WINAPI
DOSVM_AllocRMCB( CONTEXT86
*context
)
581 RMCB
*NewRMCB
= DPMI_AllocRMCB();
583 TRACE("Function to call: %04x:%04x\n", (WORD
)context
->SegDs
, SI_reg(context
) );
587 /* FIXME: if 32-bit DPMI client, use ESI and EDI */
588 NewRMCB
->proc_ofs
= LOWORD(context
->Esi
);
589 NewRMCB
->proc_sel
= context
->SegDs
;
590 NewRMCB
->regs_ofs
= LOWORD(context
->Edi
);
591 NewRMCB
->regs_sel
= context
->SegEs
;
592 SET_LOWORD( context
->Ecx
, HIWORD(NewRMCB
->address
) );
593 SET_LOWORD( context
->Edx
, LOWORD(NewRMCB
->address
) );
597 SET_LOWORD( context
->Eax
, 0x8015 ); /* callback unavailable */
603 /**********************************************************************
604 * FreeRMCB (WINEDOS.@)
606 void WINAPI
DOSVM_FreeRMCB( CONTEXT86
*context
)
608 FIXME("callback address: %04x:%04x\n",
609 CX_reg(context
), DX_reg(context
));
611 if (DPMI_FreeRMCB(MAKELONG(DX_reg(context
), CX_reg(context
)))) {
612 SET_LOWORD( context
->Eax
, 0x8024 ); /* invalid callback address */
618 /**********************************************************************
621 * Handler for real-mode int 31h (DPMI).
623 void WINAPI
DOSVM_Int31Handler( CONTEXT86
*context
)
625 /* check if it's our wrapper */
626 TRACE("called from real mode\n");
627 if (context
->SegCs
==DOSVM_dpmi_segments
->dpmi_seg
) {
628 /* This is the protected mode switch */
632 else if (context
->SegCs
==DOSVM_dpmi_segments
->xms_seg
)
634 /* This is the XMS driver entry point */
635 XMS_Handler(context
);
641 RMCB
*CurrRMCB
= FirstRMCB
;
643 while (CurrRMCB
&& (HIWORD(CurrRMCB
->address
) != context
->SegCs
))
644 CurrRMCB
= CurrRMCB
->next
;
647 /* RMCB call, propagate to protected-mode handler */
648 DPMI_CallRMCBProc(context
, CurrRMCB
, dpmi_flag
);
653 /* chain to protected mode handler */
654 INT_Int31Handler( context
);