PHD Computer Consultants Ltd
... WDM Book Errata
Last modified: 18 September 2002.

Please give me any comments or corrections on the feedback page.


These errata correspond to the first printing of the book. Later print runs have some or all of these errata fixed.


General driver development issues

NT 4 and W2000 driver compatibility

Microsoft's original aim was for most NT 4 device driver binaries to run unchanged in W2000. However various contributions to the NTDEV mailing list have stated that there are quite a few drivers that used to work in NT 5 Beta 2 that do not now work in the W2000 beta 3.

My advice therefore is to provide separate binary versions of your drivers for NT 4 and W2000. Currently indications are that drivers should be source code compatible. However you may wish to consider moving your driver to the Plug and Play (PnP) architecture, supporting Power Management and Windows Management Instrumentation (WMI).
Test any existing drivers thoroughly in W2000!


Errata / Further notes

26 Oct 99
Files on the CD are read-only
All the files on the book CD are read only. This causes minor problems when rebuilding the book drivers. A simple way to install the files without having the read only bit set is to unzip the file WDMdriv3.zip in the CD root directory.


Minor typographical errors

Missing tabs in some listings
Apologies: some of the code listings have had their tab characters removed which makes them difficult to read.

16 Oct 99
Page 36
In "Deferred Procedure Calls" section, first para, add "run" before "as quickly as possible".
Last para on page should start "A driver" instead of "Drivers".

16 Oct 99
Page 45
In the "Recompiling the book drivers" section: When recompiling the UsbKbd software in W98, you will need to change the USB headers include directory to "c:\98ddk\src\usb\inc" or equivalent.

23 Jul 01
Page 47
In the "Registry Editors" section: replace "RedEdt32" with "RegEdt32".

28 Sep 99
Page 97
The text refers to "Table 5.2". It should refer to "Table 5.1".

28 Sep 99
Table 5.2
Table 5.2 should describe IoAttachDeviceToDeviceStack.

16 Sep 99
DebugPrint web link
The link to the DebugPrint web site should be www.phdcc.com/debugprint.

21 Oct 99
Table 20.4, page 406
For the GET_DESCRIPTOR request code, the Recipient column should be DIE, not D.

4 Nov 99
Interrupt transfers
Page 403. Section "Transaction packet Structure". Change the start of the third paragraph from "The host starts interrupt transfers" to "The host starts input interrupt transfers".
Page 414. Top of the page. Change "Interrupt transfers are unidirectional into the host" to "Interrupt transfers are also bidirectional".

21 Oct 99
Other Get/Set descriptors, page 442
The following function codes should also be listed:
URB_FUNCTION_GET_DESCRIPTOR_FROM_INTERFACE
URB_FUNCTION_SET_DESCRIPTOR_TO_INTERFACE

The following function code does not exist and should be deleted:
URB_FUNCTION_SET_DESCRIPTOR_TO_OTHER


Chapter 7: DeviceIoControl buffers

Date 7 Sep 99
Summary The METHOD_IN_DIRECT and METHOD_OUT_DIRECT description is incorrect
Topics DeviceIoControl buffers (page 139)
Discussion My description of how drivers receive the parameters that are passed to DeviceIoControl for the METHOD_IN_DIRECT and METHOD_OUT_DIRECT buffer methods is wrong.

For both these methods, the driver sees the input buffer in buffered memory at Irp->AssociatedIrp.SystemBuffer with the input buffer length at the IRP stack Parameters.DeviceIoControl.InputBufferLength. These fields are NULL and 0 respectively if you pass no input buffer.

METHOD_IN_DIRECT is used if you are reading lots of data, or want to use an MDL in your driver. The Win32 program passes the read data buffer in the DeviceIoControl output buffer parameter. The read data buffer is passed to the driver as an MDL in Irp->MdlAddress. The size of the read data buffer is passed in Parameters.DeviceIoControl.OutputBufferLength. If no buffer is specified then these fields are NULL and 0 respectively.

METHOD_OUT_DIRECT is used if you are writing lots of data, or want to use an MDL in your driver. The Win32 program passes the write data buffer in the DeviceIoControl output buffer parameter. The write data buffer is passed to the driver as an MDL in Irp->MdlAddress. The size of the write data buffer is passed in Parameters.DeviceIoControl.OutputBufferLength. If no buffer is specified then these fields are NULL and 0 respectively.

There are two points to note about using these techniques:

  • If METHOD_OUT_DIRECT is used, then the Win32 program is providing two sets of data into the driver. The 'input' buffer is in Irp->AssociatedIrp.SystemBuffer and the 'output' buffer is in Irp->MdlAddress.
  • It seems as though it does not matter whether you use METHOD_IN_DIRECT or METHOD_OUT_DIRECT. In both cases it seems as though you can use MDL for either input or output.


Chapter 9: Completion routines need to check PendingReturned

Summary Most IRP completion routines need to include this code:
		if( Irp->PendingReturned)
			IoMarkIrpPending();
Topics IRP Completion routines (page 179)
Discussion The paragraph starting "Some completion routines..." on page 179 is inaccurate.

Most completion routines need to use the above code in their completion routines to remember if any lower driver has pended an IRP. If any driver in a stack marks an IRP as pending then the I/O Manager has to take special steps to ensure that the IRP results are returned to user space in the correct thread context.

Calling IoMarkIrpPending sets an internal pending flag in the current IRP stack location (the SL_PENDING_RETURNED bit in the Control field). When a lower driver completes an IRP, IoCompleteRequest sets the Irp->PendingReturned flag equal to the IRP stack internal flag. To remember that the IRP was pended, a completion routine must therefore call IoMarkIrpPending if Irp->PendingReturned is set.

In two cases, the above code is not strictly necessary (though including the code will cause no bugs).

  • If the completion routine sets an event which the main code waits for, then the IRP processing is back in the context of calling thread, and so the Pending flags do not need to be set.
  • If the IRP is allocated by a driver, then there is no user space thread context completion work required. So you do not need to check the Pending flag in the completion routine of an IRP which you allocated.

Thanks to Jamie Hanrahan and others on the NTDEV mailing list for answering my questions on this.


Chapter 10: SendDeviceSetPower

Michael Feigin sent an email stating:
I found the Wdm2 driver bug: Windows doesn't resume after suspend or hibernate.
OS: Windows 2000 SP2

NTSTATUS SendDeviceSetPower(...)
{
	...
	NTSTATUS status = PoRequestPowerIrp( dx->pdo, IRP_MN_SET_POWER, NewState,
		OnCompleteDeviceSetPower, &sdsp, NULL);
	if( status==STATUS_PENDING)
	{
		KeWaitForSingleObject( &sdsp.event, Executive, KernelMode, FALSE, NULL);
		status = sdsp.Status;
	}
	...
}

Function KeWaitForSingleObject never return.
You write: "DDK documentation says that you cannot wait using events for yourown Power IRP to complete".
I think it is right and code must be:
	NTSTATUS status = PoRequestPowerIrp( dx->pdo, IRP_MN_SET_POWER, NewState,
		NULL, Irp, NULL);
	if( status==STATUS_PENDING)
	{
		status = STATUS_SUCCESS;
	}

where Irp - or pointer to IRP received or NULL.
DDK says: "When the caller requests a device set-power IRP in response to a system set-power IRP, 
the Context should contain the system set-power IRP that triggered the request. "

After these changes driver works Ok.

Chapter 10: Wdm3Power

Justin Frodsham send an email stating:
My driver was blue screening on XP at system shutdown with this error: Bug Check 0x9F: DRIVER_POWER_STATE_FAILURE Parm1=0x02 The device object completed the IRP for the system power state request, but failed to call PoStartNextPowerIrp.

I narrowed it down to the code in my Power dispatch routine. This was based on the wdm3 project. NTSTATUS Wdm3Power( IN PDEVICE_OBJECT fdo,IN PIRP Irp)

The first couple lines check for IODisabled and lock checking, if either of these scenerios are executed, PStartNextPowerIrp is not called. Adding this call fixed the crash.


Chapter 11: Windows 98 INF file section names

Section names in INF files cannot be longer than 28 characters for Windows 98 installations, as W98 will not recognise the INF file. Longer section names are OK in W2000.

This problem effects the supplied DebugPrint INF file, which had one section name that was too long. Download the updated DebugPrint INF files if you wish to install DebugPrint in W98.


Chapter 11: Minor change of functionality in Win32 ControlService SERVICE_CONTROL_INTERROGATE

Summary Calling Win32 function ControlService SERVICE_CONTROL_INTERROGATE fails in W2000 Beta 3 if the driver is stopped, as the documentation says it should.
Implication My suggested NT style driver code in install.cpp needs changing... download the revised install.cpp and copy into the WDMBook NT directory.
Preamble Win32 programs can create and control NT/W2000 device driver services using the Service Control Manager Win32 functions. You should be able to call ControlService with control code SERVICE_CONTROL_INTERROGATE to see if a driver is running.
Discussion In Windows 2000 Beta 3, the Win32 ControlService SERVICE_CONTROL_INTERROGATE call fails if the driver is stopped. GetLastError then returns error 1062 (ERROR_SERVICE_NOT_ACTIVE The service has not been started). This is the documented behaviour. In NT 4 this call succeeds.

The book software Servicer program should also be altered minorly to cope with this change of functionality.


Chapter 11: NT4 Plug and Play ISA support

Summary NT4 does provide some support for PNPISA devices
Topics NT Style Driver Installation (pages 242)
Implication You can install PNPISA devices in NT4 using W98/W2000 style INF files.
You must install PNPISA.INF first from the NT4 CD \DRVLIB\PNPISA directory first.

Search for "PNPISA" in the MSDN library for some more details.


Chapter 12: WMI Methods

Date 28 Oct 99
Summary The Wdm3 code is updated to make WMI method calls work
Get the revised driver and code.
Topics A WMI driver(pages 261-262) and ExecuteWmiMethod Handle (pages 273-274)
Discussion In the WMI chapter I say that I could not get the Wdm3Information data block PowerDown method to compile. The following code should be used, and therefore I am issuing a revised Wdm3.mof file.
[ WmiMethodId(1),
  Implemented,
  Description("Power down the device")
]
void PowerDown();

In addition the ExecuteWmiMethod method in Wmi.cpp needs updating. The old code incorrectly checked that the MethodId parameter was 0. Instead, the first method, PowerDown, has MethodId 1 and so one line in ExecuteWmiMethod should be:

if( GuidIndex==WDM3_WMI_GUID_INDEX && MethodId==1) // 1.0.1

The PowerDown method now appears in the WMI Object Browser and can be executed successfully to power down individual Wdm3 devices.

Note 1: I have also added the WMIREG_FLAG_EVENT_ONLY_GUID flag to the Wdm3Event WMIGUIDREGINFO declaration. However I have still not found how to enable these events and view them. Any suggestions?

Note 2: the mofcomp tool can now generate a VBScript script to test a WMI implementation.


Chapter 13: EventLog sections in INF files

Date 20 Aug 99
Summary Wdm3 INF file updated to fix EventLog section problem
Topics Registering as an Event Source (pages 283-284)
Discussion I did not get the book Wdm3 driver INF file to register Wdm3 as an event source. I have now fixed this problem. Download the updated Wdm3 INF files.

Listing 13.4 on page 284 should now be as follows:

[Wdm3.Install.NTx86.Services]
AddService = Wdm3, %SPSVCINST_ASSOCSERVICE%, Wdm3.Service, Wdm3.Service.EventLog

...

[Wdm3.Service.EventLog]
AddReg = Wdm3.Service.EventLog.AddReg

[Wdm3.Service.EventLog.AddReg]
HKR,,EventMessageFile,%FLG_ADDREG_TYPE_EXPAND_SZ%,"%%SystemRoot%%\System32\IoLogMsg.dll;%%SystemRoot%%\System32\drivers\Wdm3.sys"
HKR,,TypesSupported,%FLG_ADDREG_TYPE_DWORD%,7

Thanks to Bob Fruth at Microsoft for helping me sort this problem out. The latest DDK has the correct information on this topic.

Bob Fruth also pointed out that the latest W2000 DDK has a useful tool, ChkINF in the DDK Tools directory, for checking INF files. ChkINF is a perl script so I have not managed to check it out yet.
In addition a new tool for generating INF files is also available in the DDK Tools directory; it is called the INF wizard geninf.


Chapter 14: File and Object handles are process dependent

Summary File and Object handles are process dependent, not thread dependent.
Topics System threads (page 291) and System worker threads (page 294)
Discussion I use a system thread for the DebugPrint driver as the Zw... file access functions cannot access the file handle created in a different context. I stated that file handles are thread dependent, when in fact they are process dependent. This does not effect the decision to use a system thread in DebugPrint.

File handles are a type of Object handle. All Object handles process dependent, ie they are only usable by threads running in the same process.

Thanks to Jamey Kirby of StorageCraft for this information. James also adds:

"If you use kernel worker threads (work queue items), you can assume that your file handle is valid no matter what worker thread happens to be processing the request (because they are all in the system process).

"It is also possible to create system threads in a process context other than the system process. This is useful if you have a driver that needs to access network resources (i.e. a file). My creating the system thread in the context of a process that has the appropriate access rights, the thread will be able to access the resources."


Chapter 16: Extra note on cancelling IRPs and IoCancelIrp

Summary Do not assume that an IRP has been completed if IoCancelIrp returns TRUE.
Preamble An IRP that is held in a queue must be made cancellable using a cancel routine. A driver that allocates its own IRP and sends it to a lower driver can try to cancel it using the IoCancelIrp routine.
Discussion My main technique for cancelling IRPs does not remove the cancel routine when an IRP starts processing in the driver StartIo handler. Instead it does nothing, relying on StartIo later (or its follow on routines) to detect the Irp->Cancel flag, and only then complete the IRP with a cancelled status.

IoCancelIrp returns TRUE if an IRP's cancel routine was called, and FALSE if not, ie if there was no cancel routine (eg if the cancel routine was removed at the beginning of the StartIo handler).

The DDK documentation for IoCancelIrp might give the impression that if IoCancelIrp returns TRUE then the IRP has been completed. This is not the case for the technique I used earlier; the IRP's cancel routine has been called but the IRP has not been completed.

A quick look at all the W2000 DDK source code examples seems to indicate that none of these examples assume that an IRP has been completed, even if IoCancelIrp returns TRUE. If the IRP has a completion routine, wait for it to run. Only when the IRP has really completed can you free its memory.

I do not know what technique the I/O Manager uses if a user mode program cancels an IRP using CancelIo.


Chapter 17: Safer time-out code

Date 22 Nov 99
Summary Safer time-out handling in multiple processor systems
Topics WDMIo and PHDIo time-outs, page 369-370
Note that I have not yet issued updates to the PHDIo and WdmIo drivers.
Discussion The following revised code should replace Listing 17.5, in DeviceIo.cpp in the WdmIo driver. Similar code should also be used in PHDIo.

There is a chance that the CurrentIrp will have become NULL in my Timeout1s routine, and also a chance that a device interrupt will occur after the Cancel flag has been found true but before the DPC is called.

My revised code does its timeout and Cancel checks in the Critical Section routine, Timeout1sSynch.

The revised Timeout1sSynch routine now has to be changed because Timeout1s does not call the DPC routine directly. Instead, Timeout1sSynch asks that my DPC routine be called later in the normal way using IoRequestDpc.
Note that Timeout1sSynch returns TRUE if the DPC was scheduled. However this information is only used to do a debug printout.

VOID Timeout1s( IN PDEVICE_OBJECT fdo, IN PWDMIO_DEVICE_EXTENSION dx)
{
	if( dx->Timeout==-1) return;

	DebugPrint("Timeout1s: Timeout is %d",dx->Timeout);

	// Check for timeout in synch with interrupt
	if( KeSynchronizeExecution( dx->InterruptObject, (PKSYNCHRONIZE_ROUTINE)Timeout1sSynch, dx))
	{
		DebugPrint("Timeout1s: Timeout1sSynch cancelled in-progress IRP %x %I",Irp,Irp);
	}
}

static BOOLEAN Timeout1sSynch( IN PWDMIO_DEVICE_EXTENSION dx)
{
	if( dx->Timeout==-1) return FALSE;

	PDEVICE_OBJECT fdo = dx->fdo;
	PIRP Irp = fdo->CurrentIrp;
	if( Irp==NULL) return FALSE;

	// Only complete IRP if it is cancelled or if it has timed out
	if( !Irp->Cancel && --dx->Timeout>0)
		return FALSE;

	// Get DPC to complete the IRP
	dx->Timeout = -1;
	dx->TxStatus = STATUS_NO_MEDIA_IN_DEVICE;	// Win32: ERROR_NOT_READY
	IoRequestDpc( fdo, Irp, dx);
	return TRUE;
}


Chapter 17: Intel suggests thread post-interrupt processing

The Intel Architecture Labs have published an analysis of the real-time performance of WDM drivers in Windows 98 and NT 4, ie the time it takes for interrupts to be serviced.

In this paper they suggest that it may be useful (in NT 4) for post-interrupt processing to be further deferred to a high priority read-time system thread, as it is easier to code threads than DPC routines. See the More Information Page for more details.


Chapter 21: UsbGetSpecifiedDescriptor needs amending

Summary My UsbGetSpecifiedDescriptor routine needs amending to specify whether the GET_DESCRIPTOR request is for the Device, Interface or Endpoint.
Topics Getting the HID Descriptor, page 429 and 434
Discussion Some people may have found that the UsbKbdTest program could not read the HID report descriptor, even though the rest of UsbKbdTest ran OK.

The problem is that the USBDI GET_DESCRIPTOR call must be directed to the interface, not the device. My UsbGetSpecifiedDescriptor routine uses the UsbBuildGetDescriptorRequest helper function that always uses the URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE function code.

As the desired HID Descriptor is an Interface descriptor, the URB_FUNCTION_GET_DESCRIPTOR_FROM_INTERFACE function code should be used instead. As a temporary hack the following code can be inserted after UsbBuildGetDescriptorRequest has been called.

urb->UrbHeader.Function = URB_FUNCTION_GET_DESCRIPTOR_FROM_INTERFACE;

A full fix for this problem would be add an extra parameter to UsbGetSpecifiedDescriptor to indicate whether a device, interface or endpoint descriptor is required. The IOCTL_USBKBD_GET_SPECIFIED_DESCRIPTOR handler and the corresponding UsbKbdTest code would also need to be amended. I shall leave this an exercise for the reader.

It seems as though some devices respond with the desired information even if the request is directed as the device.

Thanks to Bruce Ramsland for pointing out this problem.


Chapter 23: HID kernel mode IOCTLs

Date 30 Oct 00
Summary HID IOCTLs should use IRP_MJ_DEVICE_CONTROL not IRP_MJ_INTERNAL_DEVICE_CONTROL
Discussion In W2000, it looks like you must use IRP_MJ_DEVICE_CONTROL (not IRP_MJ_INTERNAL_DEVICE_CONTROL) when sending messages from kernel mode HID drivers to the HID class driver.

The HidKbd CallHidIoctl routine calls IoBuildDeviceIoControlRequest to build a HID request. The supplied code builds an IRP_MJ_INTERNAL_DEVICE_CONTROL IRP. Change parameter 7 of IoBuildDeviceIoControlRequest to FALSE to make it build an IRP_MJ_DEVICE_CONTROL IRP instead.

Thanks to Will Dean for this information.


Chapter 23: GetPreparsedData HidPreparsedData parameter

Date 14 Dec 00
Summary The HidPreparsedData parameter to GetPreparsedData in HidKbd should be passed by reference.

Listing 23.10 page 489.

Thanks to Russell Hocken for spotting this.


Chapter 23: SetupHidIrp

Date 27 Apr 01
Summary Russell Hocken suggests changes to the end of (the commented out version of) SetupHidIrp so that it reads:

        dx->HidIrp=NULL;
        return;            // Add this return here
    }
    MmBuildMdlForNonPagedPool(dx->HidReportMdl); // new command
}

"Without this the report wouldn't get put in the right place. It now seems to work correctly."

Listing 23.12 page 493/494.


WDM Book main page