This document is intended for the experienced programmer with knowledge of
a good deal of assembly and the 386. If you are not one such person, the text
that follows will be very confusing to you. Read on if you wish... You have
This is the doc for PMODE v1.29a. I am not going to rewrite it just for
version 2.1232 since almost all of this doc applies perfectly. There are a few
changes from PMODE 1.29a to 2.1232 however. They follow at the end of this
This code, as well as this doc (damn I hate writing dox), were written up
by Tran (a.k.a. Thomas Pytel) of Renaissance. It is intended for all those of
you out there who would like to code in protected mode, but keep running up
against obstacles. If you want to use this thing as it is though, you will
have to code in 100% ASM. But hey, that's no problem. I do it all the time. I
did write this thing for myself. So if you have a problem with it, tough. You
have a few options. You can use it as it is, and code in all ASM (not a
terribly difficult feat). You can examine the code, and use what you learn to
code up your own protected mode system, one that you can maybe throw into a
nice high level language maybe. Or you can just ignore this code and this doc.
Ah... many are the joys and woes of this chip we all know as the 386. The
486 - bah, just a 386 with a cache. The 586 - hahaha, two 486s with double the
cache. But the wonderful 386 architecture. That finally gives us our needed
flat memory. Not to mention the paging (not a personal favorite, but VERY
useful). It's been around here for a while now, but it hasn't been used very
much. I'm assuming you know all the problems associated with sharing the
386 protected mode amongst different programs. So I'll just go on to tell ya
about two methods of doing this, VCPI and DPMI.
In the beginning (or at least a long time ago). There was the EMS and the
XMS. Two pathetic attempts by the hopelessly crippled (design-wise) 8086s and
286s. EMS and XMS were fine for accessing small chunks of a larger address
space at a time. These methods were usually very slow. As for XMS, the 286
actually had to copy memory every time it was requested. And hardware EMS
resided on the slow system bus (well, not that much slower compared to the
speed of those machines). With the introduction of the 386, and soon the
memory managers, things changed. EMS and XMS could now be handled with the
on-chip paging mechanism. Which was faster than the memory copy method, and
even the hardware EMS. But for this task, the memory managers had to run the
system in V86 mode. Forsaking all other programs which would want to take
control of the 386 protected mode.
Thus was devised VCPI. A method of allowing programs running in the V86
system to switch to protected mode. After switching to protected mode, those
programs would have total control of the system. Running at a privilege level
of 0. They could communicate with the other protected mode program, the VCPI
server, through a common memory address space starting with the first megabyte
of memory. VCPI was a superset of the EMS standard, and was implemented in
virtually all memory managers and 386 extenders. But VCPI was woefully
inadequate for the multitaskers that were soon to follow.
Then there came forth the DPMI standard. Born from the window, it is the
epitome of total lameness. Many are the unknowing peons who run the window.
And many are the lamer who do not know the purpose of our favorite key
combination, the CTRL+ALT+DEL. A wondrous function of that eternal TSR called
the BIOS. Many a fool activate it whilst on a local BBS, erroneously believing
they will get free downloads. But I digress, we were all lame at one time or
another. This standard of the DPMI was different from those of antiquity.
This foul interface was designed with total control in mind. Total control
by the evil host whose lameness was imposed upon all those running beneath it.
At a lowly CPL3, this loathsome beast's clients are truly at a disadvantage.
Their INs and OUTs under the thorough scrutiny of the master. Forced to
grovel for precious memory. Not even their own instructions are sacred. For
the fiend which is DPMI, patiently waits, and watches. Ready to tear the
precious life from the pitiable client at the slightest sign of the forbidden
CLI. Then the monster does its deeds, toggling an illusion of that which was
once real. This virtual flag of interrupts ensures the depraved host total
domination. So easy to hate it is this beast. But yet... In the name of
multiple tasking, and system integrity. The DPMI continues to gain ground.
(Well sure, if ya want those things. But where is your adventure... Your
spirit... Have you totally forgotten what a good crash feels like???)
These days, VCPI and DPMI permeate the world of 386 software. From memory
managers, to Windows and OS/2. If something is protected, its using one of
these. VCPI though, is on its way out. Being replaced by DPMI. And if you want
to code protected mode... Unless you force upon the user a clean boot, you
have to support one of these two standards.
So what you got here:
Having coded raw flat protected mode, I am hooked. I shall not revert to
real mode limitations. My previous protected mode system 'START32' was very
intolerant of crap like VCPI and DPMI and EMS and XMS and everything else. It
required a clean boot to function. But hey, for that I got pure wonderful
flat protected mode. Unfortunately, the masses did not share my sentiment.
And they whined about rebooting. Pitiful as they were, I have heard their
cries. And I understand. The culmination is the code before you (or rather in
the other file, PMODE.ASM). This is basically the same pure beautiful flat
protected mode system as my old START32 system, except that it will run under
VCPI and DPMI and XMS (albeit a little slower).
What it thinks it does:
The point of this protected mode header thingy is to provide a simple, flat
mode environment for easy assembly coding. PMODE will take care of detecting
a 386, the type of system (raw,XMS,VCPI,or DPMI) and making sure there is
enough memory, both low and high. The protected mode code runs in one big
segment, the size of which is infinite (or 4GB, whichever is larger). You can
call real mode interrupts of far routines from your pmode code. And those
in turn can call back to protected mode. And on and on like this to the limits
of the stack. Once in pmode, all normal IRQs are still active and are by
default, redirected to their real mode handlers. You can intercept these
IRQs for your own use, then pass them on to the real mode handlers or not.
This system was coded with speed in mind. It is meant for stuff like games,
demos (as in the ones with cool muzik and grafix), or stuff that needs a lot
of memory. What you should not attempt from this system, is going TSR, or
executing DOS programs. This system runs absolutely perfectly for what it was
meant for in raw and XMS. There are some minor problems under VCPI and DPMI
though, and i'll list them later. But right now...
First of all, the structure:
In PMODE.ASM, there are three segments. They are as follows:
) CODE16 - A 16bit segment that holds all the real mode and 16bit protected
mode init and exit code. I'm not sure (I've forgotten by now), but
you should probably leave it as the first segment of the EXE.
) CODE32 - The huge 32bit segment. You can throw in as much code as will fit
in low memory (no 64k fixup overflows). If you need more code
space, you're gonna have to load it into extended memory at runtime
and use it there. In pmode, addresses (code and data, they're the
same memory space) are offset from the beginning of this segment.
If you want to address something below the beginning of this seg,
you go about it in a different way. I'll explain later.
) CODEEND - This MUST be the last segment in the EXE. It is the base for the
stack and low memory allocation.
The stack is an interesting animal (or was it a mineral). But anyway, you
don't have to worry about the stack. The header will set it up to whatever
you specify (up to a limit of 64k (there's a reason)). The same stack is
shared by your pmode code and real mode calls (that's the reason). The stack
always begins at CODEEND, and goes on for STAKLEN (a var declared at the top
of PMODE.ASM) paragraphs. I will explain how its used by both pmode and real
mode code later.
There are two heaps you can allocate memory from at run time. Low memory,
which is all conventional memory below A0000h. And extended memory, all the
nice crap above 1M. There's a reason for keeping them separate (other than
that big hole between A0000h and 100000h (which I did not want to fill in or
rearrange with paging)). DOS can only see the low heap. So in calls where you
have to pass buffer addresses, you must pass only buffers that you have in low
memory. Low memory is the place where you would allocate any critical disk or
DMA buffers (more on a few potential problems with DMA later).
You call real mode interrupts and far routines with 'virtual registers'.
Memory images of the registers as they will be set for the real mode int or
proc. Code in real mode can call protected mode routines with those same
This thing has a few 'functions' for dealing with low and high memory and
getting and setting IRQs. There are also some variables made available to ya
which you'll find quite useful.
Details of runtime:
I'm assuming you know all about selectors and descriptor tables and stuff
like that. But going on... After it does all that it needs to, PMODE will
jump to an external label in code32 called '_main'. This is where your 32bit
code takes over. When you gain control:
) The stack is all set up.
) The interrupts are disabled (and have been all the way from real mode) just
in case theres something you want to do first (like shut them all off at
21h and 0a1h, or replace all the vektorz (so what if I spell it vektorz)).
) CS points to the code segment you're running in (duh...).
) DS,ES,FS, and SS point to an alias of the code segment (same memory). (In
case you don't remember, you can NEVER write to a CS: override in protected
mode... Read, yes... But not write).
) GS is a segment that's just as big as the others (infinite, remember?...)
but starts at absolute 0. This is ofcourse useful for accessing the real
mode int vektor table, or the BIOS data area, or the PSP, etc...
There are three main selectors you have to know. '_selcode', '_seldata', and
'_selzero' are 16bit word vars you can access to get the selector values for
the code, data, and zero (GS) segments respectively. Their values are 8, 10h,
and 18h under all protected systems except DPMI. As I said, on getting to
_main, CS=_selcode, DS=ES=FS=SS=_seldata, and GS=_selzero. You can change the
segregs if you wish (for example to do a REP MOVS in the zero seg). But the
PMODE 'functions' and ints expect the segregs to be these values (except for
the special case of SS, this will be explained later). And these must be the
values when you jump to '_exit' to return to DOS. Another thing that is
assumed by PMODE is DF=0 (direction flag is clear (like the CLD instruction)).
This is because most string moves are forward. If you want to do a string move
the other way, go ahead. Just do a CLD after.
As I said, in pmode, all addresses are relative to the beginning of the
CODE32 segment (which could start anywhere in low memory). For this reason,
you must adjust any physical memory pointers before you use them. That is,
to access something at B8000h (B800:0000, if you haven't noticed, I'm using
all true linear addresses in this doc, no need for the seg:off crap). Anyway,
If you want to write to B8000h, it will not be at DS:B8000, but at
DS:(B8000h-linear address of the beginning of CODE32). And this linear address
is stored in a variable called '_code32a'. So if the segment CODE32 was 1F43,
the linear address would be 1F430h (seg*10h remember). So to get a pointer
to B8000h, you would do something like:
And just this macro is provided in PMODE.INC, as well as a macro to go the
other way, relative address to physical. Ofcourse, if you address something
with GS (assuming GS is _selzero), you can use the actual unadjusted linear
address. The absolute linear address for CODE16 is also provided in
'_code16a'. As well as the absolute linear address of the PSP in '_pspa'. The
linear addresses '_code16a' and '_pspa' will always be less than '_code32a'.
To access them (memory pointed to by them, these vars are in CODE32), you will
have to use one of two methods. One is easy enough, just use the GS segment.
Or you could use negative indexes from the normal segment. This relies on the
4G wraparound (don't worry, the limits of all segments in the descriptor table
are 4G). Strange things may happen if the 686 doesn't support the 4G wrap, but
from what I understand, the 586 is still limited (limited??? damn it, even CDs
don't reach that) to 4G segments.
As for the memory. You have those two heaps, low and high (extended) memory.
Each of which is guaranteed to be at least as much as you specified in LOWMIN
and EXTMIN at the top of PMODE.ASM. PMODE will hog up all low memory (because
its meant to run standalone), and it will attempt to grab all the high memory
it can. Two dwords hold information about each memory area. '_lomembase' and
'_lomemtop' specify the base and top of the low memory pool as relative
addresses (ready to use, no adjustment needed). The total amount of low memory
available in bytes is _lomemtop-_lomembase (notice _lomemtop points to one
byte beyond the last available byte). The _getlomem 'function' is a very
simple routine that takes a length in EAX, and checks to see if there is
enough low mem. If there is enough, it adds the length to _lomembase and
returns a pointer (ready to use) in EAX to the low memory block along with the
carry flag clear. If it finds that _lomemtop-_lomembase > length (not enough
memory), it returns with the carry flag set and EAX undefined. '_himembase',
'_himemtop', and '_gethimem' are the same thing for high mem as those other
things are for low mem. Just to make sure you understand this, here is some
code, cuz as we all know, code speaks louder than words (actually, silence
speaks louder than code, but what the hell).
mov edi,_lomembase ; fill all available low memory with 0
mov edi,_himembase ; ditto for high memory
There is one other curiosity provided. '_getmem' will get a any block of
memory. It will first check low memory, and if there is not enough, it will
check high memory. If you wish, you can code yourself a little 'malloc'
library that will deal with blocks, and provide you with all the joys of
Calling icky real mode:
I did say you can call real mode, and back. Let me first say that this is
only provided so that you can call real mode interrupts, and routines that
you don't want to recode in protected mode. I would not suggest making it a
habit of coding across modes. Except maybe if you do a driver that you also
want to work from real mode. But anyway... You can call real mode interrupts
or procedures from protected mode through INT 32h (call real mode far proc)
and INT 33h (call real mode int). These interrupts are only available to the
protected mode part of your program. In real mode, there is a separate INT 32h
that calls a protected mode routine. Don't confuse the two INT32s with each
other, though they do basically the same thing. To pass register values across
modes, you use 'virtual registers' (I just love that word... virtual... haha).
These 'virtual registers' are merely memory images of EAX,EBX,ECX,EDX,ESI,EDI,
EBP,DS,ES,FS,and GS. AL and AH and AX and BL ... etc ... are there too, and
they share the appropriate memory space with each other so if you change the
'virtual' AH register, the 'virtual' AX and EAX registers will be changed
accordingly. You'll notice there are no SS,ESP,CS,EIP registers. CS:EIP is
taken from the real mode interrupt vektor table for int calls, and passed in
the real CX:DX registers for a procedure call. SS:ESP is set up in the master
stack used by PMODE. Which is the stack your program runs on. I'll explain the
stack handling in detail l8r. Heres a breakdown of the ints:
) INT 33h from pmode: Do a real mode interrupt.
AL=interrupt you want to do. All V86R_??? general and segment registers will
be passed to the real mode handler. They will also be passed back as the
return values. The carry, zero, aux, parity, sign, and overflow flags will
be passed back as the actual CPU flags. The real mode interrupt will be
called with interrupts disabled (as it is usually). Keep in mind, no CPU
registers will be modified (except the flags mentioned). Only their V86R_???
images will be changed by the real mode int handler.
) INT 32h from pmode: Call a real mode far procedure.
CX:DX=seg:off you want to call. The register passing works just like INT33.
Except that the interrupt flag will be preserved across the call to real
mode (but not back, the IF flag will be in the same state as it was before
) INT 32h from real mode: Call a pmode procedure.
EDX=off. A 32bit offset in the CODE32 segment. The register passing works
just like for the other INT32 in pmode, except that segregs are not passed
to or from the pmode routine. Upon entry to the routine, the system standard
thingys are set. That is:
CS=_selcode, DS=ES=FS=SS=_seldata, GS=_selzero, DF=0 (CLD).
And they must be that when the thing executes its RET (not RETF).
DPMI takes its toll and IRQs:
Upon startup, all the interrupt vektors for IRQs point to routines that
redirect the IRQs to their default real mode handlers. You can hook into any
IRQ you want. There are two dword pointers that allow you to get and set IRQ
vektorz. '_getirqvect' and '_setirqvect' point to routines to get and set the
linear address of the handler for specific IRQs within the CODE32 segment. To
get the address of a handler, just do a 'call _getirqvect' with BL set to the
IRQ num you want (0-15). EDX will be returned pointing to its current handler.
To set an IRQ, pass BL again as the IRQ number, and EDX as the offset of the
new handler. You can chain to the old handler if you want just by jumping to
the old address when your handler is done processing.
When your IRQ handler is called, you can be sure of only one thing. The IF
flag is clear. All the general regs and segregs should be treated as
undefined. It would seem logical that if SS=_seldata throughout the entire
code, it will be that in the IRQ. And this is true under all systems except
DPMI. DPMI deems it fit to switch onto another stack. And that's the stack
your handler must be on when it does its IRETD. You can switch stacks during
processing if you want, but I really don't suggest doing that. Also, if you
intend to call INT32/33 from your handler (you can, they are all totally
reenterant), you must be on that stack (DPMI requirement of a mode switch it
seems. I've tried to switch onto another stack (yes locked) and switch to
real mode (using DPMI raw mode switching and state saving) but DPMI dies if
another IRQ goes off in real mode).
Another consideration for DPMI is the IF flag. According to DPMI specs, only
CLI, STI, and INT 31h functions AX=900h and AX=901h should be counted on to
modify the interrupt flag (POPF(D) and IRET(D) should not). This is because
certain DPMI systems might have to virtualize the interrupt flag, and keep the
real flag enabled at all times (but don't worry, if the 'virtual' flag is
clear, your program will not get any IRQs). In practice, certain DPMIs do
allow IRET(D)s and POPF(D)s to modify the virtual interrupt flag. But this is
inconsistent across them. So if you want DPMI compatibility (you probably do,
or you would not be using this code), you should follow these rules:
) CLI and STI are allowed, and do their functions.
) Don't assume anything about POPF(D) and IRET(D) and the interrupt flag.
) Don't assume the interrupt flag PUSHF(D) stores on the stack is correct,
it might be the real flag or the 'virtual' flag.
) These DPMI INT 31h functions are supported under all systems.
) AX=900h: Get state of IF and disable it. Returns AL set to the IF flag.
) AX=901h: Get state of IF and enable it. Returns AL set to the IF flag.
) AX=902h: Only returns AL set to the IF flag (0=disabled, 1=enabled).
) At the end of an IRQ handler, put a STI. When the handler is called, flags
are automatically disabled. And if you do not reenable them, and neither
does the IRETD... Well... you get the point.
The stack entity:
PMODE uses one major stack for both pmode and real mode. This stack is
always located in low memory (always locked under DPMI). The size of the stack
is set at the top of PMODE.ASM in the var 'STAKLEN'. There is another var
there called 'STAKSAFE', and this is what must be explained.
When a mode switch occurs, the new stack is the old stack base minus
STAKSAFE paragraphs. The stack base is the stack location when your program
starts. And it is only modified by mode switches. An example is in order here.
Say your program starts, and you have a STAKLEN of 100h (1000h bytes) and
a STAKSAFE of 20h (200h bytes). After you have pushed a few values, and are
about to call real mode, the stack has gone down to 0F30h. After the mode
switch, the location in the stack will be 0E00h (1000h-200h). Now in real mode
you push some values. And the stack goes down to 0DE0h. Then you make a call
to protected mode. The protected mode stack will start at 0C00h (0E00h-200h).
After the return to real mode, the stack will be back to 0DE0h. You pop your
values and return again. Back in protected mode, the stack will be where it
was before the initial call to real mode, at 0F30h. So STAKSAFE is the maximum
stack size that is safe from modification if a mode switch occurs. But beware,
an IRQ that goes off that is redirected to the other mode is a mode switch.
That is, an IRQ in protected mode that is redirected to real mode will cause
the stack change. As will ANY IRQ in real mode (since it temporarily goes to
pmode, and is redirected back to real mode). An IRQ that occurs in pmode, and
is NOT sent to real mode is NOT a mode switch.
Under DPMI things are slightly different. DPMI handles its own stack
switching. Any IRQ causes a switch to a totally different stack. It gets a
little complicated as DPMI does its switching among 4 different stacks. I
was not able to put in my own IRQ redirectors to real mode, so you have to
rely on DPMIs redirectors (which in some cases don't redirect the IRQs as they
should). It seems even DPMI has problems managing its own stacks. Perhaps I
missed some critical little point. But I don't think so... Even with state
saving and the raw mode switching, if I switched off the stack the DPMI host
provided for an IRQ, then jumped to real mode, and another IRQ occurred...
Well... Let's just say that was the end of that. This may be a little
confusing, so let me summarize what will keep you safe:
) In an IRQ handler, DON'T switch off the stack it is entered with. Which is
not guaranteed that SS=_seldata.
) Don't do more nested calls across modes than (STAKLEN/STAKSAFE)-1. (-2 if
you just want to be totally safe).
) You CAN safely assume SS=_seldata in protected mode only in your main stream
of execution, and in routines that are called with INT32 from real mode.
) Consider your maximum effective stack size to be STAKSAFE, not STAKLEN.
) You CAN call across modes using INT32/33 from an IRQ handler in both real
and protected mode (useful for that pesky mouse callback thingy).
Are handled entirely by the DPMI host in those systems. In raw, XMS, and
VCPI, exceptions 0, 1, 2, 5, and 7 are reflected to real mode just like IRQs
would be (they are sent safely (actual real mode)). Exceptions 3, 4, 8 and
9-1fh cause immediate termination. There is no debug dump done. If you want
though, you can put in your own. The main exception handler is 'cp_exc'
somewhere around line 1828 in PMODE.ASM. As you can see from the little entry
code above it, it is entered directly from an exception with all registers
pushed and AL=exception number. And as you can see all it does is load up the
system default thingies and jumps to _exit. Just to clarify, it loads up DS,
ES, and FS with 10h and not _seldata simply because this is an exception
handler only for raw, XMS, and VCPI. Under which _seldata always equals 10h.
The 'virtual' registers might be a little confusing:
So here's some semi-sorta-pseudo-code.
all_genregs = [EAX, EBX, ECX, EDX, ESI, EDI, EBP]
all_segregs = [DS, ES, FS, GS]
all_regs = [all_genregs, all_segregs]
all_v_genregs = [V86R_EAX, V86R_EBX, V86R_ECX, V86R_EDX,
V86R_ESI, V86R_EDI, V86R_EBP]
all_v_segregs = [V86R_DS, V86R_ES, V86R_FS, V86R_GS]
all_v_regs = [all_v_genregs, all_v_segregs]
IF_stat = current interrupt flag status
pre-int_IF_stat = interrupt flag status before the software INT 32/33
other_IF_stat = interrupt status to set for the procedure/int called
INT 32h from protected mode:
PUSHF ; just for show, they are assumed to affect
CLI ; the interrupt flag.
INT 33h from protected mode:
INT 32h from real mode:
Potential DMA problems:
As you know, the DMA controllers in the PC use all physical addresses.
Nothing but the processor itself knows how linear memory is arranged in the
physical memory banks. When paging is disabled, the relationship is very
simple. The linear address is always the same as the physical address. But
when you enable paging, that could get all screwed up. In raw mode and XMS,
you don't have to worry about this since paging is disabled. But under VCPI
and DPMI things are different. You can almost definately count on extended
memory addresses not being consistent with their physical addresses. Low
memory however, will usually map perfectly to its physical addresses. Unless
the program is running in some sort of multitasking system. Then the chances
are slim. The point is that you can't trust DMA much under VCPI and DPMI.
There is something called VDS (Virtual DMA Specification). This is the
recommended way of handling DMA under VCPI and DPMI. I don't know too much
about it now though. Maybe in the future I'll put something in based on that
for DMA stuff. But for now your options are:
) Don't use DMA (Not too hard, except if you wanna do SB output).
) Try to use DMA normally. It will work in raw and XMS, and most probably
under VCPI and DPMI if they are not multitasking your program.
) If you know how to use VDS, feel free... (real mode int calls, remember)
And now to discuss some of the finer points of raw mode:
Which is when this thing does not find anything that is running the system
in V86 mode, and it can do all of its own protection control. This is the best
possible way this thing can be run. The protected mode system runs at a CPL0.
All IRQs and ints are handled through interrupt gates rather than task gates
for speed. There is no task switching done at all, even to go to V86 mode.
Paging is disabled to avoid that extra little bit of overhead. All IRQs are
by default redirected to real mode (not true under some DPMIs). Actually real
mode is V86 mode, still under the control of PMODE. So any IRQs that happen
while a real mode thingy is being processed are taken immediately to protected
mode. Where if you have a handler, it gets control right away. There were some
problems with my old 'START32' code and the A20. I was not waiting for it to
go stable, and was not testing to make sure. This has been fixed. If the A20
fails to enable through the standard AT method or the PS2 method, PMODE will
quit with an error message. All DMA in raw mode is real, since ALL linear
addresses are the actual physical addresses because paging is disabled. The
interrupt flag is real. When you disable interrupts, they are disabled, and
will not screw up anything that might be timing sensitive as well as interrupt
So if you need to do something like time the vertical or horizontal retrace
on your monitor, this is the mode you (or whoever) should be running in.
Is basically raw mode. Except that instead of INT 15h AH=88h, the XMS driver
is used to allocate and lock extended memory. There is only one potential
problem (and this goes for raw mode as well). If something tries to go to
protected mode while in a real mode interrupt call, it will screw up.
Obviously because the system is already in protected mode under the control of
PMODE. This mode switch attempt would usually be the result of the XMS driver
trying to copy memory for something. Or a disk cache that uses extended mem.
This is actually a slightly worse mode to run in than DPMI. True that VCPI
is also basically raw mode. The CPL is 0, and there is nothing scrutinizing
the execution of your code. Paging is enabled, but that is a minor detail.
The problem comes with the way VCPI compatibility works. To call a real mode
interrupt or procedure, PMODE has to pass protected mode control back to the
VCPI server. This comes out to one thing. IRQs that occur in a real mode call
will NOT make it to your protected mode handler. I'm sorry, there is nothing
I can do about this, it's just the way VCPI works. Yes it is possible to go
V86 yourself to service the real mode call. Believe me, I've tried it. But the
problem is that under VCPI systems, most of the real mode stuff (including
DOS) are very dependant on EMS. And if control is not passed back to the VCPI
server, EMS will not function. In fact, most memory managers require that the
server watch for the execution of a certain interrupt from real mode, and
intercept it. The actual interrupt vector in the real mode table might point
into limbo. But as long as the VCPI server is in control, it will be handled
properly. There are a few ways you could work around this:
) Do the IRQ handler in real mode. That way, it will always be called no
matter what is in control. But this seems to defeat the purpose of protected
mode. And if this is a timing critical IRQ, you have a problem because passing
control from a program (PMODE) to the VCPI server to execute the real mode
IRQ callback takes a bit of time. Not a terrible amount, but it is a delay.
) Do the IRQ handler in protected mode, and keep real mode calls to a minimum.
For example, disable all but the critical IRQs to your program. And try to
handle as many as you can in pmode. (You can read the keyboard direct from the
hardware can't you. And you do know how to output FFh to A1h and FDh to 21h).
But remember one thing... When you go to do a real mode call (DOS file call or
something else you can't do yourself). Whatever the hardware cause of your IRQ
will still be active. And if an IRQ occurs in real mode, and there is no real
mode handler for it. Well, you know... So you either put in some valid real
mode handler that may merely set a flag that you have an IRQ to service. Or
disable the source of the IRQ (mask it off at 21h or A1h).
VCPI also has the little problem of the possibility of inconsistent linear
with physical addresses. Which means DMA is screwed. Generally speaking,
unless the VCPI is coming from a multitasking thingy like DesqView, the low
memory addresses will be accurate. As I said, I'm thinking of ways to solve
this little problem. But for now, if you want to do something that requires
DMA. If you know how to work VDS, you can try it with the real mode int calls.
Or you can tell whoever is running your program, that if it doesn't work, to
do a clean boot.
Actually, DPMI is not that bad for what it was designed to be. It could be
a little more consistent across its implementations, but oh well. I am a
game/demo/speed freak however. I don't like the overhead imposed by the paging
and CPL3 (in CPL3, certain instructions have to be emulated by software...
Luckily they are not very common instructions). And multitasking in general
is not that hot when you're trying to do a timing critical action game. But
we're stuck with what we're stuck with. I figure running in real mode under
DPMI is even slower than protected mode.
One really annoying problem with DPMI is that current implementations are
far from perfect. QDPMI 1.01 for example, dies when an IRQ occurs in a pmode
call from a real mode IRQ. DPMI dox say this shouldn't happen, and it doesn't
under Windows 3.1 DPMI implementation. Thus, QDPMI 1.01 is buggy. And who
knows how many other DPMIs out there.
For those of you who know DPMI. I am using the raw mode switch routine to
do cross mode calls with INT32/33. I am ofcourse also saving the state on the
stack. So it is perfectly reenterant. Just as a minor note, DPMI converts the
environment segment in the PSP to a protected mode selector. I convert it back
to the segment after going pmode with DPMI. But since DPMI needs the selector
for the final return to real mode, I put it back then. But you can count on it
being a real mode type segment.
Hmm, another little problem is that I'm not sure how many DPMIs out there
actually reflect IRQs to real mode if they occur in protected mode. Windows
3.1 seems to send them all over as it should. QDPMI however sends IRQ1, but
not IRQ0. And it also doesn't seem to pass IRQs that occur in real mode
through their protected mode handlers. Again, Windows 3.1 does. And from what
I read in the dox, the Windows way is the way DPMI is supposed to be done.
Some misc notes:
) Under VCPI, this thing will map as much extended memory as it can, up to
60M, without allowing the page tables to use up more memory than would leave
LOWMIN. Allocating up to 60M means there is an absolute highest amount of
extended memory under VCPI of 59M (even if there is more available).
) Yes, this thing modifies its own code.
) Before exiting your program, you do NOT need to restore any IRQ vektorz
(pmode that is, if you modified the real mode vektor table, you gotta restore
it). And you do not have to restore the IRQ masks at 21h and A1h (PMODE stores
them before jumping to _main, and restores them before exiting).
) If youre gonna add other segments (16bit right), put them in between CODE16
and CODE32 only if they're small enough to still allow access to CODE32 data
from CODE16. Otherwise put them between CODE32 and CODEEND. You can also just
stick your 16bit code in a CODE16 segment.
) The '_ret' label is provided simply because there are usually a lot of jumps
that go to a RET. Just a minor convenience for myself.
) Yeah the code is a total mess. I did not know many of the workings of VCPI
and DPMI when I started. But hey, at least its functional.
) Remember that upon reaching '_main', interrupts are still disabled. Don't
forget to do the STI.
) I hope you realize with pmode IRQ handlers, you don't have the BIOS to
redirect IRQ9 to IRQ2. So any device that uses IRQ2 will actually be using 9.
) Remember that the INT31 AX=9?? flag functions are only available in pmode.
Go ahead, use the PUSHFs and POPFs in real mode to alter the IF flag... And
any DPMI host that can't handle that properly deserves to crash.
) The one byte INT3 instruction and INTO are treated as exceptions in pmode,
and cause immediate termination (unless you change that in PMODE.ASM). In real
mode they are sent to their real mode handlers.
) I would REALLY suggest not ever switching your stack in protected mode
) This thing was coded under TASM 3.0. So if you have something different,
don't blame me if it doesn't compile.
) For those of you who didn't realize it. There are no memory free functions
for low and high mem because all you have to do is subtract the amount you
want to free from '_lomembase' or '_himembase'.
Heres a list of the vars provided by PMODE to your program:
Low mem base for allocation (first free byte).
Top of low mem (last free byte +1).
High mem base for allocation (first free byte).
Top of high mem (last free byte +1).
Absolute linear address of start of PSP.
Absolute linear address of start of CODE16.
Absolute linear address of start of CODE32 (32bit code offset from this).
Code segment selector.
Data segment alias selector for code.
Data segment starting at absolute 0.
Bitmap for all 16 IRQs (actually 15, but were ignoring 2) of how they should
be redirected to their real mode handlers (this is new to PMODE 2.1232).
System bits, they have the following meanings (if I work on this system,
in the future they may also contain information on DMA maybe).
0-1: 00=raw, 01=XMS, 10=VCPI, 11=DPMI
A pointer to the get IRQ function appropriate for the mode.
The function takes arguments as follows:
BL - IRQ num (0-0fh)
EDX - offset of current IRQ handler
A pointer to the set IRQ function.
BL - IRQ num (0-0fh)
EDX - offset of new IRQ handler to set
And now some 'functions'. Remember, when calling any of them, you should have:
CS=_selcode, DS=ES=FS=_seldata, GS=_selzero, DF=0 (CLD).
Allocate any mem, (first cheks low, then high)
EAX - size requested
CF=0 - memory allocated
CF=1 - not enough mem
EAX - linear pointer to mem or ? if not enough
Allocate some low mem
EAX - size requested
CF=0 - memory allocated
CF=1 - not enough mem
EAX - linear pointer to mem or ? if not enough
Allocate some high mem
EAX - size requested
CF=0 - memory allocated
CF=1 - not enough mem
EAX - linear pointer to mem or ? if not enough
Get amount of free low mem
EAX - number of bytes free
Get amount of free high mem
EAX - number of bytes free
Get status of IRQ mask bit (at port 21h or A1h)
BL - IRQ num (0-15)
AL - status: 0=enabled, 1=disabled
Set status of IRQ mask bit
BL - IRQ num (0-15)
AL - status: 0=enabled, 1=disabled
Exit to real mode
And now, 2.1232:
Aside from a few typos in the doc, PMODE v1.29a was imperfect in another
way. In raw/XMS mode, it executed real mode calls in V86 mode. Locking out
anything in the real mode system that needed to switch to protected mode
temporarily. This could be the XMS manager or a disk cache usually. And under
VCPI it executed all real mode calls by giving control back to the VCPI server
under all conditions. Sometimes this might not be necessary, just to execute
a minor real mode routine maybe. PMODE v2.1232 allows you to control what type
of real mode call is done. Whether the call is executed in V86 mode under the
control of PMODE, or if the system is switched back to actual real mode (or
control passed back to the server in the case of VCPI).
I've added two new interrupts in pmode, INT 34h and 35h. Which have the same
functions as INTs 32h and 33h respectively. The difference is that INT 32h and
33h are the safe way to do the real mode calls. That is they actually return
to real mode (pass control back to the server under VCPI) to handle their
function. INTs 34h and 35h execute the real mode calls by switching the system
to V86 mode and keeping PMODE in control. This has the advantage of keeping in
place all your protected mode IRQ handlers. If you do one of the safe calls,
the entire protected mode system is put on hold while the call executes. INT
32h from real mode still does what it is supposed to. No matter if it executed
from a safe or a V86 real mode call.
The way IRQs are redirected to their real mode handlers can also be
controlled by you. The '_irqmode' word defines how each of the 16 IRQs are
redirected to real mode. Bits 0-15 stand directly for each of the 16 IRQs. A 1
in an IRQ bit means that the IRQ will be redirected safely (switch to actual
real mode). A 0 means the system will switch to V86 mode to do the IRQ handler
under the control of PMODE. '_irqmode' starts out with all bits set (all IRQs
are redirected safely).
PMODE 2.1232 no longer reprograms the interrupt controllers for different
base vectors for the hardware IRQs. PMODE 1.29a did that to relocate the low
8 IRQs from the 386 exception vectors. Since it always did real mode calls in
V86 mode, it could always redirect the new vectors back to their default real
mode values. But 2.1232 can actually return to real mode where the IRQs cannot
be redirected through the pmode IDT. And doing 16 real mode IRQ redirectors
might not be possible if VCPI remaps the vectors itself. And I don't like the
idea of reprogramming the interrupt controllers every time an actual mode
switch occurs (pmode/real, not pmode/V86). The way I did it now came out to be
the best option in my mind. Any exceptions that are overridden by IRQs are
lost. That is, any exception that has been overridden by an IRQ will be sent
to that IRQ erroneously. The usual exceptions that are overridden are 8-0fh.
All of which are terminal faults which you should not get in the first place.
There is one exception that is treated in a special way. Exception 13 is
needed for V86 real mode calls. As a result, it will always be on interrupt
vector 13. Even if an IRQ overrides it (usually 5). But the IRQ will not be
lost. When the handler for exception 13 gets control, it checks whether the
source of an int is an IRQ or an actual exception. For you speed freaks out
there, the check and redirection to IRQ handler in case of an IRQ takes 3
instructions. Too bad, normally all pmode IRQ handlers get control as soon as
physically possible (and appropriate to the system type (DPMI or not)).
If you didn't entirely understand that last paragraph (and I don't blame
you). Let me sum it up. You can get and set and enable and disable all 16 IRQs
as usual. Except that there will usually be a 3 instruction delay on IRQ5 in
getting to its handler. Exceptions 8, 9, 10, 11, 12, 14, and 15 will not make
it to the exception handler. Exception 13 will though.
Ofcourse these are all changes to the raw/XMS/VCPI side of the system. DPMI
will still work exactly as it did in 1.29a. Under DPMI, there is no
distinction between V86 mode and actual real mode. Thus, INTs 32h and 33h are
handled in exactly the same way as INTs 34h and 35h. It comes out to be a more
consistent int/IRQ system:
) Under raw/XMS/VCPI:
) IRQs that occur in protected mode will go directly to their protected mode
handlers, where they can be sent on to their real mode handlers or not.
) IRQs that occur in a real mode safe call (INT32/33) will not make it to
the pmode handlers, but will go directly to their real mode handlers.
) IRQs that occur in a real mode V86 call (INT34/35) will go on to the
pmode handlers, where they can be sent on to their real mode handlers or
) Under DPMI:
) IRQs should ALWAYS go to the protected mode handler first, then may be
sent on to their real mode handlers or not. However, DPMI implementations
out there now are far from perfect, and may not always do that. They might
separate the two IRQ systems (IRQs in real mode go ONLY to their real mode
handlers. IRQs in pmode go ONLY to their pmode handlers, and cannot be
sent on to real mode).
That's it for these minor additions to this doc and code. It should now be
a very reliable system. The design could still be better, but whatever...
PMODE.ASM is still a mess, but it is still functional. If you use this pmode
system is some production, please give me credits somewhere in the thing...
That's all I ask... L8r...
Tran ... (Thomas Pytel) of Renaissance.
Greets to all my friends, and all the k00l coders of the world...
Also, to all demo people (artists, muzicians, other coders, etc...)...
Thanx to Tim Sweeny for the DPMI specs, and Josh Jensen (CyberStrike) for that
You can reach me on Creativity Demo Net or SBCnet as 'Tom Tran'...
Or just call the Sound Barrier: (718)979-6629, (718)979-9406...
Or Internet: [email protected]
I would date this document, except that I don't know todays date. But I think
it is now sometime at the beginning of June 1993... Or maybe not, Who knows...