# Writing Windows Virtual Device Drivers

HTML version by Werner Zsolt - preview of the first 110 pages

# Chapter 1
The Anatomy of a VxD

Virtual device drivers (VxDs) are not just for people writing drivers for hardware devices anymore than DOS device drivers are used for the same. A VxD is Windows' way of letting you do almost anything you want. If you miss the DOS world - where you have direct access to the hardware, can interface to vital CPU functions, or can take over parts of the operating system - then welcome to VxDs, where you can do the a lot of same under Windows.

A VxD is code and data that runs at ring 0 in 32-bit flat model as part of the Windows 386 virtual machine manager (VMM). In fact, the VMM (WIN3B6.EXE) is primarily a numbex of standard VxDs compounded in a single file. VxDs only operate when Windows runs in 386 Enhanced mode.

VMM is not really a part of Windows; instead, it is a preemptive, multitasking kernel that controls multiple virtual machines. Once VMM has initialized, the Windows Graphical User Interface composed of KRNL386.EXE, GDI.EXE, USER.EXE, and all of the supporting drivers are loaded into the System VM (the initial virtual machine created when VMM is started). However, VMM could easily load COMMAND.COM into the System VM and with the assistance of a VxD and some helper hot-keys, so that you have a multitasking DOS instead of the fancy Windows GUI.

Because VxDs operate at ring 0, the operating-system level of protection, the CPU allows the code to execute any 386 instruction. At higher ring levels, access to memory addresses or I/O ports can be restricted from the VM, allowing the VMM or a VxD to process the exception as it wishes. Of course, certain instructions executed by the VM always cause processor exceptions, but a VxD can simulate the functionality of that instruction for the VM, allowing it to operate as if it has sufficient privilege.

With this power comes responsibility. Although a VxD can play with the Interrupt Descriptor Table (IDT) entries directly, this is something that should probably be avoided. Besides, the VMM provides enough functionality to get as close the IDT as needed, so why reinvent the wheel?

A VxD is always active, unlike any other part of Windows. When a DOS box is running exclusive mode, the primary code executing apart from the DOS box itself includes any VxDs responding to IRQs, code causing faulting instructions, and trapped I/O or page faults in the DOS box.

A VxD is the only program with unobstructed access to the hardware. If a VxD performs I/O to a port, it communicates directly to the physical port, without restrictions. If a VxD owns a hardware interrupt, the VxD receives the IRQ directly from the Virtual Programmable Inten`upt Controller Driver (VPICD), without ring transitions. For example, an interrupt service routine for an non-owned interrupt in a VM sees a virtualized interrupt through events scheduled by the VPICD, whereas a VxD has a more direct path for interrupt servicing. Where code communicating to hardware in a VM may be restricted or slowed by ring transitions and access permission lookups, a VxD is unrestricted and extremely fast.

VxDs operate in 32-bit flat model, the 386 equivalent of small model. All of the segment registers are fixed to the same base address. The CS and DS selector values differ, due to access and execution restrictions (code versus data), but point to the same memory. Because a VxD is in 32-bit flat model, all offsets to code and data are 32-bit; therefore, you can access any part of the address space (4 gigabytes) with just an offset.

A VxD is also given priority on all actions in a VM. A VxD can intercept and/or generate interrupts (hardware or software), trap port I/O, and even restrict access to specific regions of memory. VxDs can determine whether to allow such access to occur, provide simulation, terminate (or nuke) the VM, or simply ignore the request.

Because VxDs utilize the base components of the 80386 chipset, it is important that you have a working knowledge of 386 architecture (footnote:For a good description of 80386/80486 system architecture, see Hummel, Robert L. (1992), PC Magazine Programmer's Technical Reference: The Processor and Coprocessor, Emeryville, CA: Ziff-Davis Press.)

A misbehaving MS-DOS application will usually crash the DOS virtual machine. A misbehaving Windows application may affect the operation of other Windows applications. However, a misbehaving VxD will erash the entire Windows operating system. Because a VxD is part of the WIN386 kernel, the VxD is active during critical processing of the Windows operating system. The smallest, most subtle bug can have devastating effects on the operating system. Thorough testing of virtual device drivers is absolutely necessary. Do not simply test how the VxD operates under stringent configurations; instead, expand your testing to include all possible permutations of enduser system configurations you can design (limited only by a testing hardware budget of course!).

VxDs were originally designed to handle hardware device contention between multiple processes and to translate or buffer data transfers from a VM to hardware devices. When two or more programs attempt to access the same device, some method of contention management must be used. You can use a VxD to allow each process to act as though it has exclusive access to the device. For example, a Virtual Printer Device (VPD) would provide the process with a virtual printer port, and characters written to the port would be written to a print spooler. The VxD would then send the job to the printer when it becomes available. Windows 3.X does not operate in this fashion, but the Win-Link VxD provides this functionality (see Chapter 14 for more information). Another method would be to assign the physical device to only one process at a time, so that when a process attempts to access the device while it is in use, the VxD does not pass the request to the actual hardware, and the process operates as though the hardware did not exist. The Virtual COMM Device (VCD) uses this method.

Recently, the use of VxDs has been expanded to include interprocess communication (demonstrated in the Win-Link example). Some VxDs now also implement a truly virtual device, providing the necessary mechanisms to allow a virtual machine to see a device that may not actually exist in hardware. VxDs can also implement client-server hardware management, providing an interface to a VM that virtualizes I/O to the device and translates this information to commands to be sent across a network to a hardware server.

### The VxD Structure

A VxD has a rather simple structure. It includes a 16-bit real-mode initialization code and data segment, 32-bit initialization code and data segments, 32-bit locked or "non-locked" code and data segments, and a virtual device driver declaration block (exported in the linear executable file as the VxD's DDB). Similar to the "suicide" fence of a DOS terminate-and-stay resident program, the initialization fragments of the VxD are discarded after initialization has been completed. Under Windows 3.x, all 32-bit code and data segments are locked, because the macros provided in the VMM.INC included with the Windows 3.X Device Driver Kit resolve to the same segment definition. However, you should not assume that non-locked segments are the same as locked segments, as these definitions most likely will change in the future. Note the distinction between the two now and save yourself the bug-tracking hassle later.

Real-Mode Initialization Segment

The real-mode initialization segment is a 16-bit code and data segment of the VxD defined

by the VxD REAL MODE INIT SEG macro and is called during VMM's startup. This

allows a VxD to communicate with TSRs or other real-mode procedures to gather and then pass vital information to the VxD's protected mode initialization routines or to fail the load of the VxD or VMM prior to entering protected mode. The term "real-mode initialization" is relative. If you have installed an EMM emulator (EMM386, 386Max, or QEMM), it is likely that the real-mode initialization procedures are invoked in V86 mode and are subject to trapped I/O or other virtualization occurring under these systems. In other words, during real-mode initialization, VMM does not switch the processor to real mode and then call these procedures. Instead, it executes the 16-bit code in the mode configured prior to the startup of VMM (such as invoking WIN.COM).

Note: Due to problems in Windows 3.x, you will need to make sure that your real-mode initialization segment is not exactly 4k, 8k, 12k, or 16k in size. Additionally, real-mode initialization segments greater than 8k (or 12k in Windows 3.1 ) must be a multiple of 4. Real-mode initialization segments cannot be greater than 12k under Wlndows 3.0 or greater than 16k under Windows 3.1. Using code segments greater than these restrictions wiIl cause problems and will eventually hang VMM. These problems were reported on the CompuServe WinSDK forum and confirmed by Developer Support Engineers. Avoid these problems with real-mode initialization by adding the necessary boundary checks in your code.

Protected-Mode Initialization Segment

The 32-bit initialization code and data segments defined by the VxD_ICODE_SEG and VxD_IDATA_SEG macros are present until VMM has completed initialization, at which time they are discarded, freeing the memory used by these sometimes cumbersome pieces of code. These initialization procedures can determine whether it is safe to load the VxD or to bail out prior to further initialization. Thus, the VxD load can fail, the user can be notified, and there will be no memory wasted for the VxD when the VMM completes initialization.

Pageable Data Segments

Because VxD segments are locked by default under Windows 3.x, using data segments to store large amounts of constant data can be a waste of physical memory. One method to resolve this issue is to store the data in the initialization data segment and allocate pageable memory using _HeapAllocate during the Device_Init call. You can copy the data from the initialization segment to this block, and when system initialization has completed, the original data will be discarded.

Device Declaration Block (DDB)

The device declaration block describes the virtual device to the VMM. lt provides a VxD mnemonic, usually a somewhat descriptive title using V as the prefix and D as the suffix, such as VXFERD, suggesting a virtual transfer driver. It also provides a major and minor version, the main control procedure, the device ID number, the initialization order, and control procedures for the V86 or Protected-Mode (PM) API:

    Declare_Virtual_Device VSIMPLED, VSIMPLED_Major_Ver,\
                           VSIMPLED_Minor_Ver,\
                           VSIMPLED_Control_Proc,\
                           VSIMPLED_Device_ID,\
                           Undefined_Init_Order,\
                           VSIMPLED_V86_API_Proc,\
                           VSIMPLED_PM_API_Proc

This declaration dispatches the system control events to the VSIMPLED_Control_proc. This procedure must be declared in a VxD_LOCKED_CODE segment, which handles system event control such as the initialization dispatch, VM control events (creation or suspension of VMs), device focus changes, and system shutdown notifications. Defining it in any other segment will cause problems.

VxD Control Procedure

The control procedure is the main dispatch entry point for the VxD. The initialization messages from VMM are sent to this procedure and then dispatched to the appropriate handlers:

    VXD_LOCKED_CODE_SEG
    ;----------------------------------------------------------
    ; VSIMPLED Control Proc
    ;
    ; Description:
    ;    This is the entry point for system control calls from VMM.
    ;    Control for system messages are dispatched through the
    ;    Control_Dispatch macro in VMM.INC.
    ;----------------------------------------------------------

    BeginProc VSIMPLED_Control_Proc

       Control_Dispatch Sys_Critical_Init, VSIMPLED_Sys_Critical_Init
       Control_Dispatch_Device_Init, VSIMPLED_Device_Init

    EndProc VSIMPLED_Control_Proc

    VXD_LOCKED_CODE_ENDS

VXD_LOCKED_CODE_SEG and VXD_LOCKED_CODE_ENDS are macros that define a segment of 32-bit code in a page-locked segment. Defining this segment as "page-locked" is necessary because the calls are dispatched during critical processing of the VMM. This procedure cannot be included in the initialization code segments, because it would be discarded after VMM completed its startup procedures and system failure would occur when the VMM attempted to dispatch a control message to the VxD during later processing.

The BeginProc and EndProc macros define the beginning and end of a specific VxD entry point. These macros define the procedure name of a VxD, declare it callable by other VxD, align the procedure for "fast-calling", declare the procedure as public for access outside of this module, or additionally define the procedure as an asynchronous service callable from another VxD at interrupt time. The valid parameters to BeginProc macro are PUBLIC, HIGH_FREQ, SERVICE, and ASYNC_SERVICE, and their functionality corresponds to the following table:

    PUBLIC
      Procedure is callable from an external module
    HIGH_FREQ
      Aligns this procedure on a DWORD boundary. Useful
      for procedures called frequently such as hardware
      interrupt procedures or I/O trapping routines.
    SERVICE
      Procedure can be called from another VxD.
      Requires an exported service table.

    ASYNC_SERVICE
      Same as SERVICE, but the VxD routine can be called
      during interrupt procedures. VxD services that do
      not specify this option and are called at
      interrupt time will cause debug traces when using
      the debug version of VMM (WTIN386.EXE). If you
      declare a service to be asychronous be sure that
      it is atomic or can be interrupted while
      processing the request.

Virtual Device ID

A specialized VxD ID may be required if your VxD provides an external V86 or PM API or if your VxD exports services callable by other VxDs. In these cases, you need to request a VxD ID from Microsoft (Internet address vxdid@microsoft.com; CompuServe email, >INTERNET:vxdid@microsoft.com). Microsoft will send you a registration form, which you will need to fill out and return for processing.

If you are replacing an existing VxD, such as the Virtual Comm Device (VCD), you should use the value specified in VMM.INC. The replacement VCD would then have a device ID of VCD_Device_ID. Otherwise, assuming that your VxD does not provide an external API or services, you can use the predefined value of Undefined_Device_ID.

Initialization Order

The initialization order of the DDB defines the load order of the virtual device drivers. VMM will load and initialize the VxDs in the order specified by the load-order values. For most secondary virtual device drivers, the Undefined_Init_Order equate is sufficient. If your VxD requires other VxDs to be present and initialized prior to calling your initialization procedures, you need to specify a load order constant here.

API Entry Procedures

API entry procedures are invoked when a VM running in either protected mode or V86 mode calls the VxD's entry point. A VxD entry point is available to VMs only when the VxD defines the necessary entry procedures in the DDB. These procedures are discussed in depth in Chapter 4.

### Virtual Device Initialization

System Critical Initialization (Sys_Critical_Init)

VMM dispatches a Sys Critical Init message to all VxDs in the order defined by the Initialization Order values of the VxDs. During Sys_Critical_Init, interrupts are disabled, and VxDs perform system-critical initialization such as memory mapping and hooking V86 interrupts or faults. Because interrupts are disabled, you should keep the initialization during this message to a minimum.

Sys_Critical_Init may also be used to hook your VxD in front of certain handlers, such as GP fault or NMI processing. Sys_Critical_Init is an optional procedure, and you should only define this procedure if you have specific initialization to perform. Below is a sample Sys_Critical_Init handler as used in the VSIMPLED Sample:

    ;-----------------------------------------------------
    ; VSIMPLED Sys_Critical_Init
    ;
    ; Description:
    ; On entry, interrupts are disabled. Critical initialization
    ; for this VxD should occur here. For example, we can read
    ; settings from VMM's cached copy of the SYSTEM.INI and set up
    ; our VxD as appropriate.

    ; This procedure is called when the VSIMPLED Control Proc
    ; dispatches the Sys Critical_Init notification from VMM.
    ; We can notify VMM of failure by returning with carry set
    ; or carry clear will suggest success.

    BeginProc VSIMPLED_Sys_Critical_Init

      clc
      ret

    EndProc VSIMPLED_Sys_Critical_Init

Device Initialization (Device Init)

Device initialization is where non-system critical initialization of your VxD is performed. For example, you may want to install I/O trap handlers, virtualize an interrupt using VPICD services, or allocate a VM control block. Returning from this procedure with carry set will fail the loading procedure of the VxD. If everything has passed initialzation, clear the carry flag and return.

### The VSIMPLED Sources

Using the information provided in this chapter, we are ready to create our first VxD. This skeleton VxD declares a DDB, and defines a control procedure supporting the two system initialization messages (Sys_Critical_Init and Device_Init):

    MAKEFILE

    !IFDEF DEBUG
    DEFS=-DDEBUG
    !ENDIF

    .asm.obj:
       masm5 -p -w2 -Mx $(DEFS) $*;

    .asm.lst:
       masm5 -1 -p -w2 -Mx $(DEFS) $*;

    OBJS =        vsimpled.obj

    all:          vsimpled.386

    vsimpled.obj: vsimpled.asm

    vsimpled.386: vsimpled.def $(OBJS)
         link386 /NOI /NOD /NOP /MAP @<<
    $(OBJS)
    vsimpled.386
    vsimpled.map

    vsimpled.def
    <<
             addhdr vsimpled.386
             mapsym32 vsimpled

    clean:
             del *. 386
             del *. obj
             del *. map
             del *. sym

    VSIMPLED.ASM

      page 60, 132
    ;***************************************************************
    ;  title VSIMPLED - A simple virtual device driver example
    ;***************************************************************

    ;****************************************************************
    ; Functional Description:
    ; Provides a minimal virtual device driver interface.
    ;****************************************************************

      .386p

    ;================================================================
    ;         I N C L U D E S    &    E Q U A T E S
    ;================================================================
      .XLIST
      INCLUDE VMM. Inc
      INCLUDE Debug.Inc
      .LIST

    VSIMPLED_Major_Ver equ 01h
    VSIMPLED_Minor_Ver equ 00h
    VSIMPLED_Device_ID equ Undefined_Device_ID

    ;================================================================
    ;     V I R T U A L    D E V I C E    D E C L A R A T I O N
    ;================================================================

    Declare_Virtual_Device VSIMPLED, VSIMPLED_Major_Ver,\
                           VSIMPLED_Minor_Ver, VSIMPLED_Control_Proc,\
                           VSIMPLED_Device_ID, Undefined_Init_Order,,,

    ;================================================================
    ;                         I C O D E
    ;================================================================

    VxD_ICODE_SEG

    ;----------------------------------------------------------------
    ; VSIMPLED Sys Critical_Init
    ;
    ; Description:
    ;      On entry, interrupts are disabled. Critical initialization
    ;      for this VxD should occur here. For example, we can read
    ;      settings from VMM's cached copy of the SYSTEM.INI and act
    ;      set up our VxD as appropriate.
    ;
    ;      This procedure is called when the VSIMPLED_Control_Proc
    ;      dispatches the Sys_Critical_Init notification from VMM.
    ;
    ;      We can notify VMM of failure by returning with carry set
    ;      or carry clear will suggest success.
    ;---------------------------------------------------------------

    BeginProc VSIMPLED_Sys_Critical_Init

         Trace_Out "VSIMPLED: Sys_Critical_Init"

         clc
         ret

    EndProc VSIMPLED_Sys_Critical_Init

    ;---------------------------------------------------------------
    ; VSIMPLED_Device_Init
    ;
    ; Description:
    ;         This is a non-system critical initialization procedure.
    ;         IRQ virtualization, I/O port trapping and VM control
    ;         block allocation can uccur here.
    ;
    ;         Again, the same return value applies...
    ;         CLC for success, STC for error notification.
    ;---------------------------------------------------------------

    BeginProc VSIMPLED_Device_Init

      Trace_Out "VSIMPLED: Device_Init"

      clc
      ret

    EndProc VSIMPLED_Device_Init

    VxD_ICODE_ENDS

    VxD_LOCKED_CODE_SEG

    ;===============================================================
    ;           N O N P A G E A B L E         C O D E
    ;===============================================================

    ;---------------------------------------------------------------
    ; VSIMPLED Control Proc
    ;
    ; DESCRIPTION:
    ;        Dispatches v MM control messeges to the appropriate handlere.
    ; ENTRY :
    ;        EAX = Message
    ;        EBX = VM associated with message
    ;
    ; EXIT :
    ;        Carry clear if no error (or if not handled by the VxD)
    ;        or set to indicate failure if the message can be failed.
    ;
    ; USES :
    ;        All regigters.
    ;---------------------------------------------------------------

    BeginProc VSIMPLED_Control_Proc

              Control_Dispatch Sys_Critical_Init, VSIMPLED_Sys_Critical_Init
              Control_Diapatch Device_Init, VSIMPLED_Device_Init

              clc
              ret

    EndProc VSIMPLED_Control_Proc

    VxD_LOCKED_CODE_ENDS

    END

    ;---------------------------------------------------------------
    ; End of File: vsimpled.asm
    ;---------------------------------------------------------------

    VSIMPLED.DEF

    LIBRARY     VSIMPLED
    DESCRIPTION 'W386 VSIMPLED Sample Device (Version 3.10)'
    EXETYPE     DEV386

    SEGMENTS
              _LTEXT PRELOAD NONDISCARDABLE
              _LDATA PRELOAD NONDISCARDABLE
              _ITEXT CLASS 'ICODE' DISCARDABLE
              _IDATA CLASS 'ICODE' DISCARDABLE
              _TEXT  CLASS 'PCODE' NONDISCARDABLE
              _DATA  CLASS 'PCODE' NONDISCARDABLE

    EXPORTS
              VSIMPLED_DDB @1

### Debugging the VSIMPLED VxD

Before entering the Windows environment, you need to copy the debug version of the VMM into your system directory. The Windows 3.1 Device Development Kit contains this special version. There are many reasons to use this version of the VMM when developing your VxDs:

- VMM displays debug traces when unexpected events occur. These messages help you track down problems with your VxD. You will know that you have a "clean" VxD, When the system does not display these messages while running with your VxD installed.
- VMM includes a special debugging trace log that logs faults, device calls, and interrupt counts. These logs help to pinpoint the exact cause of a failure in your VxD.
- Special services are enabled for debuggers to display VMM's execution state, VM information such as event lists, interrupt vector tables, the VM execution state, and other critical information not available in the retail release of VMM.

Using the debug version of WIN386.EXE requires either a serial terminal on COM1 or COM2 and WDEB386, the 386 debugger included with the Windows Software Development Kit and Device Driver Development Kit, or a Windows Enhanced Mode Debugger such as Soft-ICE/WTM available from NuMega.

The VSIMPLED device displays trace information at each initialization phase. Before the GUI starts, break into the debugger by using the appropriate hot-key (Control-D for SoftICE/W or a Control-C from the terminal keyboard for WDEB386) and unassemble the VSIMPLED_Sys_Critical_Init procedure:

    Registration # SIW012345
    :ALTSCR OFF
    :LINES 50
    :iihere on
    :wc
    :x
    VSIMPLED: Sys_Critical_Init
    Break Due to Hot Key
    D800:00001A20 MOV CX,0040
    :u VSIMPLED_Sys_Critical_Init
    VSIMPLED_Sys_Critical_Init
    0028:8029478C CALL [Log_Proc Call]
    0028:80294792 PUSHFD
    0028:80294793 PUSHAD
    0028:80294794 MOV ESI,VSIMPLED DDB+38(80OFEA2C)
    0028:80294799 CALL [Out_Debug_String]
    0028:8029479F POPAD
    0028:802947A0 POPFD
    :g
    VSIMPLED: Device_Init
    VMM Version 03.10 - Build Rev 00000103
    Break Due to Hot Key
    0028:800110A6 CMP AX,0030
    :u VSIMPLED_Sys_Critical_Init
    VSIMPLED_Sys_Critical_Init
    0028:8029478C INVALID
    0028:8029478E INVALID
    0028:80294790 INVALID
    0028:80294796 INVALID
    0028:80294798 INVALID
    :g

Re-enter the debugger when the Windows GUI has completed initialization and unassemble the same procedure. You will find that the address is invalid because the initialization code and data segments were discarded after the device initialization was completed.

For more information on VMM's debugging services and debugging techniques, see Chapter 11, "Using the Debugging Services."

# Chapter 2 - The Virtual Machine Manager

The Virtual Machine Manager is a single-threaded, non- reentrant, preemptive multitasking, event-driven operating system. This operating system is often referred to as "WIN386" or "VMM." VMM provides an interface layer to VxDs for event scheduling, memory management, descriptor table management, and other vital system services.

The VMM creates, runs, and destroys virtual machines (VMs). On startup, the VMM creates the System VM for the Windows GUI. The System VM interfaces to the SHELL VxD in VMM to create new virtual machines or DOS boxes - each new VM starts operation in Virtual 8086 (V86) mode. Because a VxD is a part of the VMM, it runs within whatever VM is active when it is called. Consequently, when a DOS VM calls a VxD, the VxD runs in protected mode in the context of the calling VM.

To write a VxD, you must have a clear understanding of how the VMM works

### Event Processing

The execution path of VMM is driven by event lists. Event lists are linked lists of scheduled event procedure calls. These scheduled calls are created by the WIN386 system as the result of faults, interrupts, or specific VxD requests.

There are two types of event lists: the global event list and VM-specific event lists. The global event list is the event list for the VMM. As each VM is created, VMM creates an event list for specific events of that VM. Prior to returning control to a VM, VMM processes any events in the global event list, any pending NMI events (a special form of a global event), and then the VM event list as shown in Figure 2.1. Note that VM-specific events are only processed for the active VM.

##### Figure 2.1
VMM Event Processing Order

When a VxD processes an event, it has complete control of the system. Because extended event processing reduces the system performance, the event procedure must be fast and avoid lengthy processing. Returning from the event allows VMM to continue the processing of the event list.

When VM events are created, the execution priority of the VM can be adjusted. This is also known as a "boost." The boost can be temporary (automatically removed by VMM) or can be specifically removed by the VxD when all of the necessary event processing for that VM is completed. The execution priority of a VM is used by the primary scheduler (execution priority scheduling) to determine the active VM. (See the section on Scheduling for more detail.)

When all events from the global event list and active VM event list have been processed, the primary scheduler walks the VM list searching for the VM with the highest execution priority. The VM with the highest execution priority becomes the active VM. VMM returns to the active VM until it is reactivated by interrupt or fault processing. When a VxD is processing an event, asynchronous VMM services may be called and new events generated as the result of IRQ handling. When an IRQ is generated by the PIC, the handlers installed into the IDT by VPICD (Virtual PIC Device) call the Hw Int Proc for the IRQ. During non-virtualized IRQ processing, the default VPICD handlers then schedule VM events for interrupt simulation. VxDs must be aware that VPICD handles interrupts while events are processed, and disabling interiupts during event processing may be necessary for VxDs performing critical hardware processing. IRQ handling is detailed in Chapter 7.

Because a VM does not continue executing until all events in the global event list and VM event list have been dispatched, the results of event processing in a VxD can become stacked in the VM. For example, a VxD processing a global timeout event may schedule an asynchronous call to a procedure in a VM. During this processing, the VxD may request that the VM resume execution. Before resuming execution of the VM, VMM processes any remaining events on the event list. If this includes an interrupt event scheduled by VPICD, the VxD may request a simulated interrupt in the VM. Finally, when VMM returns to the VM, the actual results of the event processing are executed in reverse order as pushed onto the VM's stack: The interrupt service is be processed first, before the callback scheduled by the timeout event.

### Scheduling

There are two schedulers used in the WIN386 system: the primary scheduler and the secondary, or time-slice scheduler. The primary scheduler (execution priority scheduler) selects the active VM based on highest execution priority of the non-suspended VMs. A VM will remain active until a higher priority VM is found in the queue.

When a VM is boosted, its order is changed in the queue. Normally, the active VM has a boost of Cur_Run_VM_Boost in as its execution priority. Devices that require a VM to become active as the result of I/O or interrupt processing may use a device boost of High_Pri_Device_Boost to force the VM to become active. This is typically implemented using the Call_Priority_VM_Event service. Using this service, VMM adjusts execution priority of the specified VM, and a callback is notified when the VM has activated. The VxD can then continue its processing for the VM. Figures 2.2 and 2.3 demonstrate the effect in the scheduling queue of changing the execution priority. The following code example demonstrates the technique of boosting a VM's execution priority:

    // Example of calling priority VM event in 'C'

    DWORD             dwEventHandle ;
    static PEVENTPROC pEventProc = NULL ;

    if (!pEventProc)
      pEventProc =
          vmmwrapThunkEventProc( BoostEventProc ) ;
    dwEventHandle =
      vmmCallPriorityVMEvent( hVM, High_Pri_Device_Boost,
                                PEF_Wait_Not_Crit, dwRefData,
                                pEventProc, 0 ) ;

    // Boost Event Proc - handler for VM event callback

    VOID BoostEventProc( DWORD hVM, DWORD dwRefData, PCRS_32 pCRS )
    {
      TRACEMSGPARAM( "VM #EAX is now active\r\n", hVM );
    } // end of BoostEventProc()

##### Figure 2.2
Scheduler queue prior to device boost

##### Figure 2.3
Scheduler queue after device boost.

The secondary scheduler (or time-slice scheduler) adjusts the execution priority for VMs for a period of time based on the background and foreground priorities set for each VM. The secondary scheduler determines which VM to boost based on the time-slice priorities specified in the.PIF file of a DOS application.

The time-slice priorities are also used to determine how long the execution priority of a VM will be boosted. The boost value is constant - that is, changing the time-slice priorities does not affect the amount of execution priority boost that a VM receives. When the next time-slice occurs and the VM's time-slice period has been exhausted, the VM is unboosted and the next VM in the time-slice scheduler's queue receives the execution priority boost.

The time-slice scheduler's execution priority boost for a VM is low compared to other high-priority event processing. Thus, the high-priority VM remains active until it is unboosted or until another VM of higher priority is found in the primary scheduler s queue.

### Services and Dynalinking

VMM, its component VxDs, and third-party VxDs can provide services callable by other VxDs. The calls to these services are resolved at runtime by the dynalink mechanism. The VxDCall and VMMCall macros provided by VMM.INC are expanded in code as follows:

    int    Dyna_Link_Int
    dd     VxD_ID SHL 16 + VxD_Service

    < Clean up C parameters>

When the IDT dispatches the software interrupt to VMM, the dynalink routine patches the int 20h and the following dword with a direct call to the VxD service handler. Stack parameters to the service are passed with the 'C' calling convention. VxDJmp is similar to VxDCall, with the exception that stack parameters cannot be used and the resulting code jumps to the VxD service handler, avoiding the extra cycles involved when the service call is followed by a return instruction.

Under some 386 'C' compilers, you cannot generate the appropriate in-line assembly instructions to duplicate this interface and/or load the registers required by the service. Consequently, you need to use.ASM thunks to provide a 'C' callable interface. Similarly, replacement VxDs (for example, a replacement VCD) may require register-parameter passing, and an assembly language front-end is necessary. The VDDVGA sample was written in 'C' and demonstrates the techniques required to interface to some of these services.

Note: For more information on writing VxDs in 'C' see Chapter 10.

### Critical Sections

The primary scheduler implements a single critical section using the Begin_Critical_ Section and End_Critical_Section services in VMM. The critical section can be claimed on behalf of a VM by a VxD. The critical section is most commonly used when calling MS-DOS or BIOS interrupt handlers because these real-mode code pieces are not reentrant. However, the critical section can also be used for other drivers or TSRs loaded prior to starting WIN386.

Note that the critical section does not halt scheduling of VMs; that is, other VMs may be scheduled while the critical section is claimed. If a second VM attempts to claim the critical section, the VM is suspended until the current critical section owner has released the critical claim. When a VM claims a critical section, the execution priority of the VM is adjusted by the predefined value of Critical_Section_Boost; the execution priority is restored when the critical section is released.

The critical section allows a VxD to prevent multiple VMs from entering the same piece of code. If two VMs are executing and interfacing to the same TSR and the TSR can not handle multiple VMs calling simultaneously because it maintains global non-instanced data for the specific procedure, a VxD may wrap the V86 interrupt chain and claim a critical section prior to reflecting the interrupt to the VM. It releases the critical section when the interrupt has returned. This prevents two VMs from simultaneously entering the same interrupt routine in the TSR. The following example demonstrates hooking the V86 interrupt, watching for a specific signature, and claiming a critical section around the API call:

    ;
    ; Hook the V86 interrupt (Int 60h)
    ;
    BeginProc VSIMPLED_Sys_Critical_Init

      pushad
      mov eax, 60h
      mov esi, OFFSET32 VSIMPLED_Int60_Hook
      VMMCall Hook_V86_Int_Chain
      popad
      clc

      ret

    EndProc VSIMPLED_Sys_Critical_Init

    ;
    ; Watches for the API signature. If found, claims
    ; a critical section and hooks the "back-end".
    ;

    BeginProc VSIMPLED_Int60 Hook, High Freq

      cmp [ebp.Client_AX], 4257h
      jne SHORT VIH_Exit

      pushad

      ;
      ; Claim the critical section but allow interrupts
      ; to be serviced if we block.
      ;

      mov ecx, Block_Svc_Ints or Block_Enable_Ints
      VMMCall Begin_Critical_Section

      ;
      ; Hook the back end of the Int60 call.
      ;

      xor eax, eax
      xor edx, edx
      mov esi, OFFSET32 VSIMPLED_Int60_Complete
      VMMCall Call_When_VM_Returns

      popad

    VIH_Exit:
      stc                           ;  always chain
      ret

    EndProc VSIMPLED_Int60_Hook

    ;
    ; Completes the Int 60h handling by releasing the
    ; critical section and returning.
    ;

    BeginProc VSIMPLED_Int60_Complete, High_Freq

      VMMCall End_Critical_Section
      ret

    EndProc VSIMPLED_Int60_Complete

### Suspending VMs, Resuming VMs, and Semaphores

VMM provides services to suspend and resume the execution of a VMs (Suspend_VM and Resume_VM). It is not possible for a VxD to suspend the execution of the System VM because VMM prevents this, but all other VMs can be suspended. Also, if a VM is the critical section owner, suspending the VM is not valid, and consequently the suspend call will fail.

When it suspends a VM, a VxD causes the VM to be removed from the active queue and added to the inactive queue. The primary scheduler does not activate this VM until it is resumed. If a VxD suspends a VM that is currently active, an immediate task switch occurs and the execution path in the VxD halts at the Suspend VM call. To see this, try using debug traces to "wrap" the call to the Suspend_VM service. The debug trace in front of this call displays and a task switch occurs as when the active VM is placed in the inactive queue (the VM with the highest priority becomes the active VM), after which global events and VM events are processed. When the suspended VM has been resumed, the debug trace after the Suspend_VM call in the VxD is displayed, as the execution path of the VM continues.

VMM provides services (Wait_Semaphore and Signal_Semaphore) that allow VxDs to block and unblock VMs, based on events occurring in the VxD that decrement a token count by signaling the semaphore. A VM waiting on a semaphore resumes when the token count is less than or equal to zero. Additionally, it is possible to specify that certain events can be processed in a blocked VM. The following list describes the flags associated with the Wait_Semaphore service:

    Block_Enable_Ints     Forces interrupts to be enabled and
                          serviced even if interrupts are
                          disabled in the blocked v M.
                          (Only relevant if Block_Svc_Ints or
                          Block_Svc_If_Int_Locked specified.)

    Block_Poll            Causes the primary scheduler to not
                          switch away from the blocked VM
                          unless another VM has higher priority.

    Block_Svc_Ints        Service interrupts in the VM even if
                          the virtual machine is blocked.

    Block_Svc_If_Ints_Locked Same as Block_Svc_Ints with the
                             additional requirement that the
                             VMStat_V86IntsLocked flag is set.

Figure 2.4 shows the flow control possible using the semaphore services. For example, a VxD can signal or wait on semaphores in response to API calls from both the V86 VM (DOS application) and from the PM VM (Windows Application), allowing the VxD to control a data transfer channel through the VxD.

##### Figure 2.4
Possible design of semaphore implementation.

### Asynchronous Services

Because VMM is non-reentrant, only a subset of VMM's API is available when a VxD is entered through an asynchronous interrupt. Services in a VxD can be declared AsYNC and are available at interrupt time. If your VxD declares such a service, it may call only asynchronous services. The following tables list all the asynchronous services that may be called in interrupt handlers:

    Asynchronous VMM Services

    Begin_Reentrant_Execution            Get_Time_Slice_Info
    Call_Global_Event                    Get_VM_Exec_Time
    Call_Priority_VM_Event               Get_VMM_Reenter_Count
    Call_VM_Event                        Get_VMM_Version
    Cancel_Global_Event                  List_Allocate
    Cancel_VM_Event                      List_Attach
    Close_VM                             List_Attach_Tail
    Crash_Cur_VM                         List_Deallocate
    End_Reentrant_Execution              List_Get_First
    Fatal_Error_Handler                  List_Get_Next
    Fatal_Memory_Error                   List_Insert
    Get_Crit_Section_Status              List_Remove
    Get_Crit_Status_No_Block             List_Remove_First
    Get_Cur_VM_Handle                    Schedule_Global_Event
    Get_Execution_Focus                  Schedule_VM_Event
    Get_Last_Updated_System_Time         Signal_Semaphore
    Get_Last_Updated_VM_Exec_Time        Test_Cur_VM_Handle
    Get_Next_VM_Handle                   Test_Debug_Installed
    GetSetDetailedVMError                Test_Sys_VM_Handle
    Get_System_Time                      Update_System_Clock
    Get_Sys_VM_Handle                    Validate_VM_Handle

    Asynchronous Debugging Services

    Clear_Mono_Screen                    Is_Debug_Chr
    Debug_Convert_Hex_Binary             Log_Proc_Call
    Debug_Convert_Hex_Decimal            Out_Debug_Chr
    Debug_Test_Cur_VM                    Out_Debug_String
    Debug_Test_Valid_Handle              Out_Mono_Chr
    Disable_Touch_1st_Meg                Out_Mono_String
    Enable_Touch_1st_Meg                 Queue_Debug_String
    Get_Mono_Chr                         Set_Mono_Cur_Pos
    Get_Mono_Cur_Pos                     Test_Reenter
    In_Debug_Chr                         Validate_Client_Ptr

    Asychronous VxD Services

    BlockDev_Command_Complete            VPICD_Get_Complete_Status
    BlockDev_Send_Command                VPICD_Get_IRQ_Complete_Status
    DOSMGR_Get_DOS_Crit_Status           VPICD_Get_Status
    PageFile_Read_Or_Write               VPICD_Phys_EOI
    VPICD_Call_When_Hw_Int               VPICD_Physically_Mask
    VPICD_Clear_Int_Request              VPICD_Physically_Unmask
    VPICD_Convert_Handle_To_IRQ          VPICD_Set_Auto_Masking
    VPICD_Convert_Int_To_IRQ             VPICD_Set_Int_Request
    VPICD_Convert_IRQ_To_Int             VPICD_Test_Phys_Request
    VPICD_Force_Default_Behavior         VTD_Update_System_Clock
    VPICD_Force_Default_Owner

# Chapter 3
Memory Management

The VMM implements two memory managers. The V86MMGR VxD manages memory for V86-mode applications, including Expanded Memory Specification (EMS) and Extended Memory Specification (XMS), and the Memory Manager (MMGR) provides services such as GDT/LDT management, global heap management, physical memory management, protected mode address translation, and V86 page management, including V86 address mapping and allocation.

If you are writing a virtual display device or writing a VxD for a device requiring contiguous physical memory (such as devices using DMA transfers), you need to implement some form of memory management. Additionally, certain memory management implementations in your VxD such as memory mapped devices may require knowledge of the way the 80386 implements memory management using page tables.

### VMM Memory Mangement Services

All memory in the system is allocated by the memory manager. This includes large allocations for VMs as well as a small heap available to VxDs requiring dynamic memory allocation.

While each VM has its own memory and linear address space, any VM that is presently ecuting is also mapped into the first megabyte of the linear address space. The MMGR rforms this mapping on each task switch by updating the page tables to reflect the new

ping of the lower linear address space. Figure 3.1 shows a possible memory configuration with multiple VMs.

##### Figure 3.1
VMM Memory Map

The MMGR can provide per-VM data to a VxD. When a VxD initializes, it can request a number of bytes of control block data. The MMGR returns an offset from the VM handle, which is reserved for your VxD's control block area at the same offset in each VM control block. The following 'C' code sample shows how a VxD control block is allocated and assigned a pointer.

    // Allocate part of VM control block for VDD usage

    if (NULL = (dw Vid C B Off =
      vmmAllocateDeviceCBArea( sizeof( VDDCB ), 0 )))
    {
      vmmDebugOut( "VDD ERROR: Could not allocate control\
    block area:\r\n" ) ;

      vddFatalMemoryError() ;
      return ( FALSE ) ;
    }

    pSysvMCH=(PvDnCB) (hVM + dwVidCBoff);

VMM allocates a control block containing vital information for each VM and is located at the zero offset from the VM handle. VMM's control block has the following structure: //------------------------------------------------------------- // VM control block structure (VMM) //------------------------------------------------------------- typedef struct tagVMMCB { DWORD CB_VM_Status ; DWORD CB_High_Linear ; DWORD _Client_Pointer ; DWORD CB_VMID ; } VMMCB, *PVMMCB ;

Thus, given a VM handle, a VxD can obtain the VM's ID using the following method:

    DWORD dwVMID ;

    dwVMID = ((PVMMCB) hVM) -> CB_VMMID ;

The low memory (inten-upt vector table, BIOS & DOS data, and so forth) for each VM is located in high linear address space along with the rest of the memory for that VM. It is preferable to access VM memory using the high linear addresses, as these will not change. If a task switch occurs during memory reads or writes to a low linear address, your VxD may access an invalid address.

### Translation Services

The MMGR provides an address translation API. While registers are preserved when making a ring transition between V86 mode and flat 32-bit mode, a pointer using a realmode segment and offset is meaningless in protected mode. A number of macros in VMM.INC use MMGR services to convert the parameters in the client VM's registers automatically.

Client_Ptr_Flat is a macro that sets up a call to the Map Flat_service:

    Client_Ptr_Flat       esi, DS, DX

which expands to:

      push eax
      mov ax, Client DS * l00h + Client DX
      VMMCal 1 Map_Flat
      mov es i, eax
      pop eax

The actual address mapping magic is performed in VMM's Map_Flat service. The following algorithm is used by Map_Flat to map the pointer to a 32-bit flat offset:

      mov esi, [ebp. Client_EDX]
      mov eax, [ebp.Client_DS]
      if (VM is V86 mode)
          shl     eax, 4
          movzx   esi, si               ; zero high order offset
          add     eax, esi
          add     eax, [ebx.CB_High_Linear]
      else (VM is prot. mode)
          if (!32-bit)
              movzx   esi, si
          eax = _Selector_Map_Flat( hVM, [ebp.Client_DS], 0 )
          if (eax != -1)
              add     eax, esi
          if (eax < 1 MB + 64KB)
              add     eax, [ebx.CB_High_Linear]
      endif

The translation APIs are often used when accessing memory specified through V86 or PM APls. Dual-mode (combination V86 and PM) APls accessing application-provided buffers can be easily implemented using the Map_Flat service as demonstrated here:

    ;----------------------------------------------------------------------
    ;
    ; VSIMPLED Get_Info, PMAPI, RMAPI
    ;
    ; DESCRIPTION:
    ;           This function is used to get information about the
    ;           VSIMPLED configuration.
    ; ENTRY :
    ;           Client_ES = selector/segment of VSIMPLEDINFO structure
    ;           Client_BX = offset of VSIMPLEDINFO structure
    ;
    ; EXIT :
    ;           IF carry clear
    ;                 success
    ;                 Client_AX = non-zero
    ;                 Client_ES:BX ->filled in VSIMPLEDINFO structure
    ;           ELSE carry set
    ;                 Client_AX = 0
    ;
    ; USES :
    ;           Flags, EAX, EBX, ECX, ESI, EDI
    ;
    ;----------------------------------------------------------------------

    BeginProc VSIMPLED_API_Get_Info

      Assert_Client_Ptr ebp

      Trace_Out "VSIMPLED_API_Get_Info: called"

      Client_Ptr_Flat edi, ES, BX
      cmp             edi, -1
      je              SHORT GI_Fail

      lea             esi, [gVxDInfo]
      mov             ecx, size VSIMPLEDINFO
      cld
      shr             ecx, 1
      rep             movsw
      adc             cl, cl
      rep             movsb

      mov             [ebp.Client AX], 1            ; success
      clc
      ret

    GI_Fail :
      Debug_Out "VSIMPLED_API_Get_Info: FAILED
      mov             [ebp.Client AX], 0            ; failed
      stc
      ret

    EndProc VSIMPLED_API_Get_Info

### Page Allocation

Allocation of memory can be accomplished using either the _HeapAllocate or _PageAllocate VMM services. In most cases, using the heap allocation services is sufficient for your VxD and may make implementation easier than using the page allocation services. To allocate memory using the heap services use the following code:

    VMMCall    _HeapAllocate,
    or         eax, eax
    jz         SHORT Alloc_Failed
    mov        pDataBlock, eax

VMM allocates the memory on a doubleword boundary, but the cbSize parameter does not have to be dword aligned. The VxD is responsible for making sure that it stays within the bounds of the memory block, because VMM does not provide protection against accessing memory beyond the allocated range. The memory allocated by this service is fixed, and frequent allocating and freeing of memory may fragment the heap. Also, the memory block is not page-locked and may not be present when accessed. PageSwap VxD resolves the not- present fault so your VxD can continue with memory accesses.

If you require page-locked memory and are using the heap management services, the service _LinPageLock can be implemented. This avoids the possibility of VMM discarding the physical memory between accesses by a VxD. However, because physical memory is a limited resource, you should only use this service in cases where page-locked memory is vital to your implementation.

_HeapGetSize, _HeapReAllocate, and _HeapFree are used to determine the block size and to, reallocate and free the memory block, respectively. Using _HeapReAllocate may cause the address of the block to change, and VxDs must not rely on the possibility of the address remaining constant. _HeapReAllocate can preserve the contents of the old block by copying the contents to the new block. The following flags are defined for use with this service:

    HeapNoCopy        Do not copy the contents of the existing
                      block.
    HeapZeroInit      Initialize the new bytes in the heap
                      to zero.
    HeapZeroReInit    Fill all bytes in the block with zero.

MMGR also provides low-level memory management services, allowing a VxD to allocate memory within a physical address range, to perform allocations within physical boundary constraints (not crossing 64k or 128k boundaries), and to allocate memory visible to all VMs or to only a single VM. Additionally, the page-fault handler for the allocated pages can be redirected to a specific handler in your VxD. (See the next section for more information on hooked pages.)

Allocation of pages with physical boundary restrictions and/or physical address limitations can only be performed during initialization. The following example demonstrates allocating a buffer for use with a DMA device:

    ;-----------------------------------------------------------
    ; VSIMPLED_Allocate_DMA_Buffer
    ;
    ; DESCRIPTION:
    ;
    ; This function allocates a buffer suitable for DMA transfers.
    ; It attempts to allocate enough contiguous pages to hold the
    , requested size. If the request fails, the size is halved
    ; until all allocation attempts have failed.
    ;
    ; ENTRY :;
    ; EAX = Desired size (in KB) of the DMA buffer to allocate.
    ;       This size cannot be exceed 64.
    ;
    ; EXIT :
    ;
    ; IF carry clear
    ;      EAX = memory handle of the memory block allocated
    ;      EBX = _physical address_ of memory block
    ;      ECX = actual size in _bytes_ of memory block allocated
    ;      EDX = _ring 0 linear address_ of memory block
    ; ELSE carry set
    ;      EAX = EBX = ECX = EDX = 0
    ;
    ; USES :
    ;
    ; Flags, EAX, EBX, ECX, EDX
    ;
    ;-----------------------------------------------------------
    BeginProc VSIMPLED_Allocate_DMA_Buffer

      cmp eax, 64
      jle SHORT ADB Start

      Debug_Out "Requested size #EAX too big!"
      mov eax, 64

    ADB Start:

      add eax, 3                    ; round up to get
      shr eax, 2                    ; # of pages

    ADB_Allocate_DMA_Buffer_Loop:

      mov ebx, eax                  ; EBX = # of pages to allocate
                                    ; (examples :   3     7      11
                                    ;             12K   28K     44K
      dec eax                       ; # pages -   11Ob 111b   1O11b
      bsr cx, ax                    ; max power of 2 1    2       3
      inc cl                        ; shift cnt      2    3       4
      mov eax, 1
      shl eax, cl                   ; mask + 1    100b 1000b 10000b
      dec eax                       ; mask         11b  111b  1111b
                                    ; alignment    16K   32K    64K
      mov ecx, ebx
      Trace_Out "pages=#ECX alignment=#EAX"

      ;
      ; EAX = alignment mask for allocation
      ; ECX = number of pages to allocate
      ;

      push ecx
      VMMcall _PageAllocate >
      pop      ecx
      or       eax, eax
      jnz      short ADB_Success

      Trace_Out "Allocation failed: pages=#ECX"

      mov      eax, ecx
      shr      eax, 1
      jnz      short ADH_Loop

      xor      ebx, ebx
      xor      ecx, ecx
      stc
      ret

    ADB_Success:

      shl      ecx, 12              ; pages-->bytes

      ;
      ; Returns:
      ;
      ; EAX = memory handle of the memory block allocated
      ; EBX = _physical address_ of memory block
      ; ECX = size in _bytes_ of memory block allocated
      ; EDX = _ring 0 linear address_ of memory block
      ;

      clc                           ;  success
      ret

    EndProc VSIMPLED_Allocate_DMA_Buffer

### Hooked Pages and Page Faults

Hooked pages are allocated with _PageAllocate, using the PG HOOKED attribute. This form of memory management is most commonly used in virtual display drivers to manage multiple VMs that access video display memory. A range of V86 pages is assigned to the VxD and then hooked using the _Assign_Device_V86_Pages and _Hook_V86_Page services, respectively. V86 pages can be assigned globally (global to all VMs) to a device at any time, provided that the page is not already assigned. V86 page assignment to a specific VM can only be performed after device initialization, again with the restriction that the page is not already assigned to a device.

To hook V86 pages, a range of pages is first assigned to the VxD:

    // Buffer used for reserving pages
    DWORD aVMPagesBuf[ 9 ];

    vmmGetDeviceV86PagesArray( NULL, &aVMPagesBuf, NULL ) ;
    if (aVMPagesBuf[ 0x A0/32 ] & 0x FF00FFFF)
    {
      vmmDebugOut( "VDD ERROR: Pages already allocated\r\n" ),
      vmmFatalError(szVDD_Str_CheckVidPgs ) ;
      return ( FALSE ) ;
    }
    if (!vmm Assign Device V B6 Pages( Ox AO, 16, NULL, NULL ))
    {
      vmm Debug Out( "VDD ERROR: Could not allocate pages\r\n" ) ;
      vmm Fatal Error( sz VDD Str Check Vid Pgs ),
      return ( FALSE ) ;
    }
    if (!vmm Assign Device V B6 Pages( Ox BB, 8, NULL, NITLL ))
    {
      vnunDebugOut( "VDD ERROR: Could not allocate pages\r\n" ) ;
      vmm Fatal Error( sz VDD_Str Check Vid Pgs ) ;
      return ( FALSE ) ;
    }

The V86 pages are then directed to a page fault handler:

    // Put an .ASM front end on the page-fault procedure.

    if (NULL =- (p VDD P Fault = VMMWRAP Thunk V86 P H Proc( VDD_P Fault )))
    {
      vmmDebugOut( "VDD ERROR: Could not thunk VDD_PFault!\r\n" ) ;
      vmmFatalError( ) ;
      return( FALSE ),
    }

    // Hook graphics pages

    for (i = 0; i < 16; i++)
      vmmHookV86Page( OxAO + i, pVDD_PFault ) ;

    // Hook text pages

    for (i = 0; i < 8; i++)
      vmmHookV86Page( OxBB + i, pVDD_PFault ) ;

During the Create_VM message processing, the V86 pages are marked as not available (not present and not writeable), using the _ModifyPageBits service:

    vmmModifyPageBits( hVM, OxAO, 16, ~P_AVAIL, NULL,
                       PG_HOOKED, NULL ) ;
    vmmModifyPageBits( hVM, OxBB,  8, ~P_AVAIL, NULL,
                       PG HOOKED, NULL ) ;

Note that it is necessary to specify the PG_HOOKED in the type parameter of the _ModifyPageBits service when clearing any of the PG_PRES, PG_USER, or PG_WRITE bits.

After the initialization is complete, any read or write access of the hooked pages causes a page fault. The page fault handler is called with the faulting page number and the handle of the VM, causing the fault. It is the responsibility of the page fault handler to map memory into the page to resolve the fault or terminate the virtual machine. To map physical memory into the faulting page, use the following code:

    // dwPhysPage is the physical page allocated using
    // _PageAllocate with PG_HOOKED

    vmmPhysIntoV86( dwPhysPage, hVM, uFaultPage, nPages, 0 ;

Under some circumstances (such as low memory or other memory mapping error), it may be more desirable to allow the VM to continue without crashing the VM. In these cases, the system null page is assigned to this linear page:

    vmmMapIntoV86( VMM_GetNulPageHandle(),
                   hVM, uFaultPage, 1, 0, 0 ) ;

The system null page is guaranteed to contammin invalid informationmm for any given VM. Do not rely on its contents for further processing in your VxD.

The VDD uses these techniques to allow multiple VMs to access the video display hardware and maintainmm separate virtual displays for virtual machines. It is also possible to simulate ROM in a virtual machine using hooked pages. When the page fault occurs, map the pages using _PhysIntoV86 and clear the P_WRITE bit using _ModifyPageBits. Note, however, that when the VM restarts, the instruction causing the fault also restarts. If the VM was performing a write operation, a page fault would occur immediately. To resolve this loop, you would need to modify the VM client registers to point the IP to the instruction following the faulting instruction.

### Examining Page Table Entries

A VM can determine whether pages in the linear address space have been accessed and whether data has been written on these pages by examining the page table entries (PTEs) using VMM's _CopyPageTable service. The VDD uses this technique to determine which pages have been accessed and need to be updated in the virtual display of a windowed MS- DOS box.

A linear address in a paging operating system such as VMM is decoded shown in Figure 3.2. Each PTE is 4 bytes in length and contains the access bits and physical address of the page. To examine the PTEs of the first megabyte of the active virtual machine, use page numbers in the range 0 to l OFh. Page numbers of other virtual machines are computed using the CB_High_Linear field in the control block of the respective VM.

Given a pointer to a memory block in a VM, a VxD can use the Map_Flat service to translate this address to a flat offset. Shifting this address right by 12 gives you the page number. To determine if pages in a hooked V86 range have been accessed or if data has been written to these pages use the following code:

      VMMCall       _CopyPageTable, , 0>
      mov           ecx, gu Num Hooked Pages

    Check_Accessed_Or_Dirty:
      test dword ptr aPageBuf[ ecx ], P_ACC or P_DIRTY
      jz SHORT Next_Page
      Trace_Out "Page #ECX of hooked range is dirty or has been\
    accessed"

    Next_Page:
      loop Check_Accessed_Or_Dirty

##### Figure 3.2
Decoding a linear address to a physical address

### Allocating Selectors

A VxD can allocate selectors in the GDT or in a VM's LDT using the _Allocate GDT_Selector and _Allocate_LDT_Selector services. Two descriptor double-words are required when allocating selectors. VMM provides the _BuildDescriptorDWORDs service to generate these double-words:

    VMMCall _BuildDescriptorDWORDs,< dwLinAddr,cbSize,\
                                    RW_Data_Type, 0, 0>
    VMMCall AllocateGDTSelector,

The following equates are useful when building descriptor double-words:

    ; Common definitions for segment and control descriptors

    D_PRES                   segment is present in memory

    D_NOTPRES                segment not present

    D_DPL0                   descriptor privilege level definitions
    D_DPL1
    D_DPL2
    D_DPL3

    D_SEG                    segment descriptor (application type)

    D_CTRL                   control descriptor (system type)

    D_GRAN_BYTE              limit in byte granularity

    D_GRAN_PAGE              limit in page granularity

    D_DEF16                  default operation size is 16 bits (code)

    D_DEF32                  default operation size is 32 bits (code)

    ; Definitions specific to segment descriptors

    D_CODE                   code segment

    D_DATA                   data segment

    D_RX                     if code, readable

    D_X                      if code, executable only

    D_W                      if data, writeable

    D_R                      if data, read only

    D_ACCESSED               segment accessed bit

    ; Useful segment definitions

    RW_Data_Type             present R/W data segment

    R_Data_Type              read-only data segment

    Code_Type                code segment

### Instance Pages

The MMGR manages instance data for VMs. Instance data is a range in V86 address space that VMM maintains separately for each VM. It is used frequently for MS-DOS and some TSRs.

For example, if an MS-DOS device driver maintains an input buffer, it may be useful to have the buffered input directed to the VM that was active when the buffer was filled. In this case, the VxD would query the device driver for the buffer address and maximum size and add an instance data area as shown here:

    // Define instance data for instance data manager

    INSTDATASTRUC Instance_Area = { NULL, NULL,
                                    NULL, NULL,
                                    ALWAYS_Field } ;

    // Specify instanced area as provided by DOS driver.

    Instance_Area.dwInstLinAddr=pInputBuffer;
    Instance_Area.dwInstSize=dwBufferSize;
    if(!VMM_AddInstanceItem(&Instance_Area,0))
      goto DI_FatalError ;

### Mapping Memory into Multiple VMs

When writing VxDs for use with "Windows-aware" TSRs, it may be necessary to allocate a block of memory that is global to all VMs, that is, a memory block with a V86 address mapped to the same physical memory in all VMs. The _Allocate_Global_V86_Data_Area service performs this type of allocation as shown here:

    // Allocate a global V86 data area of 512 bytes

    if (NULL ==
        (gdwGlobalArea =
            vmmAllocateGlobalV86DataArea( 512,
                                          GVDADWordAlign ) ) )
    {
      vmmDebugOut( "Failed to allocate global V86 data area!\r\n" ) ;
      return ( FALSE ) ;
    }

    vmmTraceOutParam( "Allocated global area at #EAX\r\n",
                      gdwGlobalArea ) ;

The _Allocate_Global_V86_Data_Area service accepts the following flags:

    GVDADWordAlign             Aligns the block on a doubleword boundary.

    GVDAHighSysCritOK          Informs the services that the VxD can handle
                               a block that is allocated from high MS-DOS
                               memory, such as UMBs or XMS.
                               (Win 3.1 only)

    GVDAInquire                Returns the size in bytes of the largest
                               block that can be allocated, given the
                               requested alignment restrictions.
                               (Win 3.1 only)

    GVDAInstance               Creates an instance data block, allowing
                               the VxD to maintain separate blocks for
                               each VM.

    GVDAPageAlign              Aligns the block on a page boundary.

    GVDAParaAlign              Aligns the block on a paragraph boundary.

    GVDAReclaim                Unmaps the physical pages in the block when
                               mapping the system null page into the block.
                               The physical pages are added to the free list
                               when this value is specified. Only applies
                               to blocks allocated on a page boundary.
                               If this flag is not specified, it is up to the
                               virtual device to reclaim these pages.

    GVDAWordAlign              Aligns the block on a word boundary.

    GVDAZeroInit               Fills the allocated block with zeros.

In the VMEMTRAP sample, an unassigned V86 area is located and assigned to the virtual device. Pages are allocated for each new VM and "instanced" pages are simulated, using hooked V86 pages and a page-fault handler. Using the _AllocateGlobalV86DataArea service specifying the GDVAInst accomplishes the same thing in a single service call, with the exception that a specific V86 range cannot be specified.

_AllocateGlobalv86DataArea has limitations. For example, you cannot hook the page fault handler or modify the page bits of the V86 linear range returned by this service. Windows 3.x does not provide an interface to allow VxDs to monitor access of these pages other than viewing the page table entry access bits. A virtual device must provide an additional interface to manage VM contention of these pages using software inten-upts or the VxD's API.

### Page Protection

As stated in the preceding section, VMM's support for monitoring access to a given V86 address space is limited. Page protection can be implemented with pages assigned to a device using the _Assign_Device_V86_Pages service, but these pages are usually only available when memory is not already mapped into the reserved ROM addresses. Because of upper memory blocks (UMBs) implemented by most 386 memory managers, this region is usually already claimed by VMM. Also, the normal accessible regions of V86 memory (between _GetFirstV86Page and _GetLastV86Page) are off limits to a VxD using the API provided by VMM.

An unsupported method of providing page protection is to modify the page table entries (PTEs) directly and hook the Invalid_Page_Fault handler. The PTE contains the page frame address in the upper 20 bits (4k page aligned), and the lower 12 bits provide access restriction and accessed and/or dirty information.

Entry 0 in the page directory contains the physical address of the page table for the V86 address space of the active VM. By modifying these page table entries, you can modify the access rights to a given page in V86 address space.

You must use caution when accessing the page tables directly. Modifying not-present page tables or incorrectly modifying page access bits will cause the system to crash. In other words, "Ok, here's your weapon, first point it at your foot before pulling the trigger!"

Page protection is risky business when it is not directly supported by the host operating system, but some implementations require such information about how a VM is behaving. Take note ! ! You can guarantee that anything that you do now to provide this mechanism may not be supported in future releases of Windows. Use this information at your own risk and version bind your code to the Microsoft Windows 3.1 VMM.

##### Figure 3.3
PosSible design of TSR to VxD communication

The VGLOBALD sample demonstrates the allocation of a global V86 data area that would be suitable for a TSR and VxD to use for communication in multiple VMs. If you run this sample under the debugging version of WIN386.EXE you should notice that, when new VMs are created and the System VM does not have access to the pages that are hooked using this page protection scheme, VMM will "gripe" about the not-present page within the V86 page range. You may decide to modify the page table entries to match WIN386 expectations before creating a new VM.

### V86MMGR

V86MMGR provides an interface for VxDs to map protected- mode data buffers to V86- interfaces. When a virtual device translates an API which transfers data using pointers to data blocks from protected mode applications to DOS-mode device drivers, it needs to implement services provided by V86MMGR to translate these buffers to a V86 addressable memory. Also, DOS device drivers that update buffers asynchronously require memory to be mapped into global V86 address space.

For example, Int 21h eommonly uses buffers referenced by DS:DX. The DOSMGR virtual device provides automatic buffer translation for most of these APIs by hooking Int 21 h and translating the protected mode addresses so that DOS can understand the request without additional work required by the protected-mode application. Additionally, VNETBIOS provides buffer mapping for NetBIOS data packets using V86MMGR services. These buffers are updated as the result of interrupt processing.

V86MMGR provides two types of services: buffer mapping and buffer translation. The mapping services update the page tables in all VMs so that the buffer is in global V86 space. The translation services copy a buffer to a V86 copy buffer and use the copy buffers address to communicate with the DOS device driver code. The mapping services should be used only when the buffers will be updated asynchronously. Do not use the mapping services in place of the translation services to avoid copying the buffer s data - it is faster to copy data to and from a translation buffer than to map a buffer into multiple virtual machines.

V86MMGR does not directly support the mapping or translation of buffers referenced by pointers within a structure. The VxD is responsible for translating or mapping the buffer using V86MMGR services; it updates the structure to contain a valid V86 pointer and then passes the call to the DOS device driver.

When a VxD requires V86MMGR services, it must inform V86MMGR how many pages are required by using the V86MMGR_Set_Mapping_Info service. This service call must be made during initialization, preferably during Sys_Critical_Init processing. Alternatively, the VxD can call this service during Device_Init, if the VxD has an Init_Order less than V86MMGR_Init_Order.

When a call to the DOS device has been intercepted by the VxD, the VxD should determine whether the call is from V86 mode or protected mode. When a V86 call is trapped, buffer translation is not necessary, but mapping for asynchronously updated buffers may be necessary if the buffer is not located in global V86 address space determined by using the _TestGlobalv86Mem service.

To map pages to DOS addressable memory, a VxD calls V86MMGR_Map_Pages with the linear address and number of bytes to map. The returned linear address is guaranteed to be in the first megabyte and in global V86 address space. A map handle is also returned by this service. When the mapping region is no longer required, it is freed using the V86MMGR_Free_Page_Map_Region service with the map handle that was returned by V86MMGR_Map_Pages.

To translate a protected-mode buffer to V86 addressable memory, a VxD calls V86MMG_Allocate_Buffer with the linear address of the buffer to translate and the number of bytes to allocate. If specified, this service copies data to the new buffer. Translation buffers are allocated in a "stack" fashion. In other words, the last buffer allocated must be the first buffer freed. When the translation buffer is no longer required, the V86_Free_Buffer service is used.

The following code fragment demonstrates how a software interrupt buffer is translated from a protected-mode to a real-mode driver:

    ;
    ; On entry Client_DS:Client_DX points to a buffer that is
    ; filled asynchronously and needs to be napped globally.
    ; Eat the PM interrupt and reflect it to V86 mode.
    ;
    ; When the DOS device driver has completed the data
    ; transfer, the pages must be unmapped using the
    ; V86MMGR_Free_Page_Map_Region service.
    ;

    BeginProc PM_Translate

      pushad
      test       [ebx.CB_VM_Status], VMStat_PM_Exec
      jz         SHORT PT_Bail
      VMMCall    Simulate_Iret
      Map_Flat   esi, DS, DX
      movzx      ecx, [ebp.Client-CX]
      VxDCall    V86MMGR_Map_Pages
      mov        hPageMap, esi
      shl        edi, 12
      shr        di, 12

      ;
      ; Simulate the interrupt to V86

      ;
      Push_Client_State
      Begin_Nest_V86_Exec
      mov        [ebp.Client_DX], di
      shr        edi, 16
      mov        [ebp.Client_DS], di
      mov        eax, Trapped_INT
      VMMCall    Exec_Int
      VNNCakk    End_Nest_Exec
      Pop_Client_State
      clc

    PT_Bail:
      Debug_Out "Failure: Call not from protected mode!"
      stc

    PT_Exit:
      popad
      ret

    EndProc PM Translate

V86MMGR provides a number of macros to define a script for use with the V86MMG_Xlat_API service. A VxD defines a translation script in its data segment using these translation macros and calls the VB6MMGR service to execute the script. This provides the VxD with a way to reduce the code size of V86 translation services and to use the optimized routines in VB6MMGR.

The translation scripts are terminated by Xlat_API_Exec_Int or Xlat_API_Jmp_To_Proc. When the V86MMGR_Xlat_API service executes one of these commands, control returns to the VxD after the command has been executed. The following sample code demonstrates the use of these macros to translate a null-terminated string for a call to a DOS device driver:

    ;
    ; This code demonstrates a simple translation of a NULL
    ; terminated string in DS:SI to a local V86 buffer.

    ;

    VxD_DATA_SEG
    Xlat_ASCIIZ_Script:
      Xlat_API_ASCIIZ       ds, si
      Xlat_API_Exec_Int     60h
    Vx_D_DATA_ENDS

    Vx_D_CODE_SEG
    BeginProc Translate_Int60h_Buffer

      mov        edx, OFFSET32 Xlat_ASCIIZ_Script
      VxDJmp     VB6MMGR_Xlat_API

    EndProc Translate_Int60h_Buffer
    VxD_CODE_ENDS

# Chapter 4
V86/PM VxD API

A VxD can export an API to protected-mode and V86 mode applications, extending the capabilities of a Windows or MS- DOS driver using supervisor code. For example, the VCD provides an interface to the Windows communications driver (COMM.DRV) to acquire a COM port. The COMM driver queries the VCD for the availability of a given port. If the port is in use by an MS-DOS application, the VCD returns failure. This API allows the COMM.DRV to provide intelligent information regarding the availability of COM ports to the calling application and provides a mechanism to manage device contention.

A VxD declares the API support by defining API procedure entry points in the DDB (see Chapter 1). In the following example, VSIMPLED_V86_API_Proc and VSIMPLED_PM_API_Proc procedures are the entry points for the API from V86 mode and protected mode, respectively. Additionally, the VxD must declare the device ID, as supplied by Microsoft. Declare_Virtual_Device VSIMPLED, VSIMPLED_MAJOR_VER,\ VSIMPLED, MINOR_VER,\ VSIMPLED_Control_Proc,\ VSIMPLED_Device_ID,\ Undefined_Init_Order,\ VSIMPLED_V86_API_8roc,\ VSIMPLED_PM_API_Proc

An application acquires the entry point of the VxD by using Int 2Fh with AX=1684h and BX=VxD Device ID: ; ; Obtain the VxD entry point, if NULL, VxD is not present. ; mov ax, 1684h ; get VxD API entry point mov bx, VSIMPLED_Device_ID int 2fh mov word ptr dwVxDEntry, di mov word ptr dwVxDEntry + 2, es

When this entry point is called by the application, the call is dispatched to the VxD, where it processes the request and returns control to the calling application.

Prior to requesting the VxD entry point from VMM, the application should first determine whether Windows/386 (VMM) is present. A Windows application can use the GetWinFlags() API. A DOS application needs to use Int 2Fh, AX=1600h interface to determine whether VMM is present:

    mov     ax, 1600h          ; Enhanced Windows Check
    int     2fh
    test    al, 7fh            ; VMM (Win386) present?
    jz      Not_Win386

### The Faulting Mechanism and API Dispatch

If calling ring-0 VxD code directly from ring 3 seems too good to be true, you should be interested in how this call is dispatched to the VxD. When the Int 2Fh request is processed, the VMM allocates a callback address in the VM's address space. When the VM calls this address, the code generates a fault, a ring transition results, and the fault is dispatched to VMM's fault handler.

VMM determines the operation mode of the VM by testing the status flags in the VM control block. It determines whether the call was made from V86 or protected mode and then dispatches the call at ring 0 to the appropriate handler, as declared in the DDB.

### The Client Register Structure

When the API entry points are called, the EBP register points to the Client_Register_Structure (CRS):

    typedef struct tag CRS_32
    {
      DWORD Client_EDI ;
      DWORD Client_ESI ;
      DWORD Client_EBP ;
      DWORD dwReserved_1 ; // ESP at pushall
      DWORD Client_EBX ;
      DWORD Client_EDX ;
      DWORD Client_ECX ;
      DWORD Client_EAX ;
      DWORD Client_Error ; // DWORD error code
      DWORD Client_EIP ;
      WORD  Client_CS ;
      WORD  wReserved_2 ; // (padding)
      DWORD Client_EFlags ;
      DWORD Client_ESP ;
      WORD  Client_SS ;
      WORD  wReserved_3 ; // (padding)
      WORD  Client_ES ;
      WORD  WReserved_4 ; // (padding)
      WORD  Client_DS ;
      WORD  wReserved_5 ; // (padding)
      WORD  Client_FS ;
      WORD  wReserved_6 ; // (padding)
      WORD  Client_GS ;
      WORD  wReserved_7 ; // (padding)

      DWORD Client_Alt_EIP ;
      WORD  Client_Alt_CS ;
      WORD  wReserved_8 ; // (padding)
      DWORD Client_Alt_EFlags ;
      DWORD Client_Alt_ESP ;
      WORD  Client_Alt 85 ;
      WORD  wReserved_9 ;  // (padding)
      WORD  Client_Alt ES ;
      WORD  wReserved_10 ; // (padding)
      WORD  Client_Alt_DS ;
      WORD  wReserved_11 ; // (padding)
      WORD  Client_Alt_FS ;
      WORD  wReserved_12 ; // (padding)
      WORD  Client_Alt_GS ;
      WORD  wReserved_13  ; // (padding)

    } CRS 32, *PCRS 32 ;

The parameters to the API call, as set by the calling application, are contained in the CRS, and the current VM handle is in EBX.

A VxD usually defines a jump table to the specific API functions that perform the nequested action and return the results to the API handler that reflects the results in the CRS. The following example code demonstrates how functions are dispatched from a VxD API procedure entry point:

    ;***************************************************************
    ;  D E V I C E D A T A
    ;***************************************************************
    VxD_DATA_SEG

    DOBXFER_PM_Call_Table LABEL DWORD
      dd OFFSET32 DOSXFER_Get_Version
      dd OFFSET32 DOSXFE_PM_Enable_Call_Backs
      dd OFFSET32 DOSXFE_PM_Copy_Data

    Max_DOSXFER_PM_Service equ ($ - DOSXFER PM Call Table) / 4
    VxD_DATA_ENDS

    .***************************************************************
    ; E X P O R T E D     A P I
    .***************************************************************

    BeginProc DOSXFER_PM_API_Proc, PUBLIC

      Trace_Out "In DOSXFER_PM_API_Proc"

      VMMCall Test_Sys_VM_Handle
    IFDEF DEBUG
      jz             SHORT @f
      Debug_Out      "DOSXFER_PM_API_Proc not from SYS VM"

    @@:
    ENDIF
      jnz            SHORT DOSXFER_PM_Call_Bad
      movzx          eax, [ebp.Client DX]            ; function in DX
      cmp            eax, Max_DOSXFER_PM_Service
      jae            SHORT DOSXFER_PM_Call_Bad
      and            [ebp.Client EFLAGS], NOT CF_Mask ; clear carry
      call           DOSXFER_PM_Call_Table[ eax * 4 ] ; call service
      jc             SHORT DOSXFER_PM_API_Failed
      ret

    DOSXFER_PM_Call_Bad:
    IFDEF DEBUG
      Debug_Out "Invalid function #EAX on DOSXFER_PM_API_Proc"
    ENDIF

    DOSXFER_PM_API_Failed:
      or             [ebp.Client EFLAGS], CF_Mask    ;  set carry
      ret

    EndProc DOSXFER_PM_API_Proc

### Examining and Modifying Information of the Active VM

Changes made in the CRS by the API handler are reflected to the VM when VMM returns control. This is the primary communication channel between code executing in the VM and the API handlers. VMM defines three structures for the CRS: One references the registers with 32-bit definitions (EAX), another for 16-bit registers (AX), and the last for 8-bit register access (AH and AL).

Modification of the client registers is made easy using these structure definitions:

    ; Copy the data structure to the VM and return the results
    ; of the function.
    ; EBX = VM handle, EBP = -> CRS
    ;

      Map_Flat edi, ES, DI
      lea      esi, gDataStruc
      mov      ecx, size DATASTRUCT
      shr      ecx, 1
      rep      movsw
      adc      cl, cl
      rep      movsb
      mov      [ebp.Client_CX], size DATASTRUCT
      mov      [ebp.Client_AX], 1                   ; SUCCESS!
      and      [ebp.Client E_Flags], NOT( CF_Mask ) ; clc

A VxD may also update a buffer referenced in the CRS by obtaining a flat address using the mapping services discussed in Chapter 3.

### Creating a Dual-Mode API

By setting both the V86 and PM API entry points in the DDB to the same handler, a VxD can provide the same services to all VMs and reduce the amount of code of duplicate dispatch functions. To determine the operating mode of the calling VM, the VxD queries the execution status of the VM using the status flags of the VM control block. By testing CB_VM_Status for VMStat_PM_Exec, a VxD can determine whether a VM is calling from V86 or protected mode:

    ;
    ; Determine the execution mode of the VM.
    ;

      test [ebx.CB_VM_Status), VMStat_PM_Exec
      jz SHORT API_VM_In_V86
      test [ebx.CB_VM_Status], VMStat_PM_Use32
      jz SHORT API_VM_In_PM16

    API_VM_InPM32 :
      Debug_Out "VM calling from 32-bit protect mode."
      ret

    API_VM_InV86 :
      Debug_Out "VM calling from V86 mode."
      ret

    API_VM_In_PM16:
      Debug_Out "VM calling frm 16-bit protected mode."
      ret

Note: In Windows 3.x, calling VxD procedures through VxD API calls from 32-bit code segments in the System VM can cause unexpected results when the offset of the return address of the calling routine is greater than OxFFFF. This is a problem with the way that VMM determines the "32- bitness" of the calling application. The System VM is flagged for 16-bit protected mode operation, because Krnl386.EXE is responsible for the switch to protected mode when the Windows GUI is started. Whether 32- bit segments are allocated within the System VM and code within these segments calls VxD APIs, VMM determines that the calling application is 16-bit because of the VM flags. The return address is assumed to be 16 bits and is truncated. This is also a problem for protected-mode software interrupts hooked by a VxD. The only current work- around is to guarantee that the code calling the VxD has a return address with an offset less than OxFFFF.

### Callbacks and Hooking Existing DOS Devices

Callbacks are used indirectly when defining a VxD API. However, a VxD can also allocate a callback entry point that, when called by a VM, switches control to the associated callback procedure in the VxD.

Callbacks can be used to simulate DOS devices that return a pointer to a jump table by allocating a global V86 table and stuffing the address of the callback allocated using Allocate_V86_Call_Back service into this table. A segment and offset are returned that directs any calls to this routine to the VxDs callback procedure. The CRS reflects the current state of the VM when the callback entry point was called by the VM. A VxD can also provide a "chaining" interface to hooked software interrupts by using these services.

A VxD with "carnal" knowledge of a DOS device driver can intercept calls to this device by using the Install_V86_Break_Point service. This service patches the memory at the requested address with a call to the break point. When the break point is executed, the VxD can process the VM request as necessary and then return control by "bumping" the IP to the next instruction or by using Simulate_Far_Jmp to move the Client CS: Client IP to the correct address.

# SECTION II
Advanced Topics

# Chapter 5
Nested Execution

The nested execution services of VMM provide a controlled environment in which a VxD can cause a redirection of the execution path in a VM. A VxD saves the client registers, begins a nested execution block forcing a VM into V86 or protected mode, calls the necessary services to set up stack frames, and then resumes the VM execution. When the VM returns, the nested execution block is ended and the client registers are restored. Using this technique, a VxD can force the execution of code in TSRs, DOS applications, and even Windows procedures.

When calling routines in a VM other than the current VM, you may need to schedule a VM event to force a specific VM to become aetive. You may also need to determine the execution status of the VM and wait for critical sections to be completed, interrupts to be enabled, and so on. In these cases, you can use the Call_Priority_VM_Event service and begin the nested execution when the event is processed.

### Simulating Software Interrupts

As demonstrated in Chapter 3, a VxD can simulate software interrupts to a VM using the Simulate_Int or Exec_Int services. Simulated interrupts are subject to being trapped by other VxDs and will respond exactly as if a VM executed the software interrupt in application code. Additionally, a VxD that has hooked a protected-mode interrupt can affect the callers stack to "eat the interrupt" in protected mode by using a non-nested Simulate_Far_Iret and then reflect it to V86 mode by using nested execution services.

Note that when a VxD simulates calls to a VM and the execution has returned to the VxD, the VxD must copy the results from the CRS before restoring the client's state:

    ;
    ; Simulate a software interrupt to the current VM
    ;

      Push_Client_State
      VMMCall Begin_Nest_V86_Exec

      mov          [ebp.Client_AX], 4257h           ; specific function
      mov          [ebp.Client_BX], 4C57h           ; subfunction
      mov          eax, 60h
      VMMCall      Simulate_Int
      VMMCall      Resume_Exec
      VMMCall      End_Nest_Exec
      movzx        eax, [ebp.Client AX]             ; get return value
      Pop_Client_State

What magic occurs in this code that allows a VxD to simulate an interrupt call in a VM? The Push_Client_State macro allocates space on the stack and copies the current CRS to this block. Begin_Nest_V86_Exec modifies the VM state so that the execution block occurs in V86 mode. Simulate_Int builds an IRET frame and modifies the client's stack and CS:(E)IP to call the interrupt handler. Resume_Exec forces VMM to complete event processing and then resumes the execution of the VM. When the VM completes the execution block, control returns to the VxD and the End_Nest_Exec restores the VM's execution state. The Pop_Client_State macro restores the client's registers, as saved on the stack.

### Calling Windows Functions from a VxD

The techniques used to simulate software inten-upts to a VM can be extended to call functions in the System VM. There are a few restrictions when calling Windows functions or functions provided by Windows DLLs:

- The function must be able to handle reentrancy. Many Windows functions are not reentrant. PostMessage() and its derivatives are safe, as are a few other Windows multimedia services.
- The code segment of the function must be present. The Windows Kernel does not support not-present segment faults when reentered. Because a VxD can not determine when the Windows Kernel is executing code, the segment must always be present (or non-discardable).
- If DOS or BIOS is used for paging, the function code must be page-locked in memory. Because DOS and BIOS are not reentrant, a page-fault cannot be resolved if DOS or BIOS code is currently executing in any VM.
- The safest segmentation for a function called by a VxD is in a FIXED code segment of a DLL. Calling application code is dangerous and is not recommended.

To call Windows functions, you must use a helper application or DLL to provide the procedure address to the VxD. The VxD can then use the nested execution services to simulate a far call to the procedure in the System VM. If a VM context switch is required (if the current VM is other than the System VM), the VxD must schedule a VM event to call the procedure. The following code sample calls the Windows PostMessage() function from a VxD assuming the PostMessage function pointer was obtained from the application or DLL):

    ;=================================================================
    ;
    ; VSIMPLED Notify App
    ;
    ; This routine notifies the Windows application through a
    ; call to the Post Message() API.
    ;
    ; ENTRY :
    ;       EDX: contains the 1Param of the message
    ;
    ; USES :
    ;       FLAGS
    ;
    ;=================================================================

    BeginProc VSIMPLED_NotifyApp, High_Freq

      VMMCall           Test_Sys_VM_Handle
      je                SHORT VSIMPLED Post Event

    NA_Schedule:
      push              ebx
      mov               eax, High_Pri_Device_Boost
      VMMCall           Get_Sys_VM_Handle
      mov               ecx, PEF_Wait_For_STI OR PEF_Wait_Not_Crit
      mov               esi, OFFSET32 VSIMPLED_PostEvent
      xor               edi, edi
      VMMCall           Call_Priority_VM_Event
      pop               ebx
      ret

    EndProc VSIMPLED_Notify_App

    ;=================================================================
    ; VSIMPLED_PostEvent
    ;
    ; Called by the priority VM event dispatch routine or
    ; directly if System VM was already active.
    ;
    ; ENTRY :
    ;   EBX: The system VM handle
    ;   EBP: Client register structure
    ;   EDX: Reference data
    ;
    ; USES :
    ;   EAX, EDX, FLAGS
    ;=================================================================

    BeginProc VSIMPLED_PostEvent

      Trace_Out  "In VSIMPLED_PostEvent"

      cmp        lpPostMessage, 0           ; Q: ptr == NULL?
      je         SHORT PE_Exit              ; Y: can't call

      Push_Client_State
      VMMCall    Begin_Nest_Exec

      mov        ax, NotifyWnd              ; handle to window
      VMMCall    Simulate_Push
      mov        ax, Notify Msg             ; notification msg
      VMMCall    Simulate_Push
      xor        ax, ax
      VMMCall    Simulate_Push              ; wParam is NULL
      mov        eax, edx
      shr        eax, 16
      VMMCall    Simulate_Push              ; lParam is ref data
      mov        eax, edx
      VMMCall    Simulate_Push

      movzx      edx, WORD PTR [lpPostMessage]
      mov        cx,  WORD PTR [lpPostMessage + 2]

      VMMCall    Simulate_Far_Call         ; call PostMessage()
      VMMCall    Resume_Exec
      VMMCall    End_Nest_Exec
      Pop_Client_State
    PE_Exit :
      ret

    EndProc VSIMPLED_PostEvent

### Calling Code in a TSR at Ring 0

In Windows 3.1, the VPICD added services that allow a Windows driver to provide interrupt service routines callable at ring 0. This means a Windows device driver to provide a common code base for hardware interrupt servicing. This technique can be implemented by other VxDs to call routines in a VM directly from ring 0, as shown in Figure 5.1.

##### Figure 5.1
Possible design of calling a TSR directly (at ring 0) from a VxD

The technique to call TSR code from ring 0 is actually quite simple. A VxD provides an API that allows a V86 or PM application to register a procedure as a "direct" callback procedure. Ring 0 16-bit GDT selectors are built to access code and data of the callback procedure. When the required event occurs, the VxD calls the callback procedure by setting up a far return frame, including a 32-flat far return address to a return-to-flat procedure and a 16:16 far return address to a return-from-16 procedure in the VxD. The VxD then performs a far return kicking out to the 16-bit code in the TSR. When the TSR has completed processing, the far return kicks back to the return-from-16 procedure in the VxD. The last remaining issue is to return to 32-flat model by using a final far return to the return-to-flat procedure.

This method makes some assumptions of the way TSRs are loaded in the system:

- The TSR is loaded before Windows is started and is therefore global to all VMs.
- The GDT selectors are based on the low linear address of the TSR. Because the TSR is

  global in all VMs, this mapping must remain constant in all page tables.

- If the code was specific to a VM, a priority VM event would be required to make the VM active before calling the code directly at ring 0.
- Using this scheme, the stack is provided by VMM and is a Use32 segment. Stack parameter passing is not valid unless the TSR uses 32-bit references to the stack (ESP and EBP). The TSR code should not attempt to change SS.

  The following code fragments demonstrate the technique of calling TSR code (16-bit code) at ring 0. In Sys Critical Init, the GDT selectors used for the call to the TSR are allocated. For this sample, a global timeout is used to initiate the calls to the TSR.

      ;---------------------------------------------------------------
      ; VCALLTSR_Sys_Critical_Init
      ;
      ; DESCRIPTION:
      ;       Allocates necessary GDT selectors.
      ;
      ; ENTRY :
      ;       EBX = handle to Sys VM
      ;       EDX = reference data from real-mode init
      ;
      ; EXIT:
      ;       Carry clear if no error, otherwise set if failure.
      ;
      ; USES :
      ;       Flags
      ;---------------------------------------------------------------

      BeginProc VCALLTSR_Sys_Critical_Init

        Trace_Out "VCALLTSR: Sys_Critical_Init"

        pushad

      ;
      ; Note :
      ;
      ; An assumption is made that CS:0 is the base of the TSR.
      ; Since we don't have a segment size, we'll assume 1 page,
      ; but this could be handled by using a pointer to a structure
      ; within the TSR obtained from Exec Int instead of using
      ; Real Mode_Init to gather the information.
      ;

        mov           eax, edx
        movzx         edx, ax
        mov           dwTSR_RingO_EIP, edx
        shr           eax, 16
        shl           eax, 4

        push          eax                     ; save address
        VMMCall       _BuildDescriptorDWORDS, < eax, ,\
                                                Code Type,\
                                                ,\
                                                BDDExplicitDPL >
        VMMCall       _Allocate_GDT_Selector, < edx, eax, 0 >
        or            eax, eax
        jnz           SHORT SCI_GotCSSel
        pop           eax
        jmp           SHORT SCI_Failure

      SCI_GotCSSel:
        mov           dwTS_RingOCS, eax

        pop           eax                     ; restore address

        VMMCall       _BuildDescriptorDWORDS, < eax, ,\
                                                RW_Data Type, \
                                                ,\
                                                BDDExplicitDPL >
        VMMCall       _Allocate_GDT_Selector, < edx, eax, 0 >
        or            eax, eax
        jz            SHORT SCI_Failure
        mov           dwTSR_Ring0_DS, eax

        VMMCall       _BuildDescriptorDWORDS, < ,\
                                                VCT_Switch_Size,\
                                                Code_Type,\
                                                ,\
                                                BDDExplicitDPL >
        VMMCall       _Allocate_GDT_Selector, < edx, eax, 0 >
        or            eax, eax
        jz            SHORT SCI_Failure
        mov           wTSR_Switch_To_Flat_CS, ax

        mov           eax, 500                ; 500 ms timeout
        xor           edx, edx                ; no data
        mov           esi, OFFSET32 VCALLTSR_Time_Out
        VMMCall       Set_Global_Time_Out
        mov           hTimeOut, esi
        popad
        clc
        ret

      SCI Failure :

        ;
        ; Free any allocated selectors and exit
        ;

        mov           eax, dwTSR_Ring0_CS
        or            eax, eax
        jz            SHORT SCI_Failure_TryDS
        VMMCall       _Free_GDT_Selector,

      SCI_Failure_TryDS:
        mov           eax, dwTS_Ring0_DS
        or            eax, eax
        jz            SHORT SCI_Failure_TryFlat
        VMMCall       _Free_GDT_Selector,

      SCI_Failure_TryFlat:
        movzx         eax, wTSR_Switch_To_Flat_CS
        or            eax, eax
        jz            SHORT SCI_Failure_Exit
        VMMCall       _Free_GDT_Selector,

      SCI_Failure_Exit:
        popad
        stc
        ret

      EndProc VCALLTSR_Sys_Critical_Init

  When the timeout procedure is called, the stack frames are created to call the TSR code directly. When the TSR returns the VxD unwraps the stack to get back to 32-bit flat model:

      ;---------------------------------------------------------------
      ; VCALLTSR_TimeOut
      ;
      ; DESCRIPTION:
      ;       Event handler for global timeout. Calls TSR code directly
      ;       from ring 0.
      ;
      ; ENTRY :
      ;       EBX = Current VM handle
      ;       ECX = additional ms since timeout
      ;       EDX = reference data
      ;       EBP = -> CRS
      ;
      ; EXIT :
      ;      Reachedules time-out.
      ;
      ; USES :
      ;      All registers.
      ;---------------------------------------------------------------

      BeginProc VCALLTSR_TimeOut

        pushad
        mov           hTime Out, 0            ; clear handle
        Trace_Out     "Setting up stack frames to call TSR."

        ;
        ; This stack frame is so we can get back to flat model.
        ;
        push          cs                      ; save CS
        mov           eax, OFFSET32 VCALLTSR_Back_To_Flat
        push eax                              ; save EIP

        ;
        ; This stack frame will get us back to 32-bit code in
        ; the VxD and is addressable via 16:16 for the TSR.
        ;

        push          ds                      ; save off DS
        push          dwTSR_RETF_From_16

        ;
        ; This is the stack frame used to get us to the TSR
        ; code. Additionally, DS is setup with a R/W*I pointer
        ; to the same base address.
        ;

        mov           eax, dwTSR_Ring0_DS
        mov           ds, ax
        push          cs:dwTS_Ring0_CS
        push          cs:dwTSR_Ring0_EIP
        retf                                  ; go to the TSR

      VCT_Switch:
        pop ds                                ; restore DS
        retf                                  ; return to flat

      VCT_Switch_Size equ ($ - VCALLTSR_Switch_To_Flat) - 1

      VCALLTSR_Back_To_Flat:

        Trace_Out "Back in flat model. Return from TSR = #AX"

        ;
        ; Reschedule time out event
        ;

        mov           eax, 500                ; 500 ms timeout
        xor           edx, edx                ; no data
        mov           esi, OFFSET32 VCALLTSR_TimeOut
        VMMCall       Set_Global_Time_Out
        mov           hTimeOut, esi

        popad
        ret

      EndProc VCALLTSR_TimeOut

  # Chapter 6
  I/O TraMpping

  I/O protection is a powerful feature provided by the 80386/80486 chipset. When the Current Privilege Level (CPL) is less than or equal to the I/O privilege level (IOPL), the following instructions can be executed:

      IN        input
      INS       input string
      OUT       output
      OUTS      output string
      CLI       clear interrupt-enable flag
      STI       set interrupt-enable flag

  If CPL is less than or equal to IOPL in protected mode, the processor allows the I/O operation to proceed. If CPL is greater than IOPL or if the processor is operating in virtual 8086 mode, the I/O permissions bitmap (IOPM) is used to determine whether access to the port is allowed. Because MS-DOS VMs run in virtual 8086 mode and a Windows application has a CPL of 3 (for Windows 3.1 ) and IOPL is 0, the I/O permissions bitmap is always used in these cases to determine whether access to the port is valid.

  VMM keeps a copy of the IOPM for each VM (it is associated with the TSS and other task information). VxDs can enable or disable access to ports by modifying the IOPM using VMM services. Also, it is possible to trap ports in one VM and allow access to the hardware directly in another VM.

  The Install_IO_Handler and Install_Mult_IO Handlers services install handlers that are called when the GP fault handler has determined that I/O to the associated port has caused the fault. VMM provides the Enable_Local_Trapping, Enable_Global_Trapping, Disable_Local_Trapping, and Disable_Global_Trapping services to modify the IOPM of virtual machines to enable and disable access to the I/O ports.

  I/O trapping is the primary method used to manage device contention. By allowing only one VM access to a hardware device address space, the VxD can manage accesses by other VMs. For cases of contention, a VxD can simulate the device I/O and submit the actual hardware request when the hardware is free, ignore the hardware access, and return as though the hardware did not exist or crash the VM attempting to access the hardware.

  A VxD can simulate hardware that does not exist by virtualizing the device using a finite state machine (or other similar method) and returning the appropriate information to the requesting application.

  ### Trapping and Dispatching I/O

  To trap I/O addresses, a VxD uses the Install_IO_Handler or Install_Mult_IO_Handlers services of VMM. These services are only available during device initialization.

  These services associate a callback (or table of callbacks) with an I/O port (or table of I/O ports). By default, global trapping is enabled, any access to the trapped ports causes a fault, and the associated callback procedure is called.

  An I/O table has the following format: VxD_IDATA_SEG Begin_VxD_IO_Table VTRAPIOD_Port_Table VxD_IO TRAPIO_IDX, VTRAPIOD_IO_Index_Reg VxD_IO TRAPIO_DATA, VTRAPIOD_IO_Data_Reg End_VxD_IO_Table VTRAPIOD_Port_Table VTRAPIOD_Port_Table_Entries equ (($-VTRAPIOD_Port Table)-\ (SIZE VxD_IOT_Hdr)) / (SIZE VxD_IO_Struc) VxD_IDATA_ENDS

  This table uses offsets from the base I/O address as the port address. When the base address of the hardware has been determined, the VxD can update the I/O table and install the handlers:

      ;---------------------------------------------------------------
      ; VTRAPIOD Device Init
      ;
      ; DESCRIPTION:
      ;             Non critical system initialization procedure.
      ; ENTRY :
      ;       EBX = Sys VM handle
      ;
      ; EXIT :
      ;       CLC if everything's A-OK, otherwise STC
      ; USES :
      ;       Flags.
      ;---------------------------------------------------------------

      BeginProc VTRAPIOD_Device_Init

        Trace_Out "VTRAPIOD: Device_Init"

        pushad

        ;
        ; Build an I/O port table for Install Mult_IO Handlers
        ; using the base address.
        ;

        mov           ecx, VTRAPIOD_Port_Table_Entries
        mov           esi, OFFSET32_VTRAPIOD_Port_Table
        mov           edx, VTRAPIOD_Base_IO

      DI_Install_IO_Handlers:
        mov           edi, esi                ; save a copy in EDI
        add           esi, (size VxD_IOT_Hdr)

      DI_Bump_IO_Loop:
        add           [esi.VxD_IO_Port], dx   ; add port base to offset
        add           esi, (size VxD_IO_Struc)
        loop          DI_Bump_IO_Loop

        ;
        ; Tell VMM to trap ports.
        ;

        VMMcall       Install_Mult_IO_Handlers
      ifdef DEBUG
        jnc           SHORT DI_Exit
        Debug_Out     "VTRAPIOD: cannot trap ports!!"
      endif

      DI_exit :
        popad
        ret

      EndProc VTRAPIOD_Device_Init

  When an I/O port within the given range has been accessed, the fault handler dispatches to the associated I/O handler. For this example, the index register simply stores the index if valid (on write) or returns the current index (on read): ;--------------------------------------------------------------------- ; VTRAPIOD_IO_Index_Reg ; ; DESCRIPTION: ; Handles IO trapping. ; ; This is a virtual R/W index register. ; ; ENTRY : ; EBX = VM Handle. ; ECX = Type of I/O ; EDX = Port number ; EBP = Pointer to client register structure ; ; EXIT : ; EAX = data input or output depending on type of I/O ; ; USES : ; FLAGS ;--------------------------------------------------------------------- BeginProc VTRAPIOD_IO_Index_Reg, High_Freq Dispatch_Byte_IO Fall_Through, mov al, bIndex clc ret IIR_Out: cmp al, VTRAPIOD Max_Index ja SHORT IIR_Exit mov bIndex, al IIR_Exit: clc ret EndProc VTRAPIOD_IO_Index_Reg

  The one drawback with this simple I/O trapping interface is that there is a single global virtual device. Multiple VMs can simultaneously (well, almost simultaneously) access this device and may inadvertently affect the processing of another VM by switching the index register while a different VM is updating an indexed data register. This is commonly referred to as device contention, and this VxD must be improved to properly handle contention between VMMs. The next below discusses this topic in greater detail.

  ### Device Contention Management

  When multiple virtual machines attempt to access the same hardware interface and device contention is not handled by a VxD, the VMs probably interact with the hardware in such a way that all the hardware sees is gibberish.

  To avoid these problems, a VxD implements one of the following methods of device contention:

  - A VxD can completely virtualize the hardware interface, buffer the requests, and submit them when the hardware is free.
  - A VxD ean allow only one VM to access the hardware at a time. The hardware will not be visible to other VMs until the hardware is released by the owner.
  - The VM can be terminated for attempting to access the hardware. (Not the most userfriendly or recommended method.)

  The most commonly used method is to allow only one VM to access the hardware at a time. Other VMs cannot access the hardware until it has been released by the owner.

  To implement this form of device contention, all I/O ports for the hardware device are trapped. When a VM accesses a trapped port, the handler routine checks to see whether the device has been assigned to a VM. If a contention is detected, the VxD may display a warning message using the Shell VxD's API and then return with carry set for all reads and writes to the hardware. If there is no current owner, the VxD assigns the device to the VM and disables the I/O trapping for the VM using the Disable_Local_Trapping service. When the VM terminates or when the hardware is explicitly released by the VM, the VxD re-enables the trapping for the VM, using the Enable_Local_Trapping service, and clears the owner status of the hardware.

  The following sample code is contention management in its simplest form: ;--------------------------------------------------------------------- ; VCONTEND_Check_Owner ; ; DESCRIPTION: ; Checks the current VM owner; if none, assigns ; device to VM. If the VM is an owning VM, returns ; carry clear, otherwise it returns carry set. ; ; ENTRY : ; EBX = VM Handle. ; ; EXIT: ; CLC if owner OK, or STC if contention ; ; USES : ; FLAGS ;--------------------------------------------------------------------- BeginProc VCONTEND_Check_Owner, High_Freq push eax mov eax, hOwnerVM or eax, eax jz SHORT CO_Assign_To_VM cmp eax, ebx jne SHORT CO_Failure CO_Success: pop eax clc ret CO_Assign_To_VM: mov hOwnerVM, ebx jmp SHORT CO_Success CO_Failure: pop eax stc ret EndProc VCONTEND_Check_Owner ;--------------------------------------------------------------------- ; VCONTEND_IO_Index_Reg ; ; DESCRIPTION: ; Handles IO trapping. ; This is a virtual R/W index register. ; ; ENTRY : ; EBX = VM Handle. ; ECX = Type of I/O ; EDX = Port number ; EBP = Pointer to client register structure ; ; EXIT : ; EAX = data input or output depending on type of I/O ; ; USES : ; FLAGS ;--------------------------------------------------------------------- BeginProc VCONTEND_IO_Index_Reg, High_Freq call VCONTEND_Check_Owner jc SHORT IIR_Exit Dispatch_Byte_IO_Fall_Through, mov al, bIndex clc ret IIR_Out: cmp al, VCONTEND_Max_Index ja SHORT IIR_Exit mov bIndex, al clc IIR_Exit: ret EndProc VCONTEND_IO_Index_Reg

  Note that with this method of contention management, the hardware remains in the state the last owning VM left it in. You may decide to define an initial state for a VM in the VM control block and update the state when the VM releases the hardware. When a VM acquires the hardware, the state would be copied from the VM's control block to the hardware.

  ### Simulating Hardware

  As demonstrated in the preceding code fragments, it is possible to simulate (or virtualize) hardware through the use of trapped I/O interfaces. The Windows 3.1 Device Driver Kit contains sources to VxDs that simulate hardware such as the Virtual DMA Device and the Virtual COMM Device. You should investigate these sources for examples of more complex interfaces.

  VxDs can use these techniques to translate common hardware interfaces to new or improved hardware interfaces and maintain the backward compatibility of the older platforms for MS-DOS applications.

  To fully virtualize a hardware interface, your VxD may need to incorporate IRQ virtualization and/or DMA virtualization. These topics are covered in Chapters 7 and 8, respectively.

  # Chapter 7
  IRQ Virtualization

  The Virtual Programmable Interrupt Controller Device (VPICD) provides an interface to hook (virtualize) IRQs, query information about the state of a hooked IRQ, simulate hardware interrupts to VMs, share interrupts, and handle interrupts in the System VM with a single ISR interface using the bimodal interrupt interface.

  During initialization, the VPICD configures the PICs (slave and master), hooks the IDT entries, and establishes default handling for non-virtualized IRQs. The PICs are virtualized to all VMs. When a VM masks an interrupt, it is communicating with the VPICD and does not perform I/O directly to the PIC. VPICD provides services to affect the physical state of the PICs. It is strongly recommended that VxDs use this interface to change the physical state of a virtualized IRQ.

  IRQ virtualization is recommended for hardware devices that use hardware interrupts as a form of communication with device drivers. There are several reasons for this recommendation:

  - IRQ virtuaIization is a requirement for proper device contention management.
  - Some devices require immediate interrupt servicing. Interrupt latency caused by nonvirtualized interrupt handling in an ISR in either a TSR or Windows device driver do not satisfy this requirement.
  - VPICD's default IRQ handling is sometimes inappropriate for devices that intend to be "Windows-GUI-only" oriented. IRQs that are unmasked prior to starting Windows are designated as "global." Global interrupts are not appropriate for this implementation.

  The most common complaint of interrupt processing under Windows is the interrupt latency issue introduced by simulating interrupts to VMs. Additionally, you may be interested in monitoring interrupt response from a hardware device before simulating the interrupt to a VM. In these cases, IRQ virtualization is required.

  ### Default VPICD Handling

  Before discussing IRQ virtualization in detail, we need to explain the default operation of VPICD when an interrupt is not virtualized. By default, all IRQs are "virtualized" by VPICD. If the intemzpt was unmasked prior to starting Win386 (or the special case of IRQ 9), the default owner is global. Otherwise, no default owner exists.

  The default hardware interrupt procedure (Hw_Int_Proc) simulates an interrupt to the current VM if the IRQ is unowned. When the IRQ is global, VPICD simulates the interrupt to the current critical section owner or the current VM, if there is no critical section owner. Also, interrupts simulated for global IRQs are nested in the VM until the nesting has been "unwound", but non-owned interrupts are always simulated to the current VM in all circumstances.

  When an interrupt is simulated to a VM (by a default IRQ handler or using the VPICD_Set_Int_Request service), the VM priority is boosted and the IRET procedure is hooked to notify the IRET procedure when the interrupt has been completed. These events only occur when the IRQ is not nested.

  End-of-Interrupt results when the VM issues an EOI to the virtual PIC. The default EOI handler clears the virtual interrupt request and performs a physical EOI using the VPICD_Clear_Int_Request and VPICD_Phys_EOI services respectively.

  By default each unowned or global interrupt procedure has a timeout of SOOms. A VM timeout is scheduled to watch the interrupt processing time in a VM. If the ISR in the VM does not service the interrupt within the specified timeout period, VPICD continues execution as though the ISR had issued an IRET. The timeout is canceled when the VM issues an IRET (or the last IRET in a nested block).

  VPICD simulates a level-triggered PIC. That is, when a virtual EOI occurs another interrupt will be simulated immediately unless the virtual interrupt request has been cleared by the VPICD_Clear_Int_Request service.

  ### IRQ Virtualization and Sharing

  óIRQ VirtualizationÓ

  A VxD can change the default behavior of interrupt processing by virtualizing the IRQ using the VPICD_virtualize_IRQ service. The VxD fills the following structure and calls this service to obtain an IRQ handle:

      VPICD_IRQ_Descriptor  STRUC
      VID_IRQ_Number             dw      ?
      VID_Options                dw      0
      VID_Hw_Int_Proc            dd      ?
      VID_Virt_Int_Proc          dd      0
      VID_EOI_Proc               dd      0
      VID_Mask_Change_Proc       dd      0
      VID_IRET_Proc              dd      0
      VID_IRET_Time_Out          dd      500
      VPICD_IRQ_Descriptor  ENDS

  Some of the elements of this structure require further detail:

  - VID_Hw_Int_Proc contains a pointer to the procedure called when hardware interrupts occur for the specified IRQ. (Required)
  - VID_virt_Int_Proc contains a pointer to the procedure called when interrupts are simulated to the VM for this IRQ. (Optional)
  - VID_EOI_Proc contains a pointer to the procedure called when the hardware interrupt service routine in the VM issues an EOI to the PIC. (Optional)
  - VID_Mask_Change_Proc contains a pointer to the procedure called when the VM changes the mask status of the IRQ on the PIC. (Optional)
  - VID_IRET_Proc contains a pointer to the procedure called when the VM IRETs (or the last IRET of a nested block) from the simulated interrupt. This procedure is also called when a timeout occurs when a VM is servicing an interrupt. (Optional)
  - VID_IRET_Time_Out is the timeout value for a VM to service an interrupt. When the timeout occurs, VPICD reacts as though the VM issued an IRET with the exception that the interrupt has not been physically serviced. (Optional, default is 500ms)

  A VxD must virtualize an interrupt during device initialization. It is recommended that the VxD virtualize the interrupt during Sys_Critical_Init if you are using IRQ 9 to avoid problems introduced when interrupts occur between the Sys_Critical_Init and Device_Init control messages.

  The following sample code demonstrates the use of VPICD services to virtualize an IRQ:

      ;=========================================================================
      ;  I N I T      D A T A
      ;=========================================================================

      VxD_IDATA_SEG

      VIRQD_IRQ Descriptor VPICD_IRQ_Descriptor <, \
                                 OFFSET32 VIRQD_Hw_Int_Proc"\
                                 OFFSET32 VIRQD_EOI_Proc",>
      VxD_IDATA_ENDS

      ;=========================================================================
      ;  I N I T     C O D E
      ;=========================================================================

      VxD_ICODE_SEG

      ;-------------------------------------------------------------------------
      ; VIRQD_Device_Init
      ;
      ; DESCRIPTION:
      ;          Non critical system initialization procedure.
      ; ENTRY :
      ;          EBX = Sys VM handle
      ;
      ; EXIT :
      ;          CLC if everything's A-OK, otherwise STC
      ;
      ; USES :
      ;          Flags.
      ;------------------------------------------------------------------------

      BeginProc VIRQD_Device_Init

        Trace_Out "VIRQD: Device_Init"

        push      eax
        push      edi

        mov       edi, OFFSET32 VIRQD_IRQ_Descriptor
        mov       [edi.VID IRQ Number], VIRQD_Interrupt
        VxDCall   VPICD Virtualize_IRQ
      ifdef DEBUG
        jnc       SHORT @F
        Debug_Out "VIRQD: Unable to virtualize IRQ"
        jmp       SHORT DI_Exit

      @@:
        else
        jc        SHORT DI_Exit
      endif
        mov       hVirtIRQ, eax

      DI_Exit:
        pop       edi
        pop       eax
        ret

      EndProc VIRQD_Device_Init

      VxD_ICODE_ENDS

  When the hardware intenupt occurs, the following procedures simulate the interrupt to the current VM and clear the interrupt when the ISR issues an EOI to the virtual PIC: ;========================================================================= ; H A R D W A R E I N T E R R U P T P R O C E D U R E S ;========================================================================= VxD_LOCKED_CODE_SEG ;------------------------------------------------------------------------ ; VIRQD_Hw_Int_Proc ; ; DESCRIPTION: ; Hardware interrupt handler. Called by VPICD. ; ; ENTRY : ; EAX = IRQ handle ; EBX = current VM handle ; ; EXIT: ; CLC if processed, STC otherwise. ; USES : ; Flags. ;------------------------------------------------------------------------ BeginProc VIRQD_Hw_Int_Proc, High_Freq Trace_Out "" VxDCall VPICD_Clear_Int_Request VxDCall VPICD_Phys_EOI ret EndProc VIRQD_EOI_Proc VxD_LOCKED_CODE_ENDS

  Note that services called during the processing of the Hw Int Proc procedure must be declared asynchronous (see Chapter 2 for a complete list of asynchronous services). If a VxD requires the use of a non-asynchronous service to continue interrupt processing, the VxD must schedule a global event to continue. The debug version of WIN3B6.EXE notifies you when you attempt to call a non-asynehronous service during interrupt processing. Heed the warnings of VMM, lest your ignorance cause the system to crash.

  óShared IRQ ProceduresÓ

  If the hardware platform supports shared interrupts (Micro Channel Architecture) or the device is using an ISA shared interrupt strategy, the IRQ can be virtualized specifying the VPICD_Opt_Can_Share flag in the VID_Options element of the VPICD_IRQ_Descriptor structure. When the hardware interrupt is dispatched to the Hw_Int_Proc, the VxD should determine whether the interrupt was generated by the associated hardware device and, if so, process the interrupt and return with carry clear. If the interrupt was not generated by the supported hardware, the VxD should return immediately with carry clear. VPICD will continue to walk the shared interrupt list until a VxD responds with carry set.

  Note that the VxD cannot assume that subsequent calls to other callback procedures specified in the IRQ descriptor structure are the result of an interrupt for the associated hardware device. The VxD should set a flag when it has simulated an intenupt to a VM and test against this flag when notifications from VPICD are processed. When the VxD processes the EOI_Proc it should clear the flag, perform the necessary EOI procedures, and then return.

  ### Dispatching IRQs to a VPM

  The example below demonstrates a very simple IRQ virtualization. The VIRQD_Hw_Int_Proc simply sets the interrupt request for the current VM and returns. When the ISR performs an EOI to the PIC, the VIRQD_EOI_Proc clears the interrupt request and performs a physical EOI.

  When a VxD requests an interrupt for a VM using the vPICD_Set_Int aequest service, the interrupt simulation may not occur immediately. There are several conditions that do not allow an interrupt to be simulated immediately:

  - Interrupts are disabled in the VM.
  - The virtual IRQ is masked in the VM.
  - A higher priority virtual IRQ is already in service.
  - The virtual machine is suspended.

  In these cases, the interrupt is simulated as soon as the conditions are met.

  Note that using VPICD_Set_Int_Request does not guarantee that an interrupt will be simulated to a VM. For example, if a VM has masked and never unmasks the IRQ, the interrupt will not be simulated. Additionally, a call to VPICD_Clear_Int_Request before the interrupt has been simulated prevents the VM from receiving the intemzpt.

  The example also does not demonstrate proper techniques when processing hardware interrupts for device contention management. The VIRQD_Hw_Int_Proc should be expanded to first determine whether an owner VM exists and then simulate the interrupt to that VM, as follows:

      ;------------------------------------------------------------------------
      ; VIRQD_Hvr_Int_Proc
      ;
      ; DESCRIPTION:
      ;          Hardware interrupt handler. Called by VPICD.
      ;          simulates the interrupt to the hardware owner or
      ;          to the current VM if unowned.
      ;
      ; ENTRY :
      ;          EAX = IRQ handle
      ;          EBX = current VM handle
      ; EXIT :
      ;          CLC if processed, STC otherwise.
      ;
      ; USES :
      ;          EBX, Flags.
      ;------------------------------------------------------------------------

      BeginProc VIRQD_Hw_Int_Proc, High_Freq

        Trace_Out " < i "
        cmp       hOwnerVM, 0
        je        SHORT HIP_Setlt
        mov       ebx, hOwnerVM

      HIP_Set_It:
        VxDCall   VPICD_Set_Int_Hequest

        clc
        ret

      EndProc VIRQD_Hw_Int_Proc

  ### Servicing Interrupts in a VxD

  To reduce the interrupt latency of servicing a hardware device contained in ISR code of a VM, a VxD can service intenupts directly during processing of the Hw_Int_Proc procedure. In cases where a steady stream of data is processed, the VxD should buffer the information from the hardware device and provide the information to the owning VM in chunks.

  A Hw_Int_Proc for servicing an interrupt directly might be similar to this:

      ;------------------------------------------------------------------------
      ; VIRQD_Hw_Int_Proc
      ;
      ; DESCRIPTION:
      ;             Hardware interrupt handler. First, EOI the PIC
      ;             so we avoid missing another IRQ generated by the
      ;             device. Call a procedure elsewhere in the VxD to
      ;             service the hardware device and then return.
      ;
      ; ENTRY :
      ;             EAX = IRQ handle
      ;             EBX = current VM handle
      ;             Interrupts are disabled.
      ;
      ; EXIT :
      ;             CLC if processed, STC otherwise.
      ;
      ; USES:
      ;             EBX, Flags.
      ;------------------------------------------------------------------------

      BeginProc VIRQD Hw_Int_Proc, High_Freq

        Trace_Out     " < i > "
        VxDCall       VPICD_Phys EOI
        call          VIRQD_Service_Hardware
        clc
        ret

      EndProc VIRQD_Hw_Int_Proc

  In this example, VIRQD_Hw_Int_Proc does not set the interrupt request for the VM. The VIRQ_Service_Hardware procedure may set an interrupt request to the owning VM when a threshold has been reached. This is strictly determined by the requirements of your hardware and the maximum amount of CPU load you wish to generate. The VxD could also use some other form of communication to a driver in a VM, such as nested execution or updating global memory buffers.

  Additionally, the VIRQ_EOI_Proc would not perform a physical EOI of the PIC. Its only requirement would be to clear the interrupt request status for the VM if simulated interrupts are used to communicate with the VM's device driver.

  Note that interrupt simulation is an expensive procedure. Ring transitions and VM context switches are often a result of interrupt simulation, and reducing simulated interrupt generation w'ill help reduce the total burden of the CPU.

  ### Bimodal Interrupt Handlers

  Bimodal interrupt handlers are a new feature of the Windows 3.1 VPICD that allows a Windows device driver (or DLL) to service inten-upts without waiting for VPICD to simulate an interrupt to the System VM and can avoid the associated delays of VM focus changes and VM event processing. Interrupt latency can be reduced using these services while maintaining a common code base for the ISR under Standard and Enhanced Mode Windows. Note that servicing interrupts directly in a VxD (as discussed in the preceding section ) yields minimal interrupt latency.

  The following services are available through the PM API of the VPICD to install and remove bimodal interrupt handlers:

      VPICD_API_Get_Ver                  retrieve the VPICD version
      VPICD_Install_Handler              install a bimodal IRQ handler
      VPICD_Remove_Handler               remove a bimodal IRQ handler

  The VPICD API can only be accessed via the protected mode API entry point. It is not available to V86 VMs. To access the VPICD API, a VM obtains the API entry point:

      VPICD_Device_ID                    EQU 0003h
      VPICD_API_Get_Ver                  EQU 0000h
      VPICD_Install_Handler              EQU 0001h
      VPICD_Remove_Handler               EQU 0002h
      VPICD_Call_At_Ring0                EQU 0003h

        xor                 di, di
        mov                 es, di
        mov                 ax, 1684h                  ; get API entry point
        mov                 bx, VPICD_Device_ID        ; of the VPICD
        int                 2fh
        mov                 word ptr lpVPICDEntry, di
        mov                 word ptr lpVPICDEntry + 2, es
        mov                 ax, es
        or                  ax, di
        jz                  SHORT No_VPICD_API

  Under Windows 3.0, the VPICD entry point will be NULL, because it does not support any API functionality. If the entry point is not NULL, VPICD's version can be obtained:

      Get_VPICD_Version:
        mov                 ax, VPICD_API_Get_Ver
        call                dword ptr lpVPICDEntry
        jc                  SHORT VPICD_Error
        cmp                 ax, 30Ah
        jbe                 SHORT VPICD_Error

  A DLL installs and removes a bimodal IRQ handler using the VPICD API Install and VPICD_API_Remove functions respectively:

      Install_Bimodal_Handler:
        les                 di, lpBIS          ; pointer to BIS struct.
        mov                 ax, VPICD_Install_Handler
        call                dword ptr lpVPICDEntry
        jc                  SHORT VPICD Error

      Remove_Bimodal_Handler:
        les                 di, lpBIS          ; pointer to BIS struct.
        mov                 ax, VPICD_Remove_Handler
        call                dword ptr lpVPICDEntry
        jc                  SHORT VPICD_Error

  In these routines, the Bimodal_Int Struc (BIS) is referenced. This structure has the following format:

      Bimodal_Int_Struc  STRUC
        BIS_IRQ_Number           dw ?
        BIS_VM_ID                dw 0
        BIS_Next                 dd ?
        BIS_Reserved1            dd ?
        BIS_Reserved2            dd ?
        BIS_Reserved3            dd ?
        BIS_Reserved4            dd ?
        BIS_Flags                dd 0
        BIS_Mode                 dw 0
        BIS_Entry                dw ?
        BIS_Control_Proc         dw ?
                                 dw ?
        BIS_User_Mode_API        dd ?
        BIS_Super_Mode_API       dd ?
        BIS_User_Mode_CS         dw ?
        BIS_User_Mode_DS         dw ?
        BIS_Super_Mode_CS        dw ?
        BIS_Super_Mode_DS        dw ?
        BIS_Descriptor_Count     dw ?
      Bimodal_Int_Struc  ENDS

  The field definitions of this structure are detailed as follows:

      BIS_IRQ_Number     VPICD installs a bimodal intenupt for the IRQ specified
                         by this field when the VPICD_Install_Handler API is
                         called.

      BIS_VM_ID          Contains the current VM ID when the inten`upt handler
                         specified by BIS_Entry is called.

      BIS_Next           Currently not used by the Windows 3.1 VPICD.

      BIS_Flags          Must be set to zero.

      BIS_Mode           Set to 0 to indicate user mode or 4 to indicate supervisor
                         mode. This value can be used as an offset to obtain the
                         appropriate user-mode or super-mode BIS API handler.
                         (Set by VPICD when calling the procedures defined by the
                         BIS_Entry and BIS_Control_Proc offsets.)

        mov      bx, es:[di.BIS_Mode)        ; mode 0=user, 4=super
        call     es:[bx][di.BIS_User_Mode_API]

      BIS_Entry          Specifies the offset of the ISR from the CS specified in the
                         BIS_User_Mode_CS field. When VPICD calls the
                         interrupt handler for interrupt servicing, ES:DI points to
                         this structure. (Filled by caller for the call to
                         VPICD_Install_Handler.)

      BIS_Control_Proc   Specifies the offset of the control procedure from the CS
                         specified in the BIS_User_Mode_CS field. The control
                         procedure is currently not used by the Windows 3.1
                         VPICD, but should point to a dummy control procedure
                         that performs a far return. (Filled by the caller for
                         VPICD_Install_Handler.)

      BIS_User_Mode_API  Specifies the far address of the user-mode API procedure
                         entry point. (Filled by VPICD after a call to
                         VPICD_Install_Handler API.)

      BIS_super_Mode_API Specifies the far address of the supervisor mode API
                         procedure entry point. (Filled by VPICD after a call to the
                         VPICD_Install_Handler API.)

      BIS_User_Mode_CS  Specifies the selector of the user-mode code segment of
                        the interrupt handler. The BIS Entry and
                        BIS_Control_Proc offsets must be relative to the code
                        selector specified by this field. (Filled by caller for
                        VPICD_Install_Handler.)

      BIS_User_Mode_DS  Specifies the selector of the user-mode data segment of the
                        interrupt handler. The Bimodal Int_Struc structure should
                        be located in this segment. (Filled by caller for
                        VPICD_Install_Handler.)

      BIS_super_Mode_CS VPICD stores the GDT alias of the user-mode CS selector
                        in this field after a call to VPICD_Install_Handler.

      BIS_Super_Mode_DS VPICD stores a GDT alias of the user mode CS selector in
                        this field after a call to VPICD_Install_Handler.

      BIS_Descriptor_Count Specifies the number of EBIS_Sel_Struc structures
                           immediately following the Bimodal_Int_Struc
                           structure. VPICD creates a GDT alias for each of the
                           selectors in the structures that follow.

       EBIS_Sel_Struc STRUC
           EBIS_User_Mode_Sel      dw ?
                                   dw ?
           EBIS_Super_Mode_Sel     dw ?
       EBIS_Sel_Struc ENDS

      EBIS_User_Mode_Sel   User mode selector

      EBIS_Super_Mode_Sel  GDT alias of selector created by VPICD after a call to
                           VPICD_Install_Handler.

  VPICD automatically creates GDT aliases for the ISR code and data segments as specified in BIS_User_Mode_CS and BIS_user_Mode_DS, respectively. Additionally, the caller can request that VPICD create GDT aliases for a number of selectors specified by BIS_Descriptor_Count. The user-mode selectors are filled in an array of the EBIS_Sel_Struc structures immediately following the Bmodal_Int_Structure. The associated GDT aliases are returned in the EBI_Super_Mode_Sel element of each of the EBIS_Sel_Struc structures. For example, the Windows 3.1 COMM driver uses this functionality to create GDT aliases of the receive and transmit queues.

  A DLL creates a Bimodal_Int_Struc and fills the appropriate fields. When the IRQ occurs, VPICD calls the ISR directly at ring 0, regardless of the current VM. On entry to the ISR, the CS is set to the GDT alias of the ISR code segment and ES:DI is set to the GDT alias of the Bimodal_Int_Struc. If this structure is located in the data segment, you can make the data addressable by moving ES into DS.

  The ISR executes at ring 0 (CPL=0) through a 16-bit GDT code segment alias. As with calling TSR code directly from a VxD, the provided stack is a Use32 segment and parameter passing must reference the stack using 32-bits (ESP and EBP). The ISR cannot switch to a different stack unless a ring 0 stack selector is created. Note that a DLL cannot legally create such a selector.

  The ISR must return from the procedure with a far return and carry clear if the IRQ was serviced or carry set if the IRQ was not serviced. When the ISR is called directly by VPICD, it must not manipulate the PIC directly. Instead, VPICD provides services through the BIS Super Mode API procedure to perform these operations:

      BIH_API_EOI        EQU 0000h
      BIH_API_Mask       EQU 0001h
      BIH_API_Unmask     EQU 0002h
      BIH_API_Get_Mask   EQU 0003h
      BIH_API_Get_IRR    EQU 0004h
      BIH_API_Get_ISR    EQU 0005h
      BIH_API_Call_Back  EQU 0006h

      BIH_API_EOI           Equivalent to calling VPICD_Phys_EOI.

      BIH_API_Mask          Equivalent to calling the VPICD_Physically_Mask
                            service.

      BIH_API_Get_IRR       Equivalent to calling the VPICD_Test_Phys_Request
                            service. Returns carry set if the physical interrupt request is
                            set.

      BIH_API_Get_ISR       Retrieves the in-service state of the IRQ. Returns with
                            carry set if the IRQ is in service.

      BIH_API_Call_Back     Uses the Call_Priority_VM_Event service to schedule an
                            event for the target VM specified BX. When the event
                            callback is processed, VPICD will use nested execution
                            services to simulate a far call to the address specified by
                            CX:DX.

  The BIH_API_Call_Back procedure is useful for calling routines that do not have GDT aliases or that must be executed in a specific VM. A common use of this service is to call a routine in the driver that posts a message using the PostMessage() Windows API.

  Note: VMM schedules event services to process the callback in the specified VM. The callback is not executed synchronously. A driver should not post more than one event without notification that the event has been processed. If multiple events are posted without verifying that outstanding callbacks already exist, the VMM event services may run out of resources and crash the system.

  # Chapter 8
  Virtualized DMA

  The Virtual DMA Device (VDMAD) provides services that allow a VxD to take control of a DMA channel. A VxD using these services can intercept the DMA requests and modify the VM state causing the VM to believe that the request completed. Also, it is possible to translate or modify the VM's request before the physical state of the DMA controller is updated. Additionally, by using these services, a VxD can add another level of hardware contention management or indirectly replace portions of VDMAD's default handling.

  All DMA channels are virtualized by VDMAD to map DMA requests by drivers to the physical hardware. VDMAD validates the memory region supplied by the driver, and if necessary, allocates the region from an internal DMA buffer.

  Certain restrictions imposed by the DMA controller require the region management of VDMAD:[Footnote:For simplicity, this discussion only reference the hardware with the lowest common denominator, the 8253 DMA controller. Other controllers may support advanced features, but for proper coverage by your VxD, this controller interface constrains the functionality of the DMA interface.]

  - The DMA controller can only understand contiguous physical memory addresses.
  - The DMA controller can not cross 64k boundaries, because the page register does not auto increment.
  - The DMA controller has an address limit of 16 MB

  VDMAD breaks up requests into partial DMA transfers to satisfy these requirements. DMA buffers submitted using the auto-init mode of the DMA controller cannot be broken; consequently, these requests must be submitted with regions adhering to the restrictions.

  For this reason, auto-init-mode DMA requires special memory management on behalf of the device driver.

  Note that this discussion does not cover advanced DMA topics, such as bus-mastering devices and DMA controllers supporting scatter-gather.

  ### Physical State vs. Virtual State

  As a VM programs the DMA controller, the controller s virtual state is updated, but state is not submitted to the hardware until the VM unmasks the channel. This is important to remember when you are debugging drivers using DMA. To display the channel status, use the debug version of Win386 supplied with VxD-Lite and query VDMAD.

  After the VM has unmasked the channel, VDMAD attempts to lock the memory region, as programmed by the VM. If it is unsuccessful, VDMAD buffers the DMA transfer and modifies the DMA controller s physical state.

  VDMAD uses the VPICD_Hw_Int_Proc service to provide a watchdog event to poll for the DMA controllers terminal count when non-auto-init-mode DMA transfers are requested. When the DMA controller has completed the request, the necessary buffers are updated (if a read operation was requested and buffers were allocated) and the VM's virtual DMA state is updated to reflect the completed transfer.

  A VxD can modify the DMA controllers virtual and physical states using the VDMAD_Set_Virt_State and VDMAD_Set_Phys_State services, which are usually incorporated with a handle of DMA channel that has been virtualized by a VxD.

  ### DMA Virtualization

  A VxD uses DMA virtualization to add functionality to the base support of VDMAD. A VxD can use this virtualization to change the virtual state before the request is submitted to the hardware. To virtualize a DMA channel, a VxD uses the VDMAD_Virtualize_Channel service:

      ;
      ; Tell VDMAD that we want to know about this
      ; DMA controller.
      ;

        xor           eax, eax
        mov           [gdwDMAHandle], eax

        movzx         eax, gbDMAChannel
        mov           esi, OFFSET32 VSIMPLED_Virtual_DMA_Trap

        VxDCall       VDMAD_Virtualize_Channel
        mov           [gdwDMAHandle], eax
        jc            SHORT VDC_Exit_Failure

  When a VM has changed the virtualized DMA controller s mask state, it calls the supplied procedure, in this case VSIMPLED_Virtual_DMA_Trap.

  The VxD can modify the virtual state of the VM and then call the default handler, VDMAD_Default_Handler, to allow VDMAD to continue the region management as follows:

      ;------------------------------------------------------------------
      ; VSIMPLED_Virtual_DMA_Trap
      ;
      ; DESCRIPTION:
      ;             Forces DMA block mode and then calls the default
      ;             DMA handler.
      ;------------------------------------------------------------------

      BeginProc    VSIMPLED_Virtual_DMA_Trap, High_Freq

          VxDCall      VDMAD_Get_Virt_State
          test         dl,DMA_requested
          jz           SHORT VDT_Exit
          test         dl,DMA_masked
          jnz          SHORT VDT_Exit

      ; Force block mode DMA,channel is requested and
      ; unmasked by the VM.

          and          dl,NOT (DMA_mode_mask)
          or           dl, DMA_block_mode

          xor          dh, dh
          VxDCall      VDMAD_Set_Virt_State

      VDT_Exit :
          VxDCall      VDMAD_Default_Handler
          ret

      EndProc VSIMPLED_Virtual_DMA_Trap

  If necessary, a VxD can handle the actual DMA buffer translation and program the þ. physical state of the DMA controller. This type of virtualization requires the use of the VDMAD buffer copy and region management services (listed in Appendix A).

  Additionally, a VxD can translate the DMA request to a replacement interface, such as those supplied by the PCMCIA hardware implementations. Again, the VxD must virtualize the DMA channel and process the notifications from VDMAD.

  Although some of the buffer management details are discussed in the next section, you should investigate the VDMAD sources provided in the Microsoft Windows 3.1 Device Driver Kit for code samples and to develop a better understanding of the operation of VDMAD.

  ### DMA Region Mapping

  As already mentioned, the primary purpose of VDMAD is to buffer DMA requests and to map the regions to memory accessable by the DMA controller. DMA region mapping is automatically performed by VDMAD on a non-virtualized channel when the DMA channel is unmasked. A VxD virtualizing a DMA channel can use these services without additional code overhead simply by calling the VDMAD_Default_Handler. When a non-standard interface is implemented, some or all of the region mapping services of VDMAD will be needed.

  To request a DMA buffer from VDMAD and copy information from a VM to this buffer, the VxD uses the VDMAD_Request_Buffer and VDMAD_Copy_To_Buffer services:

        ;
        ; Request a buffer from VDMAD and copy from VM
        ; On entry, EAX is DMA handle, EBX is VM handle.
        ;

        VxDCall      VDMAD_Get_Virt_State
        push         edx                      ; save mode for later
        push         ebx                      ; save VM for later

        ; ESI = linear address
        ; ECX = count
        ; DL/DH = mode/flags

        test         dl, DMA requested
        jnz          SHORT Buffer_New

        test         dl, DMA_masked
        jnz          SHORT Buffer_Clean_Up

        VxDCall      VDMAD_Request_Buffer
        jc           SHORT Error_No_Buffer

        ; EDX now contains the physical address of
        ; the DMA buffer..

        test         dl, DMA_type_read
        jz           SHORT Dont_Copy

        ; EBX = buffer handle
        ; ESI = linear region
        ; ECX = size
        ; EDI = offset

        xor        edi, edi
        VxDCall    VDMAD Copy_To_Buffer
        jc         SHORT Error_Copy

  To prepare the hardware state, the VxD updates the region information and programs the physical state to the DMA controller. The VxD starts DMA transfer by unmasking the channel:

      Dont_Copy:
        pop        ebx
        VxDCall    VDMAD_Set_Region_Info
        pop        edx
        VxDCall    VDMAD_Set_Phys_State

        ; Unmask the DMA channel to begin the transfer

        VxDCall    VDMAD_UnMask_Channel

  Note that these code fragments are very simple and incomplete. For instance, the VxD does not check to see whether the region can be locked by using the VDMAD_Lock_DMA_Region service before requesting the buffer from VDMAD.

  When a DMA channel is unmasked using the VDMAD_UnMask_Channel service, the ownership of the DMA channel is assigned to the requesting VM. VDMAD sets up the watchdog event to modify the virtual channel state when the terminal count is reached for non-auto-init-mode transfers. When the watchdog event determines that the channel has reached terminal count, VDMAD virtually masks it. If the operation was a DMA write operation, the buffer is copied to the VM's linear address, as supplied with VDMAD_Set_Region_Info. The virtual count register is updated, the channel is physically masked, and the channel owner is set to NULL.

  ### Avoiding VDMAD Interference

  VDMAD always attempts to complete the DMA transfer when the channel has been unmasked by using the VDMAD_UnMask_Channel service. To completely control the DMA channel in your VxD, you can virtualize the DMA channel using a NULL handling procedure and then program the DMA controller directly from your VxD. VDMAD will continue to trap the I/O range for the controller but will not update the physical state. Alternatively, you can provide a virtual DMA handling procedure and program the controller directly by using the virtual controller state information as provided by VDMAD. When using this implementation, you must avoid VDMAD services that affect the physical state or make assumptions about the ownership of the channel. Also, you need to resolve contention by other VMs in your procedure. Consult the VDMAD sources for further details.

  # Chapter 9
  VKD and Keyboard Processing

  The Virtual Keyboard Driver (VKD) provides an interface to the keyboard that allows a VxD to trap for hot keys, simulate keystrokes into a VM, and simulate a paste operation from a supplied buffer into a VM. This interface can be used to force certain actions in a VxD or to serve as form of communication between a VxD and an active application in a VM.

  ### Hot Keys

  Hot keys are registered with the VKD through the VKD_Define_Hot_Key service. Hot keys are enabled and disabled on a per-VM basis using the VKD_Local_Enable_Hot_Key and VKD_Local_Disable_Hot_Key services when the Local_Key flag is specified, as follows:

      ;
      ; Define hot keys for ctrl-pgup and ctrl-pgdn
      ;
        mov            al, 49h                ; page-up
        mov            ah, ExtendedKey_B
        ShiftState     ,
        mov            cl, CallOnPress+ CallOnRepeat + Local_Key
        mov            esi, OFFSET32 VSIMPLED_Hot_Key_Handler
        xor            edx, edx
        xor            edi, edi
        VxDCall        VKD_Define_Hot_Key
        jc             SHORT Exit_Failure
        mov            ghhkCtrlPgUp, eax

        mov            al, 51h                ; page-down
        mov            ah, ExtendedKey_B
        ShiftState     ,
        mov            cl, CallOnPress+ CallOnRepeat + Local_Key
        mov            esi, OFFSET32 VSIMPLED_Hot_Key_Handler
        xor            edx, edx
        xor            edi, edi
        VxDCall        VKD_Define_Hot_Key
        jc             SHORT Exit_Failure
        mov            ghhkCtrlPgDn, eax

  To disable these keys by default, use the VKD Local Disable Hot Key service during the Sys VM_Init and VM Critical_Init message processing:

      VSIMPLED_Sys_VM_Init LABEL NEAR
      BeginProc   VSIMPLED_VM_Critical_Init

        mov            eax, ghhkCtrlPgUp
        VxDCall        VKD_Local_Disable_Hot_Key
        mov            eax, ghhkCtrlPgDn
        VxDCall        VKD_Local_Disable_Hot_Key
        clc
        ret

      EndProc VSIMPLED_VM_Critical_Init

  Once a hot key has been enabled in a VM the VxD receives a notification from VKD whenever the hot key is pressed and processes it accordingly:

      BeginProc VSIMPLED_Hot_Key_Handler

        push eax

        ; Turn off hot key mode in case we're going
        ; to expand this to force keys. Don't want
        ; to be in hot key mode when forcing keys
        ; to a VM.

        VxDCall VKD_Cancel_Hot_Key_State

        cmp             al, 49h
        jne             SHORT HK_PgDn

        ;
        ; Ctrl-Pg Up pressed...
        ;

        Trace_Out       "Control-Pg Up pressed in VM #EBX"
        jmp             SHORT HK_Exit

      HK_PgDn :

        ;
        ; Ctrl-Pg Dn pressed...
        ;

        Trace_Out       "Control-Pg Dn pressed in VM #EBX"

      HKKH_Exit :
        pop             eax
        ret

      EndProc VSIMPLED_Hot_Key_Handler

  ### Simulating Keystrokes to VMs

  VKD provides services to force keys to a VM's keyboard buffer, so that the VM reacts as the key had been pressed on the physical keyboard. The buffer passed to the VKD_Force_Keys service contains actual keyboard scan eodes, such as the "key down," "key repeat," and "key up" codes.

      ;
      ; This code snippet just forces Pg Dn and Pg Up
      ; to the VM in place of Ctrl-Pg Dn and Ctrl-Pg Up.
      ;

      ForceKey_Buffer_Down label byte
        db 51h, D1h
      ForceKey_Buffer_Down_Len equ $-ForceKey_Buffer_Down

      ForceKey_Buffer_Up label byte
        db 49h, C9h
      ForceKey_Buffer_Up_Len equ $-ForceKey_Buffer_Up

      BeginProc VSIMPLED Hot Key_Handler

        push             eax

        ;
        ; Don't want to be in hot key mode
        ; when forcing keys to a VM.
        ;

        VxDCall          VKD_Cancel_Hot_Key_State

        cmp              al, 49h
        jne              SHORT HK_PgDn

        ;
        ; Ctrl-Pg Up pressed...
        ;

        Trace_Out "Control-Pg Up pressed in VM #EBX"

        mov              ecx, ForceKey_Buffex_Up_Len
        lea              esi, ForceKey_Buffer_Up_Len
        jmp              SHORT HK_ForceEm

      HK_PgDn:

        ;
        ; Ctrl-PgDn pressed...
        ;

        Trace_Out        "Control-PgDn pressed in VM #EBX"
        mov              ecx, ForceKey_Buffer_Down_Len
        lea              esi, ForceKey_Buffer_Down

      HK_ForceEm:
        VxDCall          VKD_Force_Keys
      IFDEF DEBUG
        jnc              SHORT @F
        Debug_Out        "VKD Force Keys failed!"

      @@:
      ENDIF
        pop              eax
        ret

      EndProc VSIMPLED_Hot_Key_Handler

  Using the force keys service is quite simple, but determining which scan codes to send is probably the most time-consuming part of using this interface.

  # Chapter 10
  Writing VxDs in C

  The concept of writing VxDs in 'C' has been widely misunderstood. Writing VxDs in 'C' is not impossible -- on the contrary, you can do it without a great deal of grief. Forget everything anyone has every told you about writing VxDs in 'C' and open your mind. VxDs wrtten in 'C' are the wave of the future, not just a passing fad.

  VMM does not look in the object code of VxDs for magical embedded notations to determine whether the code was generated by a 'C' compiler or the magical MASM 5.lOB assembler. When a good 386 32-bit 'C' compiler generates the necessary code, the LINK3B6 linker will link the objects and generate a proper executable, which can be called a VxD.

  The main hurdle to overcome when writing VxDs in 'C' is that a great portion of VMM services require either parameter passing using registers or that the mystical dynalinking macro must be used to generate the code to call VxD or VMM services. Additionally, services declared by VxDs are created with tables hidden by the VMM.INC macros and the actual procedure entry points are renamed with a new prefix. But that doesn't mean that it's time to give up and return to assembly, only that you may not be able to write all of your VxD in 'C'. Some assembly may be required: I affectionately refer to this as MASM- tape.

  The limitations and restrictions of writing a VxD in 'C' include the following

  - because most VxDs have been written in assembly, interfacing to these VxDs requires external procedures written in assembly.
  - Some of the debugging functionality (call logging, for example) is not available to VxD procedures written in 'C'.
  - Testing and debugging is more difficult, because you must rely on the compiler code generation instead of an assembler.

  ### Segment Attributes

  VxD segments require the following specific attributes:
  - All code and data segments are USE32 with the exception of the Real Mode Initialization segment, which is the only USE16 segment in the VxD executeable (excluding the stub executable).
  - Initialization code and data are defined by the _ITEXT and _IDATA segments, respectively. These code segments have the segment class definition of ICODE.
  - Pageable code and data are defined by the _TEXT and _DATA segments and have a segment class definition of PCODE.
  - Locked code and data are defined by the _LTEXT and _LDATA segments and have a segment class definition of LCODE.
  - Because VxDs require flat model, the 'C' compiler in question must be able to generate 32-bit flat model code.

  Most compilers support the #pragma code_seg and #pragma data_seg directives. The following directives will define the necessary segments and classes:

      // code and data segment directives for init code
      #pragma code_seg( "_ITEXT", "ICODE" )
      #pragma data_seg( "_IDATA", "ICODE" )

      // code and data segment directives for pageable code
      #pragma code_seg( "_TEXT", "PCODE" )
      #pragma data_seg( "_DATA", "PCODE" )

      // code and data segment directives for locked code
      #pragma code_seg( "_LTEXT", "LCODE" )
      #pragma data_seg( "_LDATA", "LCODE" )

  When developing the samples in 'C' for this book, I experienced problems with the WATCOM C/386 compiler using the #pragma code_seg directive and was forced to use command line options to define the segment and class names (see the sample makefiles for more information). Also, some 'C' compilers may not support multiple segment declarations in a single module. You may be required to create one module for initialization code and data, another for locked code and data and another for pageable code and data.

  ### A 'C' -callable Wrapper for VMM

  A VxD entry point is defined in the Device Declaration Block (DDB) as defined in Chapter 1. The DDB is exported using a.DEF file. A typical export is as follows:

      EXPORTS
        VSIMPLED_DDB @1

  In order to maintain compatibility with this naming conventionH, the compiler must not generate the 'C'-style underscore prefix. The WATCOM C/386 compiler provides an option for disabling this namiing convention.

  The DDB structure, as defined using 'C', is as follows:

      #define DDK_Version 0x30A

      typedef struct tagVxD_Desc_Block
      {
        DWORD DDB_Next ;                              // VMM reserved field
        WORD DDB_SDK_Version                          // VMM reserved field
        WORD DDB_Req_Device_Number ;                  // Required device number
        BYTE DDB_Dev_Major_Version ;                  // Major device number
        BYTE DDB_Dev_Minor_Version ;                  // Minor device number
        WORD DDB_Flags ;                              // Flags init calls complete
        BYTE DDB_Name[ 8 ] ;                          // Device name
        DWORD DDB_Init_Order ;                        // Initialization Order
        DWORD DDB_Control_Proc ;                      // Offset of control procedure
        DWORD DDB_V86_API_Proc ;                      // Offset of API procedure
        DWORD DDB_PM_API_Proc ;                       // Offset of API procedure
        DWORD DDB_V86_API_CSIP ;                      // CS:IP of API entry point
        DWORD DDB_PM_API_CSIP ;                       // CS:IP of API entry point
        DWORD DDB_Reference_Data ;                    // Ref. data from real mode
        DWORD DDB_Service_Table_Ptr ;                 // Pointer to service table
        DWORD DDB_Service_Table_Size ;                // Number of services

      } DDB ;

  The following example declares a DDB within a 'C' module:

      #include <vmm.h>
      #include "vsimpled.h"

      #pragma data_seg( "_LDATA", "CODE" )

      // ============================================================
      // V I R T U A L      D E V I C E     D E C L A R A T I O N
      // ============================================================

      DDB VSIMPLED_DDB = { NULL,                  // must be NULL
                           DDK_Version,           // DDK_Version
                           VSIMPLED_Device_ID,    // Device ID
                           VSIMPLED_Major_Ver,    // Major Version
                           VSIMPLED_Minor_Ver,    // Minor Version
                           NULL,
                           "VSIMPLED",
                           Undefined_Init_Order,
                           (DWORD) vmmwrapVxDControlProc,
                           NULL,
                           NULL,
                           NULL,
                           NULL,
                           NULL,
                           NULL,
                           NULL } ;

  To provide an interface to the register parameters for VxD control procedures, an assembly wrapper is necessary. This procedure creates a 'C' stack frame and calls the associated procedure as defined in a dispatch table:

      //
      // This table is used by the vmmwrap VxD Control Proc defined
      // in VMMWRAP.ASM. It lists the messages and associated
      // dispatch functions, it must be terminated with -1 and NULL.
      //

      DISPATCHINFO alpVxDDispatchProcs[} =
        { Create_VM,                     VSIMPLED_Create_VM,
          Sys_Critical_Init,             VSIMPLED_Sys_Critical_Init,
          Device_Init,                   VSIMPLED_Device_Init,
          -1,                            NULL } ;

  When the VxD control procedure is called by VMM, the vmmwrapVxDControlProc (provided by VMMWRAP.ASM) walks this table and dispatches the system message to the associated procedure. Note that vmmwrapVxDControlProc uses a linear search algorithm; consequently, the least-frequent system events should be located at end of the table. Some of the dispatch functions have slightly different prototypes, not listed here becausse the sample sources demonstrate their use and the VMMWRAP.ASM code is well documented.

  The following code excerpt demonstrates a VxD initialization procedure as written in 'C':

      #pragma data_seg( "_IDATA", "ICODE" )

      // ============================================================
      //                 I C O D E
      // ============================================================

      //-------------------------------------------------------------

      // BOOL VSIMPLED_Device_Init
      //
      // Description:
      //             This is a non-system critical initialization procedure.
      //             IRQ virtualization, I/O port trapping, and VM control
      //             block allocation can occur here.
      //
      //             Again, the same return value applies... TRUE for success,
      //             FALSE for error notification.
      //
      // Parameters:
      //             DWORD hVM
      //                 System VM handle
      //
      //             PSTR pCmdTail
      //                 pointer to WIN.COM's command tail
      //
      //             PCRS_32 pCRS
      //                 pointer to System VM client register structure
      //
      //-----------------------------------------------------------------

      BOOL CDECL VSIMPLED_Device_Init
      (
        DWORD      hVM,
        PSTR       pCmdTail,
        PCRS_32    pCRS
      )
      {
        UNUSED_PARAM( hVM ) ;
        UNUSED_PARAM( pCmdTail ) ;
        UNUSED_PARAM( pCRS ),

        vmmTrace Out( "VSIMPLED_Device_Init\r\n" ) ;

        return ( TRUE ),

      } // end of VSIMPLED Device Init()

  óWrapping VxD ServicesÓ

  As mentioned earlier, VxD service calls to other VxDs or VMMs use the lnt 20h dynalink interface. Embedding this code throughout your VxD is inefficient, and some form of 'C' to assembly interface is necessary with some services because of register parameter passing.

  VMMWRAP.ASM defines a large number of 'C' callable routines that convert stack parameters into the correct register parameter interfaces used by the various services and return the results of the serviee call. For example, the VMM service List Create uses the ECX, EAX, and ESI registers to define a node size and tlags and to return a handle to the list. It then becomes necessary to provide an C-callable interface: ; DWORD PASCAL vmm List Create( UINT u Node Size, UINT u Flags ) ; ; DESCRIPTION: ; Creates a new list structure. ; ; PARAMETERS: ; UINT uNodeSize ; ; UINT uFlags ; Specifies the creation flags, it can be a ; combination of the following values: ; ; _LF_Alloc_Error, LF_Async, LF_Use_Heap ; ; RETURN VALUE : ; DWORD ; handle to the list or NULL if failure ;------------------------------------------------------------------ BeginProc vmmListCreate, PUBLIC uFlags equ [ebp + 8] uNodeSize equ [ebp + 12] push ebp mov ebp, esp push esi push ecx mov ecx, uNodeSize mov eax, uFlags VMMCall List_Create pop ecx mov eax, esi pop esi jnc SHORT VLC_Exit xor eax, eax VLC_Exit: pop ebp ret 8 EndProc vmmListCreate

  A VxD in 'C' can then call this service as follows:

      // Create a list with elements of the type NODE

      hList = vmmListCreate( sizeof( NODE ), 0 ) ;

  óThunking CallbacksÓ

  A thunk is a piece of assembly code that fronts your 'C' procedure to map registers as passed by VMM to a 'C' stack frame and then calls your procedure. A thunk also converts the 'C' return value to the expected return value for the callback. Callbacks are used by VMM and other VxDs for notifieation and event processing. For example, when a V86 page is hooked, a page fault handler in the VxD is called to resolve the fault.

  A thunk is created "on the t1y" by a thunking procedure. Given a procedure address, a thunking procedure copies the base code, patches the necessary offsets, and returns a pointer to this piece of code. An advantage to using tlat model code here is that a VxD can reference code and data with the same offset. Creating executable code with a simple heap allocation is easy, because seleetor restrictions are not an issue. For example, the following will create a procedure thunk for a generic VMM event callback:

      ;------------------------------------------------------------------
      ; EVENTPROC PASCAL vmmwrapThunkEventProc( EVENTPROC pProc )
      ; DESCRIPTION:
      ;           Creates a procedure thunk for VxD generic event callbacks.
      ; PARAMETERS:
      ;           DWORD pProc
      ;              pointer to callback procedure, must have the form:
      ;                 VOID CDECL EventProc( DWORD hVM,
      ;                                       DWORD dwRefData,
      ;                                       PCRS_32 pCRS )
      ;
      ; RETURN VALUE :
      ;           EVENTPROC
      ;              pointer to thunk or NULL if failure
      ;------------------------------------------------------------------

      BeginProc vmmwrapThunkEventProc, PUBLIC

        pCRS          equ [ebp]
        pProc         equ [ ebp + 8 ]

        push          ebp
        mov           ebp, esp

        call          Allocate_Procedure_Thunk
        jc            SHORT VEProc_Failure
        jmp           SHORT VEProc_CreateThunk

      ;===================
      ; Begin thunk code

      EventThunk label byte

        push          pCRS
        push          edx                     ; uPage
        push          ebx                     ; hVM
        call $
      EventThunkCallAddr    equ   $-EventThunk
        add           esp, 12     ; fixup for CDECL
        ret

      EventThunkSize        equ   $-EventThunk
      ;
      ;End thunk code
      ;===================

      VEProc_CreateThunk:
        push          ecx
        push          edi
        push          esi

        ;
        ; Copy the thunk...
        ;

        lea           esi, EventThunk
        mov           edi, eax
        mov           ecx, EventThunkSize
        cld
        shr           ecx, 1
        rep           movsw
        adc           cl, cl
        rep           movsb

        ;
        ; Fix it up...
        ;

        push          eax
        add           eax, EventThunkCallAddr
        mov           esi, eax
        sub           esi, 4
        sub           eax, pProc
        neg           eax
        mov           dword ptr [esi], eax
        pop           eax
        pop           esi
        pop           edi
        pop           ecx
        jmp           SHORT VEProc_Exit

      VEProc_Failure:
        xor           eax, eax

      VEProc_Exit:
        pop           ebp
        ret           4

      EndProc vmmwrapThunkEventProc

  To avoid page faults while executing thunk code, allocate a non-pageable memory block for a thunk table on the first call to Allocate_Procedure_Thunk. To simplify thunk allocation management, the allocation routine uses a fixed, maximum thunk size; this routine could be improved to be more memory efficient. The actual thunk code is embedded in the specific thunk allocation procedure. After the memory allocation for the thunk has been performed, the thunk code is copied and patched with the correct offset to the caller's provided procedure address. Thunks should be created only once per procedure, as follows:

      // NOTE!!! p VMEMTRAP P Fault is a global pointer to the
      // Page Fault procedure thunk.

      if ( !pVMEMTRAP_PFault )
      {
        if (p VMEMTRAP_PFault =
        vmmwrapThunkVB6PHProc( VMEMTRAP_P Fault ))
        else
        {
             vmmDebugOut ( "Could not allocate Page Fault thunk: \r\n" ) ;
             return ( FALSE ) ;
        }
      }
      vmmHookV86Page( wPage, pVMEMTRAP_PFault );
      return ( TRUE ) ;

  óService TablesÓ

  Service tables are best left to assembly. Although it is possible to create a service table using 'C', there are many restrictions:

  - Predefined services for replacement system drivers (such as VDD, VCD, etc.) almost always use register parameter passing. An assembly front end must be used for these procedures. There is no need to create a service table in C for these cases.
  - VMM.INC uses the '@' prefix for the actual procedure name. It also generates a service number for each of the listed services. Your "public" header file must provide these definitions and your service names must be distinct from the service number defintions.
  - VMM.INC creates debugging calls to watch for VMM reentrancy of non- asynchronous services. These services will not be available to your VxD's service procedures if they are written in 'C'.

  A service table can be declared in 'C' as follows:

      #define      VSIMPLED_Get_Version (VSIMPLED_Device_ID) << 16 + 0x0000
      #define      VSIMPLED_Get_Info    (VSIMPLED_Device_ID) << 16 + 0x0001

      DWORD CDECL I_VSIMPLED_Get_Version( VOID ) ;
      BOOL  CDECL I_VSIMPLED_Get_Info( PINFOSTRUCT ),

      SERVICETABLE VSIMPLED_Service Table =
      {
        I_VSIMPLED_Get_Version,
        I_VSIMPLED_Get_Info
      }

  The service table must be located in the locked data segment. The DDB should be contain a pointer to service table and number of services declared.

  If your VxD is replacing a standard VxD, such as the Virtual Display Driver, a service interface already exists. To support this interface and to allow the VxD service procedures to be written in 'C', the service entry points are thunked using a macro, such as the following to provide an interface to the register parameters:

      Service_Thunk MACRO Service_Name, Type

      IFNB
        IFIDNI ,
             BeginProc Service_Name, ASYNC_SERVICE
        ELSE
             %OUT ERROR: Service Thunk < Type> parameter muet be\
      ASYNC_SERVICE or undefined
             .err
        ENDIF
      ELSE
        BeginProc Service Name, SERVICE
      ENDIF
        EXTRN &Service Name:NEAR

      IFDEF DEBUG
        Debug_Out 'In &Service Name'
      ENDIF
        pushad
        pushfd
        push          esp
        cCall         _&Service_Name
        add           esp, 4
        popfd
        popad
        ret

       EndProc Service_Name

        ENDM

  The service thunks are defined as follows using the macro

      VXD_CODE_SEG

      Service_Thunk       VDD_Get_Version
      Service_Thunk       VDD_PIF_State
      Service_Thunk       VDD_Get_GrabRtn
      Service_Thunk       VDD_Hide_Cursor
      Service_Thunk       VDD_Set_VMType
      Service_Thunk       VDD_Get_ModTime
      Service_Thunk       VDD_Set_HCurTrk
      Service_Thunk       VDD_Meg_ClrScrn
      Service_Thunk       VDD_Meg_ForColor
      Service_Thunk       VDD_Meg_BakColor
      Service_Thunk       VDD_Meg_TextOut
      Service_Thunk       VDD_Meg_SetCursPos
      Service_Thunk       VDD_Query_Access

      ;
      ; New services for 3.1
      ;

      Service_Thunk       VDD_Check_Update_Soon

      VXD CODE_ENDS

  The service table is defined as usual:

      .xlist
        INCLUDE VMM. INC

        PUBLIC VDD_Service_Table
        Create VDD_Service_Table EQU True

        INCLUDE VDD.INC
      .list

  Finally, a service procedure written in 'C' uses a pointer reference to the registers, as provided by the thunk, to access the parameters:

      //------------------------------------------------------------
      //
      // VOID VDD_PIF_State
      //
      // Description:
      //     Informs VDD about PIF bits for newly created VM.
      //
      // Parameters:
      //     PREGS pRegs
      //           pRegs -> ebx = VM handle
      //           pRegs -> ax = PIF bits
      //
      // Return (VOID) :
      //     Nothing.
      //
      //------------------------------------------------------------

      VOID CDECL VDD_PIF_State
      {
        PREGS pRegs
      }
      {
        PVDDCB pVMCB;

        if (vmmTestSysVMHandle( p Regs -> ebx ))
        wPIFSave = (WORD) p Regs -> eax ;
        else
        {
               pVMCB = (PVDDCB) (pRegs -> ebx + dwVidCBOff) ;
               if (pVMCB -> VDD_PIF != (WORD) pRegs -> eax)
               {
                      pVMCB -> VDD_PIF = (WORD) pRegs -> eax ;
                      VDD_TIO_SetTrap( pRegs -> ebx, pVMCB ),
               }
        }
      } // VDD PIF State()

  ### VSIMPLED Sources in 'C'

  The VSIMPLED VxD introduced in Chapter 1 has been rewritten in 'C' to demonstrate some of the techniques discussed in this chapter:

      VSDINIT.C

      //------------------------------------------------------------
      //
      // Module: vedinit.c
      //
      // Purpose:
      //      Init code and data for VSIMPLED.
      //
      //------------------------------------------------------------

      #include <vmm. h>
      #include "vsimpled. h"

      #pragma data_seg ( "_IDATA", "ICODE" )

      //============================================================
      // I C O D E
      //===========================================================

      //------------------------------------------------------------
      // BOOL VSIMPLED_Sys_Critical_Init
      //
      // Description:
      //
      // On entry, interrupts are disabled. Critical initialization
      // for thie VxD should occur here. For example, we can read
      // settings from VMM's cached copy of the SYSTEM.INI and act
      // set up our VxD as appropriate.
      //
      // This procedure is called when the VxD Control Proc
      // dispatches the Sys_Critical_Init notification from VMM.
      //
      // We can notify VMM of euccess or failure by returning TRUE or
      // FALSE.
      //
      // Parameters:
      //       DWORD hVM
      //            System VM handle
      //
      //       DWORD dwRefData
      //            reference data passed from real-mode init
      //
      //       PSTR pCmdTail
      //            pointer to WIN.COM's command tail
      //
      //       PCRS_32 pCRS
      //            pointer to System VM client register structure
      //
      //------------------------------------------------------------

      BOOL CDECL VSIMPLED_Sys_Critical_Init