Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Database

Undocumented Corner


JUL94: UNDOCUMENTED CORNER

QPI: The QEMM-386 Programming Interface

Ralf maintains the MS-DOS Interrupt List, a free collection of information about interrupt calls. He coauthored Undocumented DOS, PC Interrupts, and Network Interrupts (Addison-Wesley, 1994) and is currently a postdoctoral fellow at Carnegie Mellon University's Center for Machine Translation. Ralf can be contacted at [email protected].


Introduction

by Andrew Schulman

In this month's "Undocumented Corner," Ralf Brown examines the private programming interface provided by Quarterdeck's 386 memory manager, QEMM. Questions remain concerning the longevity of third-party memory managers such as Quarterdeck's QEMM and Qualitas's 386MAX. Why should you develop third-party software when DOSand often Windows provide it for free? Because as we've seen with DOS extenders and even disk compressors, there's often room for third-party alternatives.

Whatever the future of third-party memory managers, Ralf's description of the QEMM programming interface remains fascinating. For example, take a look at Figure 1, the output from Ralf's QEMMINFO program. The three maps displayed by this program show the arrangement of the first megabyte (plus a smidgen) of memory. Even though 386s and Virtual-8086 (V86) mode have been around for years, many PC programmers are still surprised to hear that the first megabyte of memory on a PC even has an "arrangement." If you're sitting at the DOS C:\> prompt, the first megabyte is the first megabyte, right? No, not if (like most users today) you're using a 386 memory manager such as QEMM or 386MAX, and/or are running in a DOS box under Windows Enhanced mode. For example, bits of the first megabyte of linear memory in the third column of Figure 1 actually belong to the fourth megabyte of physical memory.

Of course, 386 memory managers try to make V86 mode as invisible as possible. However, it's often necessary (or at least helpful) for programmers to view the V86-mode reality behind the "it looks like real-mode DOS" facade. Ralf shows how to do this for QEMM. Some of these QEMM APIs are also partially implemented (or "spoofed," as Ralf puts it) by other memory managers, so this information is widely applicable.

Besides describing how to use the QEMM interface, Ralf also presents some fascinating background information. For example, he shows how Compaq's original 386 memory manager, CEMM, is the basis for today's EMM386 and QEMM. Ralf also touches on how QEMM patches Windows. I enjoyed his explanation of how V86 managers can hook interrupts and establish interfaces (by hooking I/O ports, for example) in ways likely to surprise those who still think of DOS as a real-mode operating system. Make no mistake, when something like QEMM is loaded, DOS isn't running in real mode, so anything is possible. V86 is hardly like real mode.

Send your comments and suggestions to me via the Undocumented Corner area in the Dr. Dobb's CompuServe forum (GO DDJFORUM), where my ID is 76320,302.

Memory managers support a variety of industry-standard interfaces developed over the years--EMS, XMS, VCPI, DPMI, and VDS. But any programmer who has used the utility programs included with memory managers such as Quarterdeck's QEMM-386 or Qualitas's 386MAX knows that there must be another way to control and retrieve information from these managers beyond the method available through interfaces such as EMS and XMS. How else, for example, could QEMM.COM or Manifest determine how much memory QEMM is using for its own code or mapped ROM?

The answer is that QEMM, 386MAX, and Helix Software Netroom's RM386 support private APIs; Compaq's CEMM and Microsoft's EMM386 also have smaller APIs. These memory managers use quite different methods of invoking their private functions. RM386 provides direct, interrupt-based calls; QEMM, CEMM, and EMM386 have a FAR CALL entry point whose address may be determined in a number of ways, including interrupt calls; and 386MAX uses the 386's ability to trap access to a "magic" I/O port and transfer control to the V86-mode supervisor, 386MAX.SYS. Given the sizes of the APIs involved and the fact that the Netroom API is almost entirely documented (rather a rarity these days), I'll focus here on QEMM.

Through a FAR CALL entry point, QEMM provides functions to change its state, provide statistics, control memory mapping and video virtualization, and support coexistence with Microsoft Windows. In June 1993, Quarterdeck released official documentation on what it calls the QEMM-386 Programming Interface (QPI); however, the majority of this interface is still undocumented.

Finding the QPI Entry Point

Since QPI is based on a FAR CALL entry point, you first have to determine that entry point. There are at least four methods for determining QEMM's private entry point in recent versions: scanning for a signature string; using INT 67h AH=3Fh; using an IOCTL call; and using Quarterdeck's RPCI (Resident Program Communication Interface). The last two were officially, though obscurely, documented in two June 1993 Quarterdeck files called QDMEM.DOC and QPI.DOC. These four methods have accumulated over numerous revisions of QEMM.

Scanning for a signature involves looking for the string "QUARTERDECK EXPANDED MEMORY MANAGER 386" located at offset 14h in the EMMXXXX0 (expanded memory manager) device driver's segment. This is preceded by a WORD containing the entry point's offset in the driver's segment. Prior to QEMM 7.0, this device-driver code--and thus the signature string--was always located in low memory. Beginning with 7.00, this code can be relocated into upper memory, though in 7.01, copies are present in both low memory and, under some circumstances, in an upper-memory block. This method is probably used only to verify the entry point returned by the next method, since software scanning memory for the signature would likely not have known it would wind up in high memory with QEMM 7, and would thus have been "broken" by the new version.

Both Compaq's CEMM and Microsoft's EMM386 support the signature method of getting their own private entry points, using the signatures "COMPAQ EXPANDED MEMORY MANAGER 386" and "MICROSOFT EXPANDED MEMORY MANAGER 386". The similarity between the CEMM and EMM386 private APIs indicates that EMM386 is likely a direct descendant of CEMM. There are also sufficient similarities between CEMM and QEMM to hint that QEMM is derived from Compaq's memory manager as well. All three memory managers have identical functions 00h and 01h in their private APIs.

The INT 67h AH=3Fh method is the simplest, but has also been (incompletely and not entirely correctly) copied by at least two other memory managers. To check for QEMM's presence and simultaneously retrieve the entry point, load AH with 3Fh, CX with 5145h ('QE'), and DX with 4D4Dh ('MM'); then invoke INT 67h. On return, AH will be 00h if the call was successful (QEMM or one of the "spoofing" managers is installed), and ES:DI will contain the address of the entry point.

However, both Micronics' MICEMM and 386MAX provide only a few of the functions on this entry point that QEMM does. One way to distinguish between the real QEMM and other memory managers is to test for the signature string at offset 14h in the entry point's segment; however, MICEMM will provide this same signature if it has been given the DV command-line switch.

Beginning with version 5.0, Quarterdeck introduced a new interface shared by a number of its resident programs, including QEMM, QRAM, VIDRAM, and resident-mode Manifest. The RPCI uses INT 2Fh with a dynamically set function number between C0h and FFh, defaulting to D2h. All RPCI programs share this same multiplex number.

To find the RPCI multiplex number, scan AH values from D2h to FFh and then C0h to D1h, calling INT 2Fh with AX=XX00h, BX=5144h ('QD'), CX=4D45h ('ME'), and DX=4D30h ('M0'). On return, AL will be FFh if the multiplex number is in use. If it is the RPCI rather than some other program, it will be BX=4D45h ('ME'), CX=4D44h ('MD'), and DX=5652h ('VR'). Armed with the multiplex number, check for QEMM by calling INT 2Fh with AH=multiplex number, AL=01h, BX=5145h ('QE'), CX=4D4Dh ('MM'), and DX=3432h ('42'); if QEMM is present, it returns BX=4F4Bh ('OK') and sets ES:DI to the address of the entry point.

The final detection method was also added in 5.0 (though Quarterdeck's QPI.DOC claims that it is first available in 6.0). If you open the character device QEMM386$ with INT 21h AX=3D00h and perform an IOCTL INPUT (INT 21h AX=4402h) of four bytes, the four returned bytes are a FAR pointer to the QPI entry point.

Since both the RPCI and IOCTL methods have been officially (if obscurely) documented, they are the preferred methods for retrieving the QPI entry point, rather than the older and still-undocumented signature and INT 67h methods.

The QPI Functions

After finding the private entry point, QEMM's private functions may be invoked by loading the function number into AH, the subfunction (if any) into AL, setting any other required registers, and calling the far entry point. On return, CF indicates if the function was successful.

Interestingly, using a debugger to examine the actual entry point for QEMM prior to 7.0 reveals nothing more than an INT 2Ch instruction followed by an IRET (in 7.x, there is some additional indirection before the INT 2Ch). But looking at INT 2Ch reveals nothing of QEMM--by default, MS-DOS points it at an IRET instruction.

How then could this possibly be QEMM's interface? On any hardware or software interrupt (such as this INT 2Ch) in V86 mode, the CPU switches to protected mode and calls a protected-mode interrupt handler rather than the handler pointed at by the real-mode interrupt vector table at 0000:0000. So QEMM gets to see all interrupts before applications do, and can filter out those meant for it instead of passing them down to the real-mode handler.

If issued from the QEMM386$ driver's segment, INT 2Ch and most of the other interrupts in the range 22h to 30h are meant for QEMM and will provide various functions to QEMM's real-mode stub; if issued from any other segment, they are passed back down to the "real-mode" (actually V86 mode) handler. So you can't call QPI simply by putting an INT 2Ch in your own code. INT 2Ch happens to be the QPI provider and has remained stable over many versions of QEMM; various other interrupt numbers have been used for EMS, XMS, VDS, and INT 15h AH=87h services, as well as some internal calls.

Table 1 provides an overview of the QPI functions, with the few officially documented calls indicated. Except for function 1Dh, each new version of QEMM has supported all functions supported by all previous versions since 4.23 (the earliest about which information is available). The QPI functions may be roughly classified as follows:

  • Changing QEMM's state: 00h, 01h, 04h, and 05h.
  • Statistics: 11h, 16h, and 17h.
  • Memory mapping: 06h to 0Bh, 0Fh, 18h, and 1Fh.
  • Stealth: 1Dh, 1Eh, 21h, 24h.
  • Video virtualization: 0Dh, 0Eh, 13h.
  • Coexistence with Windows: 1Bh.
  • Desqview support: 1306h, 14h, 1Ch, and 22h.
  • VCPI functionality: 0Ch and 10h.
  • Miscellaneous: 02h, 03h, 12h, 15h, 1Ah, and 20h.
Although the ordering is different, function 0Ch and the various subfunctions of function 10h provide exactly the same calls as the Virtual Control Program Interface (VCPI) and are clearly the precursor of the public specification. In fact, QEMM implements both the VCPI calls on INT 67h AH=DEh and QPI functions 10xxh with calls to the same underlying subroutines. This situation is analogous to the development of the DPMI specification, which in many ways is merely a description of preexisting functionality in the Windows VMM.

Function 1Ah provides access to I/O ports that bypass any protections or virtualization QEMM may have imposed on the I/O port the program wishes to use. Other functions, such as some of the 13xxh calls and function 15h, can affect which I/O ports are virtualized by QEMM. Ports 60h, 64h, 92h, and various VGA ports are normally virtualized by QEMM.

Functions 1Dh, 1Eh, 21h, and 24h support QEMM's patented Stealth feature, which provides more upper memory by hiding the system's ROMs. Stealth remaps memory so that the ROMs appear in the first megabyte only when they are actually required, namely during an interrupt call which reaches a handler in ROM. When QEMM starts up with Stealth enabled, it hooks all interrupts that point into ROM to intercept calls just before they are chained to a ROM. This technique allows Stealth to work with the existing ROMs, in contrast to Helix Software's Cloaking or Novell's DPMS (DOS Protected Mode Services), which require software written specifically to their interface (which involves a small stub in the first megabyte and the true handler running in protected mode). In exchange, Cloaking and DPMS offer the ability to move arbitrary resident programs out of the "real-mode" first megabyte.

QEMM provides some functions specifically for use by Desqview (in combination with which it creates the Desqview/386 multitasker). The close interaction between QEMM and Desqview can be seen in function 14h, which supports Desqview's "protection level" feature. A nonzero protection level for a program enables additional checks that catch many errant programs before they cause a system-wide crash. These functions are not usable by other applications because QEMM makes various Desqview API calls (INT 15h AH=10h--12h) when nonzero protection levels are in effect; in particular, QEMM assumes that it can pop up a Desqview error-message window when it detects a protection violation.

Naturally, the conventional-memory stub of QEMM386.SYS also uses QPI calls. QEMM uses functions 00h and 01h to either temporarily or permanently change its state, such as turning itself off when a laptop goes into sleep mode and then returning to its former state when the laptop resumes. The code supporting the DISKBUF (DB) switch uses function 18h to determine whether there is any need to copy the data being transferred to or from the disk through a fixed buffer allocated by QEMM; if the logical address is identical to the physical address for every byte in the buffer being used by the application, there is no need for the temporary buffer. QEMM v6.0x used functions 1D00h and 1D01h in supporting the suspend/resume interrupt feature of many laptops.

All the 1Bxxh functions are used in some way while operating with Windows:

  • Function 1B00h returns the address of the Global EMM Import Structure.
  • Functions 1B01h and 1B02h implement the Windows V86-mode enable/disable callback provided through INT 2Fh AX=1605h.
  • Functions 1B03h and 1B04h are used by QEMM's conventional-memory stub to notify QEMM's protected-mode code that Windows is starting or terminating.
  • Functions 1B05h and 1B06h are used in patching some of Windows' drivers as they are loaded into memory (in particular, QEMM versions 6 and 7 patch Windows 3.0 Standard mode).
The previous section mentioned that two other memory managers provide the INT 67h AH=3Fh call to get the QPI entry point, but provide only a subset of the QPI functions. MICEMM provides only functions 00h, 02h, and 03h; 386MAX 6 provides only function 0Ch and the various subfunctions of function 10h. (Interestingly, these are precisely the functions which later became the VCPI specification on INT 67h AH=DEh.) The problem with 386MAX's implementation is that the few supported functions use a nonzero return value in AH instead of the carry flag to signal an error or unsupported function.

Other Undocumented Functions

The QPI just described is not the full extent of QEMM's private API. QEMM provides an additional (documented) RPCI function beyond the two already shown. Similar to QPI function 12h, calling INT 2Fh with AH=multiplex number, AL=01h, BX=4849h ('HI'), CX=5241h ('RA'), and DX=4D30h ('M0') will return BX=4F4Bh ('OK') if high memory is present and will set both CX and DX. CX contains the segment of the first memory-control block in the high-memory chain, and DX contains the segment of the owner of any locked-out memory blocks (video or ROMs between the regions of upper memory). In existing versions of QEMM, the value in DX is always the segment of the QEMM386$ device-driver code. Unlike QPI function 12h, this call is also supported by Quarterdeck's QRAM, a memory manager for sub-386 PCs which can use shadow RAM as upper-memory blocks. Quarterdeck's high-memory chain is identical to the DOS 4.x (and greater) memory chain in low memory, with the owner field in the memory-control block set to the string "UMB" for XMS upper-memory blocks and the program name for programs and their environments loaded with LOADHI. Just as with DOS's memory chain, the first byte of each memory-control block except for the last one is 4Dh ('M'). The first byte of the last one is 5Ah ('Z').

Another function provided for Windows compatibility (and supported by EMM386, CEMM, and probably other memory managers) is an IOCTL call on the character device EMMXXXX0, the actual EMS driver. EMM386 and CEMM support multiple subfunctions, but QEMM only supports the one needed to coexist with Windows: subfunction 01h, "Get EMM Import Structure Address." To use this function, Windows opens the device "EMMXXXX0" to get a file handle, then calls INT 21h with AX=4402h, BX=file handle, CX=0006h, and DS:DX pointing at a 6-byte buffer whose first byte has been set to 01h. On return, CF will be clear if the call was successful and the buffer will have been filled as in Table 2. This will be covered in detail in a future DDJ article by Taku Okazaki.

Bugs

Various versions of QEMM contain errors in range checks on function numbers. These cause attempted calls to some unimplemented functions to jump to random locations, generally causing a system crash. Versions 5.11 and 6.00, for instance, will accept INT 4Bh Virtual DMA Specification (VDS) calls with AX=810Dh, even though the highest supported subfunction is 0Ch.

Some Useful Undocumented Functions

Not surprisingly, the officially documented functions are those that are most critical for proper coexistence with QEMM's advanced features, such as Stealth. Even so, a number of other functions also come in handy.

Function 18h (already mentioned in the context of the DISKBUF switch), for example, can tell a program whether it is safe to use DMA directly to a particular buffer. If this function indicates that the specified region of the program's address space is entirely in conventional memory, then the physical addresses needed for DMA are the same as the logical linear addresses the program sees, and the DMA controller can be used without going through VDS to allocate a buffer and copy data to and from it.

The memory allocated to an EMS handle may be made visible in the program's address space using functions 0Bh and 0Fh. A program might thus make 128K of EMS visible at a time, with the limitation that no single EMS handle can be allocated more memory than the size of the address range into which the memory is mapped. This is possibly how Desqview virtualizes CGA graphics: by allocating some EMS and mapping it into the video-memory space.

Either the aforementioned two functions, function 0Ah, or function 1F01h (both of which change the mapping for a single 4K page) could map in the bulk of the memory required by a TSR. This allows a very small stub in the 1-megabyte 8086 address space which maps in the remainder of the TSR, as needed. The main TSR code is then physically located in extended memory, which can be made visible anywhere--on top of video memory, for example. Take care, however, to properly preserve the prior page mappings; this is particularly problematic when using functions 0Bh and 0Fh, since function 0Fh will undo any mappings that might have existed in the affected area before function 0Bh was used.

A Sample Program

To show how to use QPI, I've written QEMMINFO, an information-reporting utility. Like Quarterdeck's own QEMM.COM and Manifest, QEMMINFO displays maps of the memory types, which pages of memory have been accessed, and other information.

The QEMMINFO display (see Figure 1) consists of four columns. The first contains a map of the memory type for each 4K page in the first megabyte. This map, like those generated by QEMM.COM or Manifest, indicates which pages are conventional memory, mappable, high RAM, video memory, excluded, and so on.

The second column displays which pages have been accessed or modified. This map is an extension of the one displayed by QEMM.COM or Manifest, since it also shows the access status of the 16 pages making up the high-memory area (HMA). The display of the first megabyte uses the QPI function 1600h provided for that purpose, but the HMA display extracts the access bits from the page-table entries for the HMA pages.

The third QEMMINFO column displays a map which neither QEMM.COM nor Manifest can generate--the translations between the V86-mode addresses and physical-memory addresses. For each 4K page in the first 1088K (one megabyte plus HMA) of linear address space, QEMMINFO shows which megabyte of physical memory actually appears in that page. This display is created by reading the page number for each of the first 272 pages (1088K) in the current V86 address space and converting the page number into a multiple of one megabyte. A value of 0 indicates that the page shows memory from the first megabyte--conventional memory. (Except in very unusual cases, the physical address is the same as the logical address.)

Figure 1 was generated from a 512K DOS window under Desqview and clearly shows how segments 0400h--87FFh have been mapped to a block of EMS memory in megabytes 3 and 4, while the 96K from 8800h--9FFFh, which are not part of the DOS window, have not been remapped. My PC has 384K of "top" memory just below the 16M mark (as do many Compaq systems), and this memory is used to provide UMBs and shadow RAM, appearing as "F" in the QEMMINFO display.

Additional information includes the VHDIRQ setting, which affects background disk accesses by many advanced disk caches with delayed writes; this item will typically report that the setting is ignored when Stealth is disabled and respected when Stealth is active.

QEMMINFO's memory-mapping display can be used to show that QEMM doesn't actually enable or disable the A20 line, but merely remaps memory to simulate the address wrapping due to A20. When A20 is open (which it will always be when DOS=HIGH), QEMMINFO will show that the HMA is mapped to megabyte 1; when it is closed, QEMMINFO shows that the HMA is mapped to megabyte 0.

One of QEMMINFO's options is to clear the memory-access flags, just like QEMM RESET. The QEMMINFO RESET option also resets the access flags for the HMA, which QEMM RESET won't do. QEMMINFO also allows you to selectively clear either read or write flags as well as both flags for each page; QEMM RESET always clears both flags.

Listing One, QPICALL.ASM, forms the core of QEMMINFO. This module exports the C-callable function QPIcall, which invokes QEMM's private API in the same way the int86 function permits C code to call software interrupts. QEMM.C builds more than three dozen "glue" functions around QPIcall to provide access to most of the QPI; QEMMINFO.C, in turn, builds upon the functions provided by QEMM.C. The combination of QPICALL.ASM and QEMM.C can be used as a generic function library for calling QEMM functions, and it is independent of the sample program QEMMINFO. The full listings are available electronically (see "Availability," page 3), as is the complete calling information known for the private API functions (in QEMMINTS.LST).

Wrapping Up

Though the makers of memory managers have tried hard to make the V86 mode behave just like true real mode, it is not possible (and in many cases not practical) to operate exactly as in real mode. For example, VDS was created to deal with the problem that logical and physical addresses are no longer the same when running under a memory manager. Although the memory manager could virtualize the DMA controller, that would not help bus-mastering cards such as many SCSI host adapters; a set of services which allow aware software to interact with the memory manager is far superior because it can be applied to any hardware, not just that to which the memory manager's programmers have ready access. QEMM's private calls similarly allow QEMM-aware programs to accomplish things that would be possible in real mode but are not otherwise possible in V86 mode under QEMM.

References

Brown, Ralf, ed. INTER40x.ZIP, "MS-DOS Interrupt List," Release 40, April 3, 1994.

Brown, Ralf and Jim Kyle. PC Interrupts, 2nd ed. Reading, MA: Addison-Wesley, 1994.

Quarterdeck Office Systems, Technical Note QDMEM.DOC, "Quarterdeck Memory Driver Interface," and QPI.DOC, "QEMM-386 Programming Interface," June 15, 1993. Available on the Quarterdeck BBS (310-314-3227) in QPI.ZIP.

Figure 1: Sample QEMMINFO display, showing the status of each 4K page in the first 1088K of linear memory: In the first column, M=mapped ROM, period (.)=mappable RAM, H=high RAM, X=excluded memory, V=video, R=ROM, A=adapter,\=split ROM (2K ROM/2K RAM), f=page frame, r=RAMable, C=conventional.

    Memory Types       Memory Accesses      Memory Mappings   QEMM v7.03
 -------------------  -------------------  -------------------  state: ON
  01234567 89ABCDEF    01234567 89ABCDEF    01234567 89ABCDEF  HiRAM from: B100
0 XXXX.... ........  0 WWWWWWWW WWWWWWWW  0 00003333 33333333
1 ........ ........  1 WWWWWWWW WWWWWWWW  1 44444444 44444444  QEMM uses:
2 ........ ........  2 WWW.W... ........  2 44444444 44444444      768 low
3 ........ ........  3 ........ ........  3 44444444 44444444    75626 code
4 ........ ........  4 ........ ........  4 44444444 44444444    39916 data
5 ........ ........  5 ........ ........  5 44444444 44444444    18568 TASKS=
6 ........ ........  6 ........ .....WWW  6 44444444 44444444    20480 MAPS=
7 ........ ........  7 WWWWWWWW WWWWWWWW  7 44444444 44444444   196608 HiRAM
8 ........ ........  8 WWWWWWW. WWWWWWWW  8 44444444 00000000    32768 DMA buf
9 ........ ........  9 WWWWWWWW WWWWWWWW  9 00000000 00000000    16384 ROMs
A VVVVVVVV VVVVVVVV  A WW...... ........  A 00000000 00000000  Unavailable:
B VHHHHHHH VVVVVVVV  B .WW.WWWW WWWWWWWW  B 0FFFFFFF 00000000        0 conv
C ffffffff ffffffff  C R.R..... ........  C 00000000 00000000        0 ext
D HHHHHHHH HHHHHHHH  D WWWWWWWW WWWWWWWW  D FFFFFFFF FFFFFFFF        0 EMS
E HHHHHHHH HHHHHHHH  E WWWWWWWW W.WWWWWW  E FFFFFFFF FFFFFFFF        0 top/shdw
F HHHHHHHH RMRRRHMM  F WWWWWWWW R.RRRWRR  F FFFFFFFF 0F000FFF  Stealth:M
                    H R.RRRRRR R.......  H 11111111 11111111   (2 ROMs)
VCPI: 876 of 1951 pages available      Mapping context: 0141
VHDIRQ setting respected (enabled)
Global EMM Import Structure v1.00 is at physical address 00480758

Table 1: QEMM-386 programming interface functions: (a) General functions; (b) QEMM v5.0+; (c) QEMM v5.1+; (d) QEMM v6.00+; (e) QEMM v6.03+; (f) QEMM v6.04+; (g) QEMM v7.00+.


(a)
       Function    Description
       00h         Get QEMM state (documented)
       01h         Set QEMM state (documented)
       02h         Get segment of unknown data structure
       03h         Get QEMM version (documented)
       04h         Activate QEMM when in AUTO mode
       05h         Deactivate QEMM when in AUTO mode
       06h         Make new mapping context
       07h         Get mapping context
       08h         Set mapping context
       09h         Get linear page number for page table entry
       0Ah         Set linear page number for page table entry
       0Bh         Map 4K pages into memory
       0Ch         Get available memory
       0Dh         Select CRT controller I/O ports to be trapped
       0Eh         Set cursor virtualization callbacks
       0Fh         Unmap 4K pages
       10h         VCPI-precursor interface
       00h         Get protected-mode interface
       01h         Get CPU debug registers
       02h         Set CPU debug registers
       03h         Get machine status word CR0
       04h         Allocate a 4K page
       05h         Free 4K page
       06h         Null function
       07h         Get maximum physical memory address
       08h         Get physical address of page in first megabyte
       09h         Switch to protected mode
       0Ah         Switch back to virtual-86 mode
       11h         Get memory type map
       12h         Get HIRAM chain
       13h         Video-related
       00h         May be VIDRAMEGA
       01h         May be check for modified video memory
       02h         Unknown
       03h         Initialize EGA graphics virtualization
       04h         Shutdown EGA graphics virtualization
       05h         Select portion of EGA graphics to virtualize?
       06h         Set DESQview critical section counter address
       07h         Unknown
       08h         Start/reset CRT controller I/O trapping
       09h         Hercules Graphics Card mode-change support
       0Ah         Virtualize EGA/VGA DAC registers (I/O ports 03C8h/03C9h)
       0Bh         Unknown
       0Ch         Set interrupts to mask during certain Function 13h subfunctions
       0Dh         Map EGA memory at A0000h
       0Eh         Unknown
       0Fh         Reset unknown data
       10h         Copy modified pages to physical video RAM?
       11h         Set unknown flag
       12h         Apparently null function
       14h         Desqview "protection level" support
       00h         Initialize
       01h         Shutdown
       02h         Set protection level?
       03h         Add item to unknown list
       04h         NOP
       05h         Remove item from unknown list
       
       06h         Unknown
       07h         Unknown
       08h         Unprotect?
       09h         Abort program causing protection violation?
       0Ah         Unknown
       0Bh         Unknown
       05h         Set timer channel 0 virtualization

(b)
       Function    Description
       16h         Get/Set memory access status
       00h         get
       01h         set
       17h         Get memory usage statistics

(c)
       Function    Description
       18h         Check whether conventional memory mapped in address range
       19h         Null function
       1Ah         Non-virtualized I/O port access
       00h         Read byte
       01h         Write byte
       02h         Write byte, read byte from following port
       03h         Write word
       1Bh         MS Windows 3.x support
       00h         Get EMM Import Structure address (see Table 2.)
       01h         Disable V86 mode (shutdown EMS and initialize EMM Import record)
       02h         Enable V86 mode (restart EMS and free EMM Import record)
       03h         MS Windows initializing
       04h         MS Windows terminating
       05h         Determine whether program is a driver
       06h         Patch driver
       07h         Bug (fencepost error)
       1Ch         Hardware interrupt V86-mode calldowns
       00h         Disable IRQ0-7 calldowns
       01h         Set V86-mode IRQ0-7 handlers
       02h         Disable IRQ8-15 calldowns
       03h         Set V86-mode IRQ8-15 handlers

(d)
       Function    Description
       1Dh         Stealth interrupts (QEMM 6.x only)
       1Eh         Stealth information (documented)
       00h         Get Stealth configuration
       01h         Get number of Stealth'ed ROMs
       02h         Get list of Stealth'ed ROMs
       1Fh         Page-table manipulation (documented)
       00h         Get page-table entry
       01h         Set page-table entry
       20h         Asynchronous disk access support (documented)
       00h         Get VirtualHDIRQ information
       01h         Set VirtualHDIRQ state
       21h         Stealth support (documented)
       00h         Copy data from Stealth'ed addresses

(e)
       Function    Description
       22h         Desqview/X support
       00h         Get unknown data
       01h         Set unknown value

(f)
       Function    Description
       23h         Unknown (subfunctions 00h, 01h, 02h, and FFh)

(g)
       Function    Description
       24h         ST-DBL support (subfunctions 00h and 01h)

Table 2: EMM import-structure address record.

    Offset      Size    Description
	   
    00h         DWORD   Physical address of EMM import
                        structure
    04h         BYTE    Major version of EMM import
                        structure (01h)
    05h         BYTE    Minor version of EMM import
                        structure (00h for Windows 3.0,
                        0Bh for Windows 3.1)

Listing One


;************************************************************************
;*  QPIcall.ASM     High-level function to call QEMM-386 API            *
;*  (c) Copyright 1994 Ralf Brown                                       *
;************************************************************************
;LastEdit: 2/24/94

    .386

REGS    STRUC
  reg_eax   dd ?
  reg_ebx   dd ?
  reg_ecx   dd ?
  reg_edx   dd ?
  reg_ebp   dd ?
  reg_esi   dd ?
  reg_ds    dw ?
  reg_edi   dd ?
  reg_es    dw ?
  reg_flags dw ?
REGS    ENDS

;========================================================================
_TEXT SEGMENT BYTE PUBLIC 'CODE' USE16  ; forward declaration to ensure
_TEXT ENDS              ; proper segment ordering

_DATA SEGMENT WORD PUBLIC 'DATA' USE16

QEMM_name   db "QEMM386$",0
public QEMM_version
initialized db 0
QEMM_version    dw 0
QPI_entrypt dd ?

_DATA ENDS

;========================================================================
_TEXT SEGMENT BYTE PUBLIC 'CODE' USE16
    ASSUME  CS:_TEXT

;------------------------------------------------------------------------
; int QPIinit(void) ;
; Returns QEMM version (256*major+minor) or 0 if QEMM not loaded
; Destroys AX, BX, CX, DX, and flags

public _QPIinit
_QPIinit proc far
IFDEF __HUGE__
    push    ds
    mov ax,_DATA        ; in huge model, every module gets its
    mov ds,ax           ;   own data segment
ENDIF
    ASSUME  DS:_DATA
    push    es
    push    di
    mov ax,QEMM_version
    cmp initialized,1
    je  short init_done
;; first, try to use QEMM v5+ interface to get entry point
    lea dx,QEMM_name
    mov ax,3D00h        ; try to open QEMM386$ for reading
    int 21h
    jc  instchk_2       ; if open failed, not QEMM v5+
    mov bx,ax
    lea dx,QPI_entrypt
    mov cx,4
    mov ax,4402h        ; IOCTL Input
    int 21h
    pushf
    mov ah,3Eh          ; close the file handle
    int 21h
    popf
    jnc short got_entrypoint
;; if that fails, try the older installation check (which gets spoofed by
;; some other memory managers nowadays)
instchk_2:
    mov ah,3Fh
    mov cx,5145h        ; QE
    mov dx,4D4Dh        ; MM
    int 67h
    cmp ah,0
    mov ax,0            ; assume QEMM not installed
    jne short init_done     ; abort initialization if wrong return

    mov word ptr QPI_entrypt,di
    mov word ptr QPI_entrypt+2,es
got_entrypoint:
    mov initialized,1       ; QPI pointer successfully initialized
    mov ah,3            ; func = get version
    call    QPI_entrypt
    mov ax,0            ; was get-version call successful?
    jc  short init_done     ; if not, this isn't really QEMM
    mov ax,bx
    mov QEMM_version,ax     ; remember QEMM version 
init_done:
    pop di
    pop es
IFDEF __HUGE__
    pop ds
    ASSUME  DS:NOTHING
ENDIF
    ret
_QPIinit endp

;------------------------------------------------------------------------
; int QPIcall(QEMMREG far *inregs, QEMMREG far *outregs) ;
; Returns 1 if successful, 0 if QEMM call failed, and -1 if QEMM not loaded
; Destroys AX, BX, CX, DX, and flags
;
public _QPIcall
_QPIcall proc far
@inregs = dword ptr [bp+18]
@outregs_ofs = 14
@outregs = dword ptr [bp+@outregs_ofs]
    push    es
    push    di
    push    ds
    push    si
    push    bp
    mov bp,sp
IFDEF __HUGE__
    mov ax,_DATA        ; in huge model, every module gets
    mov ds,ax           ;   its own data segment
ENDIF
    ASSUME  DS:_DATA
    cmp initialized,1       ; have we been called before?
    je  short do_call       ; if yes, don't re-initialize
    push    cs
    call    near ptr _QPIinit   ; get QPI entry point
    mov ax,-1
    cmp initialized,1       ; was initialization successful?
    jne short QPIcall_done  ; return AX=-1 (error) if not init'ed
do_call:
    lea ax,call_done        ; build a fake call frame with the
    push    cs          ;   address to which we want to return
    push    ax          ;   after the QPI call
    push    dword ptr QPI_entrypt   ; also store QPI call address on stack
    lds si,@inregs      ; load up the CPU registers from the
    mov eax,[si].reg_eax    ;   input registers structure
    mov ebx,[si].reg_ebx
    mov ecx,[si].reg_ecx
    mov edx,[si].reg_edx
    mov ebp,[si].reg_ebp
    les edi,pword ptr [si].reg_edi
    lds esi,pword ptr [si].reg_esi
    ret             ; invoke the QPI call
call_done:

    push    ebp
    push    ds          ; preserve the registers which get
    push    esi         ;   clobbered in setting up addressing
    pushf               ;   to the output registers structure
    mov bp,sp           ; restore BP to pre-call value
    add bp,12
    lds si,@outregs     ; set up addressing to results buffer
    pop [si].reg_flags      ; store returned register values into
    pop [si].reg_esi        ;   the output registers structure
    pop [si].reg_ds
    pop [si].reg_ebp
    mov [si].reg_eax,eax
    mov [si].reg_ebx,ebx
    mov [si].reg_ecx,ecx
    mov [si].reg_edx,edx
    mov [si].reg_edi,edi
    mov [si].reg_es,es
    cmp ah,84h          ; 386MAX error return?
    mov ax,0
    je  short QPIcall_done  ; if yes, return 0 ("failed")
    test    byte ptr [si].reg_flags,1 ; CF set to indicate error?
    jnz short QPIcall_done  ; if yes, return 0
    inc ax          ; AX <- 1 ("OK")
QPIcall_done:
    pop bp
    pop si
    pop ds
    pop di
    pop es
    ret
_QPIcall endp

_TEXT ENDS

    END

Copyright © 1994, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.