Warning: 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 been warned. ------------------------------------------------------------------------------ Some crap: 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 doc. 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. ------------------------------------------------------------------------------ Intro: 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 'virtual registers'. 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... Selectors: 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. Linear addresses: 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: mov eax,0b8000h sub eax,_code32a 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. Memory (dis)organization: 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). xor al,al mov edi,_lomembase ; fill all available low memory with 0 mov ecx,_lomemtop sub ecx,edi rep stosb mov edi,_himembase ; ditto for high memory mov ecx,_himemtop sub ecx,edi rep stosb 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 fragmentation. 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 the int). ) 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). Exceptions: 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: PUSH all_genregs PUSHF ; just for show, they are assumed to affect CLI ; the interrupt flag. MOV other_IF_stat,pre-int_IF_stat JMP realmode backtopmode: MOV DS,_seldata MOV ES,_seldata MOV FS,_seldata MOV GS,_selzero POPF POP all_genregs IRETD realmode: MOV all_regs,all_v_regs MOV IF_stat,other_IF_stat CALL procedure CLI MOV all_v_regs,all_regs JMP backtopmode INT 33h from protected mode: PUSH all_genregs PUSHF CLI JMP realmode backtopmode: MOV DS,_seldata MOV ES,_seldata MOV FS,_seldata MOV GS,_selzero POPF POP all_genregs IRETD realmode: MOV all_regs,all_v_regs CALL interrupt CLI MOV all_v_regs,all_regs JMP backtopmode INT 32h from real mode: PUSH all_regs PUSHF CLI MOV other_IF_stat,pre-int_IF_stat JMP pmode backtorealmode: POPF POP all_regs IRET pmode: CLD MOV DS,_seldata MOV ES,_seldata MOV FS,_seldata MOV GS,_selzero MOV all_genregs,all_v_genregs MOV IF_stat,other_IF_stat CALL procedure CLI MOV all_v_genregs,all_genregs JMP backtorealmode ------------------------------------------------------------------------------ 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 sensitive. 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. ------------------------------------------------------------------------------ XMS: 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. ------------------------------------------------------------------------------ VCPI: 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. ------------------------------------------------------------------------------ DPMI: 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 yourself. ) 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: _lomembase:dword Low mem base for allocation (first free byte). _lomemtop:dword Top of low mem (last free byte +1). _himembase:dword High mem base for allocation (first free byte). _himemtop:dword Top of high mem (last free byte +1). _pspa:dword Absolute linear address of start of PSP. _code16a:dword Absolute linear address of start of CODE16. _code32a:dword Absolute linear address of start of CODE32 (32bit code offset from this). _selcode:word Code segment selector. _seldata:word Data segment alias selector for code. _selzero:word Data segment starting at absolute 0. _irqmode:word 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). _sysbyte0:byte System bits, they have the following meanings (if I work on this system, in the future they may also contain information on DMA maybe). bits: 0-1: 00=raw, 01=XMS, 10=VCPI, 11=DPMI 2-7: undefined _getirqvect:dword A pointer to the get IRQ function appropriate for the mode. The function takes arguments as follows: In: BL - IRQ num (0-0fh) Out: EDX - offset of current IRQ handler _setirqvect:dword A pointer to the set IRQ function. In: 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). _getmem: Allocate any mem, (first cheks low, then high) In: EAX - size requested Out: CF=0 - memory allocated CF=1 - not enough mem EAX - linear pointer to mem or ? if not enough _getlomem: Allocate some low mem In: EAX - size requested Out: CF=0 - memory allocated CF=1 - not enough mem EAX - linear pointer to mem or ? if not enough _gethimem: Allocate some high mem In: EAX - size requested Out: CF=0 - memory allocated CF=1 - not enough mem EAX - linear pointer to mem or ? if not enough _lomemsize: Get amount of free low mem Out: EAX - number of bytes free _himemsize: Get amount of free high mem Out: EAX - number of bytes free _getirqmask: Get status of IRQ mask bit (at port 21h or A1h) In: BL - IRQ num (0-15) Out: AL - status: 0=enabled, 1=disabled _setirqmask: Set status of IRQ mask bit In: BL - IRQ num (0-15) AL - status: 0=enabled, 1=disabled _exit: 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 not. ) 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). ------------------------------------------------------------------------------ Oh well...: 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 VCPI doc. 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: tran@phantom.com... 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...