AEP: You may get AEP_UNCONFIG_DCB calls for DCBs you never saw during AEP_CONFIG_DCB. You need to check the DCB to see if it's yours before doing anything with it. SGD: Notwithstanding what this entry says, SG_buff_ptr is *always* a physical address. Don't confuse this structure with the _Blockdev_Scatter_Gather structure used by IOS clients. IOR_buffer_ptr will be the linear address of a zero-terminated array of _Blockdev_Scatter_Gather structures if the client has also set the IORF_SCATTER_GATHER flag. If the port driver asserted the DCB_dmd_phys_sgd demand, IOR_sgd_lin_phys will be the linear address of an array of SGD structures. IOR: See the annotation under the heading SGD for an important note about the data structures in scatter/gather requests. Calldowns: Wrong!! You get from one callback entry to the next by CHAINING, not by ADDING a constant! E.g.: mov eax, [ebx+IOP_calldown_ptr] mov eax, [eax+DCB_cd_next] mov [ebx+IOP_calldown_ptr], eax push ebx call [eax+DCB_cd_io_address] add esp, 4 Messages: CONFIG_PRESHUTDOWN is missing from the list. It gets sent during system shutdown, then devices and enumerators get CONFIG_REMOVE requests, then system shuts down. CONFIG_PREREMOVE2: Unlike CONFIG_REMOVE, this message goes only to the driver and not also to its enumerator. Introduction: Note that a property page provider DLL is a 16-bit DLL. CONFIG_PREREMOVE: Unlike CONFIG_REMOVE, this message goes only to the driver and not also to its enumerator. CONFIGMG_CallBack_Enumerator: This causes a callback only for each DEVNODE that has the specified function as its enumerator. You might think from the wording that you'd get a callback for each child node created by the enumerator, but such is not the case. CONFIGMG_Register_DevLoader: This description doesn't indicate how limited the purpose of this service really is. If you have a statically loaded VxD that happens to be specified as somebody's DevLoader (e.g., VMOUSE), you'll get a PNP_New_Devnode during CONFIGMG's Device_Init. If you initialize after CONFIGMG, this is probably too early for you. Therefore, return CR_DEVLOADER_NOT_READY from the early PNP_New_Devnode. Then call this service during your Device_Init and you'll get another PNP_New_Devnode within which you can do your device loading stuff. VDMAD_Lock_DMA_Region: Use the undocumented VDMAD_Get_Max_Phys_Page service (new with Win 95) to get the maximum usable physical address. Xlat_API_Return_Ptr: This script operation uses Map_Lin_To_VM_Addr to create the returned far pointer, thereby permanently consuming a selector in the calling VM. Xlat_API_Fixed_Len: This script operation copies data from protected mode to real mode before executing the real-mode interrupt and copies data back to protected mode afterwards. It also saves and restores the affected client registers. _SHELL_PostMessage: This is not an asynchronous service. Too bad: it would be a lot more useful if it were. Xlat_API_Calc_Len: This script operation copies data from protected mode to real mode before executing the real-mode interrupt and copies data back to protected mode afterwards. It also saves and restores the affected client registers. _SHELL_LocalAllocEx: There is an additional flag named LMEM_OEM2ANSI that you can use if you've specified LMEM_STRING. This translates the string from the OEM character set (used by ring-0) to ANSI (used by most Windows APIs). VPICD_Phys_EOI: This service actually unmasks the physical IRQ rather than sending an EOI command to the PIC. When VPICD fields an interrupt, it masks it and then sends a specific EOI command to the PIC. _VWIN32_CloseVxDHandle: Note that OpenVxDHandle is a ring-3 API exported by KERNEL32 (but not in the import library). _VWIN32_ResetWin32Event Note that OpenVxDHandle is a ring-3 API exported by KERNEL32 (but not in the import library). VPICD_Force_Default_Owner: OR the optional flag bits directly into EAX rather than setting AX and shifting EAX left by 16. The bits are defined as 00010000h and 00020000h, so they've already been shifted into the high-order word. You can also supply EBX = -1 to force reflection of a global IRQ into the current VM regardless of whether someone owns the critical section. If you use this undocumented feature, be aware that the FAVOR_FOCUS flag will be ignored (so you can't force the interrupt into the execution focus VM regardless of critical section ownership). VID_Mask_Change_Proc: In reality, the Mask argument is nonzero if the interrupt is being masked (so that it can't occur) and zero if it's being unmasked (so that it can occur). Xlat_API_Var_Len: This script operation copies data from protected mode to real mode before executing the real-mode interrupt and copies data back to protected mode afterwards. It also saves and restores the affected client registers. VPICD_IRQ_Descriptor: The timeout is on the priority boost VPICD gives the VM that handles the interrupt. If the timeout expires before the virtual ISR returns, VPICD unboosts the VM anyway. _VWIN32_PulseWin32Event: Note that OpenVxDHandle is a ring-3 API exported by KERNEL32 (but not in the import library). _VWIN32_WaitMultipleObjects: Note that OpenVxDHandle is a ring-3 API exported by KERNEL32 (but not in the import library). VDMAD_Scatter_Lock: This service calls _LinPageLock, which will in turn claim the critical section, even if the pages in question are already locked. A deadlock can ensue unless you're calling this service under circumstances where you know it's safe to claim the critical section. Reported by Don Matthews _VWIN32_WaitSingleObject: Note that OpenVxDHandle is a ring-3 API exported by KERNEL32 (but not in the import library). VID_Hw_Int_Proc: A typical hardware interrupt procedure would EITHER do a VPICD_Set_Int_Request to set an interrupt request for a VM OR handle the interrupt itself and end with a VPICD_Phys_EOI. It's almost surely wrong to handle the interrupt and EOI it and then send the interrupt into a VM, which will try to handle the interrupt and send its own EOI. You can also count on EBX holding the current VM handle and EDI holding the current thread handle. Moreover, EDX will hold the reference data (if any) from the the VID structure you passed to VPICD_Virtualize_IRQ. There's no such service as Schedule_Call_Global_Event. Presumably, the author of this entry had Call_Restricted_Event in mind. Xlat_API_ASCIIZ: This script operation saves and restores the affected client registers. There is another (undocumented) script opcode named Xlat_API_ASCIIZ_InOut that copies the string back to protected mode. That service isn't of much use unless you first set the buffer to contain a string having the maximum length you expect back, since it only restores as many bytes as you pass down. _VWIN32_QueueUserApc: The Win32 procedure you call with this service should be declared void __stdcall (DWORD). The single argument is the "dwParam" you supply to this service. The easiest way to derive the right thread handle for this call is to have the ring-3 thread that will be doing the alertable wait call you (via DeviceIoControl) to give you the address of the APC callback routine. Just call Get_Cur_Thread_Handle and save the result. _VWIN32_SetWin32Event: Note that OpenVxDHandle is a ring-3 API exported by KERNEL32 (but not in the import library). OpenVxDHandle: This function is no longer in the import library for KERNEL32 in the SDK, so you need to resolve it dynamically with a call like the following: typedef DWORD (WINAPI * OPENVXDHANDLE) (HANDLE); OPENVXDHANDLE OpenVxDHandle = (OPENVXDHANDLE) GetProcAddress (GetModuleHandle ("KERNEL32"), "OpenVxDHandle"); PortSetup: Either (or both) of the queue addresses can be NULL, which means the port driver should allocate a buffer with the specified size. The port driver should also set flags to indicate which buffers it's allocated so it can release the memory when the port closes later on. PortSetReadCallback: The client can also cause CN_RECEIVE notifications every 100 ms regardless of whether the threshold has been reached by using the ENABLETIMERLOGIC escape. _VCOMM_SetCommEventMask: If you supply your own event word, be sure to initialize it first. You might hope that VCOMM or a port driver would copy the current event word as part of implementing this API, but such is sadly not the case. PortGetEventMask: The dwEvents argument has nothing to do with the event word the port driver is using to keep track of events! This is just where to put the events that are being retrieved. Note also that you retrieve ALL current events and then mask the event word using dwMask. It's not optional either: the port driver will copy the current event word there without checking the pointer. In other words, this function copies the current event word to *dwEvents (which cannot be NULL) and then clears the dwMask events in the current event word. Unless dwEvents happens to fortuitously point to the current event word setup by the last call to PortSetEventMask, *dwEvents will contain *all* of the current events and not just the ones selected by dwMask. VCOMM_PM_API_SetMSRShadow: Be sure to initialize your shadow byte before calling this service, because your byte won't be changed until the next MSR interrupt from the serial port. _VCOMM_Release_Port: If you supplied -1 as the HVM argument to _VCOMM_Acquire_Port, do so here too. VCOMM_PM_API_cevt: If you supply your own event word (the poorly named "mymask" variable mentioned in the help file), be sure to initialize it first. You might hope that VCOMM or a port driver would copy the current event word as part of implementing this API, but such is sadly not the case. Contention Management by Contention Managers: The name of the system control message VCOMM sends to find the contention function is really GET_CONTENTION_HANDLER. _VCOMM_Acquire_Port: This entry is just about totally wrong! There are five arguments (not four), as follows: HANDLE hPort - VCOMM port handle for the port (i.e., address of PortData structure by which VCOMM knows the port) DWORD lPortNum - Port number. 1 means COM1, etc. Alternatively, use the base I/O address here and set bit 1 of the flags argument. HVM hVM - Handle of the VM that will own the port. Use -1 if you intend to assign ownership to your VxD instead of to a VM, but you then need to either disable global trapping for the ports or take over virtualization from VCD. See the comments at the head of VCD_Acquire_Port in VCD.ASM. DWORD lFlags - Flags. Bit 0 means "steal the port even if someone else owns it", which is what you usually want because VMs that just touch the port end up owning it. Bit 1 means "lPortNum is the base I/O address instead of the port number". char *name - Name of your VxD in case you supply -1 for the VM handle argument. Unused otherwise. DriverControl: There are three errors in this entry. The return value is a boolean value indicating whether the driver control procedure succeeded in performing its tasks, such as adding a port via VCOMM_Add_Port. This return value ends up as the return value from VCOMM_Register_Port_Driver. The one-and-only function is really spelled DC_Initialize. The variable arguments to DC_Initialize are really in the order AllocIOBase, AllocIrq, and Name. _PortData: Despite the implication otherwise, a port driver isn't supposed to use dwClientEventMask. It should rely instead on whatever gets passed to its SetEventMask function. lpClientEventNotify, lpClientReadNotify, lpClientWriteNotify, and dwClientRefData are solely for use by VCOMM. A port driver needs to have its own variables for recording callback function addresses and reference data values. LossByte isn't really reserved. A port driver is expected to use it to keep track of contention callbacks. VCOMM does not, in fact, modify the buffering parameters (QInGet, QOutGet, etc.) Microsoft expects that you'll use these structure members to implement a ring buffer for input and output, but you're free to do your own thing here. Don't use dwLastReceivedTime for its apparent purpose. Instead, keep your own PDWORD that you initialize to the address of this field. The PDWORD can be changed by a SETUPDATETIMEADDR escape. Install_IO_Handler: You can remove an I/O trap in Win 95 by calling Remove_IO_Handler. This should be mentioned in the See Also, of course. _Allocate_Global_V86_Data_Area: This service isn't available until after Sys_Critical_Init. _CallRing3: This service only works right in a limited number of circumstances. It's really better to do as Microsoft suggests and use an AppyTime callback instead. _LinPageLock: If you use the PAGEMAPGLOBAL flag in the call to this service, you must also use it in the matching call to _LinPageUnlock to avoid a memory leak. Reported by Bob Rhoades and Mark Gruetzner _Assign_Device_V86_Pages: Before assigning a page, obtain a copy of the current assignment map by calling _Get_Device_V86_Pages_Array and inspecting the bit for the page. Hook_Device_Service: Note that the macro used in the example is really named GetVxDServiceOrdinal. In addition, you can set EDI to the address of an 8-byte, blank-padded, case-sensitive device name if it uses Undefined_Device_ID. End_Nest_Exec: May change the direction flag. At least, there's code in IFSMGR that does a CLD after calling End_Nest_Exece bit for the page. Exec_PM_Int: This is like Exec_VxD_Int except that the register images for the interrupt are taken from the current client registers instead of the actual ring-0 registers. It appears to be mostly useful for executing an interrupt on behalf of a Win32 caller (who can't do the interrupt directly because VMM thinks all apps in the System VM are 16 bit). _PageCommitPhys: It emphatically IS okay to touch memory committed with this service at interrupt time, provided you have called _LinPageLock to lock the memory block. The statement at the end of this entry to the contrary is incorrect. Dispatch_Byte_IO This entry is misleading when it says it "ignores" input or output operations if you use the Fall_Through keyword. The keyword causes the macro to fall through to the next instruction in the case(s) where you use it. Thus, Dispatch_Byte_IO bytein, Fall_Through would be followed directly by the BYTE_OUTPUT handler. _LinPageUnlock: To avoid a memory leak, you must specify the PAGEMAPGLOBAL flag in the call to this service if you specified it in the matching call to _LinPageLock. Reported by Bob Rhoades and Mark Gruetzner Locate_Byte_In_ROM: This is a very special purpose service whose only actual use is to find the 63h in the middle of a "copyright" string in the BIOS. Windows uses that byte as the ARPL instruction for implementing V86 breakpoints. Exec_VxD_Int: You may have read that this service is useless due to a bug in Begin_Nest_Exec. The reported bug has to do with a failure to properly set the execution mode of a VM that doesn't have a protected-mode application. This is not a problem in Win 95. In Win 3.1, if you called Exec_VxD_Int at a time when there was no protected-mode app in the VM and if someone then did a Begin_Nest_Exec (not very usual -- you'd usually do a Begin_Nest_V86_Exec to pass an interrupt down to real mode), you would crash the System VM and, hence, Windows. This problem has been corrected in Win 95. Hook_VMM_Fault: The purpose of this service is to provide a handler for a fault that occurs while running ring-0 code. Very few VxDs need to do this, because VMM already does an adequate job. You might want to use _Assert_Range to validate addresses before you use them and/or Install_Exception_Handler to guard against bad pointer references in a section of your own VxD. The mysterious "VMM re-entrant stack frame" to which EBP points when the fault handler gets control is nothing more than a standard 32-bit fault frame (EFLAGS, CS, EIP, error code) plus PUSHAD registers. You could use the client register structure to examine this much of the stack, but you obviously don't want to reference fields past Client_EFlags. There's no legal way for the fault handler to alter any of the segment registers (besides CS, that is) that will be reloaded when VMM redispatches. You *could* reverse engineer the stack VMM passes the fault handler, but I wouldn't recommend doing so. Begin_Nest_Exec: This service switches to the locked stack even if the execution mode of the VM doesn't change, unless the VM is already using the locked stack. In contrast to Win 3.1, it is safe to call this service nested within an Exec_VxD_Int call even if the VM has no protected-mode app. (In 3.1, doing this would cause a crash when you then called Resume_Exec.) _AllocateThreadDataSlot: The point about initialization doesn't have enough emphasis here. Any time you allocate a thread data slot, you need to scan through all existing thread control blocks to initialize your newly allocated slot, and you also need to handle Thread_Init in order to initialize your slot for new threads. _PageAllocate: It sounds pedantic to point this out, but you don't have to actually receive and process Init_Complete to use PAGELOCKEDIFDP. A dynamically loaded driver can use this flag too, provided that Init_Complete has already occurred. You can use VMM_GetSystemInitState to find out if the message has occurred yet. Unhook_Device_Service: Note that the macro used in the example is really named GetVxDServiceOrdinal. VM_Critical_Init: In fact, interrupts are enabled when this message is sent. You can't execute code in the VM (Exec_Int, etc.), but you certainly can call services that enable interrupts. _Allocate_Temp_V86_Data_Area: This service is only available during Device_Init and Init_Complete. Device_Init: The EDX register also holds the reference data value supplied by the real-mode initialization routine, just as is true with Sys_Critical_Init. Init_Complete: The EDX register also holds the reference data value supplied by the real-mode initialization routine, just as is true with Sys_Critical_Init.