[Previous] [
Working with the bus driver, the PnP Manager automatically detects hardware and
NOTE
I find it hard to give an abstract definition of theterm I/O resource that isn't circular (for example, a resource used for I/O), so I'll give a concrete one instead. The WDM encompasses four standard I/O resource types: I/O ports, memory registers, direct memory access (DMA) channels, and interruptrequests .
When the PnP Manager detects hardware, it consults the registry to learn which filter drivers and function drivers will manage the hardware. As I discussed in Chapter 2, "Basic Structure of a WDM Driver," it loads these drivers (if necessary—one or more of them might already be present, having been called into memory on
The PnP Manager initially creates a list of resource requirements for each device and allows the drivers to
filter
that list. I'm going to ignore the filtering step for now because not every driver will need to take this step. Given a list of requirements, the PnP Manager can then assign resources so as to harmonize the
Figure 6-2. Arbitration of conflicting I/O resource requirements.
Once the resource assignments are known, the PnP Manager notifies each device by sending it a PnP request with the minor function code IRP_MN_START_DEVICE. Filter drivers are typically not interested in this IRP, so they usually pass the request down the stack by using the DefaultPnpHandler technique I showed you in "IRP_MJ_PNP Dispatch Function." Function drivers, on the other hand, need to do a great deal of work on the IRP to allocate and configure additional software resources and to prepare the device for operation. This work needs to be done, furthermore, at PASSIVE_LEVEL
after
the lower
To regain control of the IRP_MN_START_DEVICE request after passing it down, the dispatch routine needs to wait for a kernel event that will be signalled by the eventual completion of the IRP in the lower layers. In Chapter 4, "Synchronization," I cautioned you not to block an arbitrary thread. PnP IRPs are sent to you in the context of a system thread that you are allowed to block, so that caution is unnecessary. Since forwarding and awaiting an IRP is a potentially useful function in other contexts, I suggest writing a helper routine to perform the mechanics:
1 |
NTSTATUSForwardAndWait(PDEVICE_OBJECTfdo,PIRPIrp)
{
KEVENTevent;
KeInitializeEvent(&event,NotificationEvent,FALSE);
IoCopyCurrentIrpStackLocationToNext(Irp);
IoSetCompletionRoutine(Irp,(PIO_COMPLETION_ROUTINE)
OnRequestComplete,(PVOID)&event,TRUE,TRUE,TRUE);
PDEVICE_EXTENSIONpdx=(PDEVICE_EXTENSION)
fdo->DeviceExtension;
IoCallDriver(pdx->LowerDeviceObject,Irp);
KeWaitForSingleObject(&event,Executive,KernelMode,FALSE,NULL);
returnIrp->IoStatus.Status;
}
|
Once we call IoCallDriver, we relinquish control of the IRP until something running in some arbitrary thread context calls IoCompleteRequest to signal completion of the IRP. IoCompleteRequest will then call our completion routine. Refer to Figure 6-3 for an illustration of the timing involved. The completion routine is particularly simple:
1 |
NTSTATUSOnRequestComplete(PDEVICE_OBJECTfdo,PIRPIrp,PKEVENTpev)
{
KeSetEvent(pev,0,FALSE);
returnSTATUS_MORE_PROCESSING_REQUIRED;
}
|
Figure 6-3. Timing of ForwardAndWait.
Notes on ForwardAndWait
I glossed over two subtleties when I described how ForwardAndWait and OnRequestComplete work together. It's sometimes possible for a thread's kernel stack to be swapped out of physical memory, but only while the thread is blocked in
user mode. See David Solomon's Inside Windows NT, Second Edition (Microsoft Press, 1998) at page 194 for a state diagram illustrating this possibility. All the calls inside ForwardAndWait that deal with the event object willcertainly fulfill the requirement that the event object be resident in memory. Since we specified a kernel mode wait, our stack can't be swapped out, so KeSetEvent will also find the event resident.Secondly, you might have noticed the absence of the
boilerplate code if(Irp->PendingReturned) IoMarkIrpPending(Irp) at the beginning of the completion routine. You don't need that statement in a completion routine that will return STATUS_MORE_PROCESSING_REQUIRED. The call can't hurt, of course, and is required in most standard completion routines. That's why all the DDK samples include the code even when it's notstrictly necessary.
In the
1 |
NTSTATUSHandleStartDevice(PDEVICE_OBJECTfdo,PIRPIrp)
{
Irp->IoStatus.Status=STATUS_SUCCESS;
NTSTATUSstatus=ForwardAndWait(fdo,Irp);
if(!NT_SUCCESS(status))
returnCompleteRequest(Irp,status,Irp->IoStatus.Information);
PIO_STACK_LOCATIONstack=IoGetCurrentIrpStackLocation(Irp);
status=StartDevice(fdo,
<additionalargs>
);
returnCompleteRequest(Irp,status,Irp->IoStatus.Information);
}
|
You might guess (correctly!) that the IRP_MN_START_DEVICE handler has work to do that concerns the transition from the initial STOPPED state to the WORKING state. I can't explain that yet because I need to first explain the
The I/O stack location's
Parameters
union has a
Table 6-2. Fields in the Parameters.StartDevice substructure of an IO_STACK_LOCATION.
|
Field
|
Description |
|---|---|
| AllocatedResources | Contains raw resource assignments |
| AllocatedResourcesTranslated | Contains translated resource assignments |
Both AllocatedResources and AllocatedResourcesTranslated are instances of the same kind of data structure, called a CM_RESOURCE_LIST. This seems like a very complicated data structure if you judge only by its declaration in WDM.H. As used in a start device IRP, however, all that remains of the complication is a great deal of typing. The "lists" will have just one entry, a CM_PARTIAL_RESOURCE_LIST that describes all of the I/O resources assigned to the device. You could use statements like the following to access the two lists:
PCM_PARTIAL_RESOURCE_LISTraw,translated; raw=stack->Parameters.StartDevice .AllocatedResources->List[0].PartialResourceList; translated=stack->Parameters.StartDevice .AllocatedResourcesTranslated->List[0].PartialResourceList; |
The only difference between the last two statements is the reference to either the AllocatedResources or AllocatedResourcesTranslated member of the parameters structure.
The raw and translated resource lists are the logical arguments to send to the StartDevice helper function, by the way:
status=StartDevice(fdo,raw,translated); |
There are two different lists of resources because I/O buses and the CPU can address the same physical hardware in different ways. The raw resources contain numbers that are bus-relative, whereas the translated resources contain
What you actually do with the resource descriptions inside your StartDevice function is a subject for the next chapter, "Reading and Writing Data."
The stop device request
1 |
NTSTATUSHandleStopDevice(PDEVICE_OBJECTfdo,PIRPIrp)
{
<complicatedstuff>
StopDevice(fdo,oktouch);
Irp->IoStatus.Status=STATUS_SUCCESS;
returnDefaultPnpHandler(fdo,Irp);
}
|
The
StopDevice
helper function called in the preceding example is code you write that
Touching the Hardware When Stopping the Device
In the skeleton of HandleStopDevice , I used an oktouch variable that I didn't show you how to initialize. In the scheme I'm teaching you in this book for writing a driver, the StopDevice function gets a BOOLEAN argument that indicates whether or not it should be safe to address actual I/O operations to the hardware. The idea behind this argument is that you may want to send certain instructions to your device as part of your shutdown protocol, but there might be some reason why you can't. You might want to tell your PCMCIA modem to hang up the phone, for example, but there's no point in trying if the end user has already removed the modem card from the computer.
There's no certain way to know whether your hardware is physically connected to the computer except by trying to access it. Microsoft recommends, however, that if you succeeded in processing a START_DEVICE request, you should go ahead and try to access your hardware when you process STOP_DEVICE and certain other PnP requests. When I discuss how you track PnP state changes later in this chapter, I'll
honor this recommendation by setting the oktouch argument TRUE if we believe that the device is currently working and FALSEotherwise .
Recall that the PnP Manager calls the AddDevice function in your driver to notify you about an instance of the hardware you manage and to give you an opportunity to create a device object. Instead of calling a function to do the complementary operation, however, the PnP Manager sends you a Plug and Play IRP with the minor function code IRP_MN_REMOVE_DEVICE. In response to that, you'll do the same things you did for IRP_MN_STOP_DEVICE to shut down your device, and then you'll delete the device object:
NTSTATUSHandleRemoveDevice(PDEVICE_OBJECTfdo,PIRPIrp)
{
PDEVICE_EXTENSIONpdx=(PDEVICE_EXTENSION)fdo->DeviceExtension;
<complicatedstuff>
DeregisterAllInterfaces(pdx);
StopDevice(fdo,oktouch);
Irp->IoStatus.Status=STATUS_SUCCESS;
NTSTATUSstatus=DefaultPnpHandler(fdo,Irp);
RemoveDevice(fdo);
returnstatus;
}
|
This fragment looks very similar to HandleStopDevice, with a couple of additions.
DeregisterAllInterfaces
will disable any device interfaces you registered (probably in AddDevice) and enabled (probably in StartDevice), and it will release the memory occupied by their symbolic link
1 |
VOIDRemoveDevice(PDEVICE_OBJECTfdo)
{
PDEVICE_EXTENSIONpdx=(PDEVICE_EXTENSION)fdo->DeviceExtension;
IoDetachDevice(pdx->LowerDeviceObject);
IoDeleteDevice(fdo);
}
|
You might be troubled by the fact that you call
IoDeleteDevice
at a time when the lower levels of the device hierarchy might still be processing the IRP_MN_REMOVE_DEVICE request. No harm can come from that, however, because the Object Manager maintains a reference count on your device object to prevent it from
Note, by the way, that you don't get a stop device request followed by a remove device request. The remove device request implies a shutdown, so you do both pieces of work in reply.
Sometimes the end user has the physical ability to remove a device without going through any user interface elements first. If the system detects that such a surprise removal has occurred, it sends the driver a PnP request with the minor function code IRP_MN_SURPRISE_REMOVAL. It will later send an IRP_MN_REMOVE_DEVICE. Unless you previously set the
SurpriseRemovalOK
flag while processing IRP_MN_QUERY_CAPABILITIES (as I'll discuss in Chapter 8, "Power Management"), the system also posts a dialog box to
In response to the surprise removal request, a device driver should disable any registered interfaces. This will give applications a chance to close handles to your device if they're on the lookout for the notifications I discuss later in "PnP Notifications." Then the driver should release I/O resources and pass the request down:
NTSTATUSHandleSurpriseRemoval(PDEVICE_OBJECTfdo,PIRPIrp)
{
PDEVICE_EXTENSIONpdx=(PDEVICE_EXTENSION)fdo->DeviceExtension;
<complicatedstuff>
EnableAllInterfaces(pdx,FALSE);
StopDevice(fdo,oktouch);
Irp->IoStatus.Status=STATUS_SUCCESS;
returnDefaultPnpHandler(fdo,Irp);
}
|
From Whence IRP_MN_SURPRISE_REMOVAL?
The surprise removal PnP notification doesn't happen as a simple and direct result of the end user yanking the device from the computer. Some bus drivers can know when a device disappears. For example, removing a universal serial bus (USB) device generates an electronic signal that the bus driver notices. For many other buses, however, there isn't any signal to alert the bus driver. The PnP Manager therefore relies on other
methods to decide that a device has disappeared.A function driver can signal the disappearance of its device (if it
knows ) by calling IoInvalidateDeviceState and then returning any of the values PNP_DEVICE_FAILED, PNP_DEVICE_REMOVED, or PNP_DEVICE_DISABLED from the ensuing IRP_MN_QUERY_PNP_DEVICE_STATE. You might want to do this in your own driver if—to give one example of many—your interrupt service routines (ISRs) read all 1-bits from a status port that normally returns a mixture of 1s and 0s. More commonly, a bus driver calls IoInvalidateDeviceRelations to trigger a reenumeration and then fails to report the newly missing device. It's worth knowing that when the end userremoves a device while the system is hibernating or in another low-power state, the driver receives a series of power management IRPs before it receives the IRP_MN_SURPRISE_REMOVAL request.What these facts mean, practically speaking, is that your driver should be able to cope with errors that might arise from having your device suddenly not present.

The Windows 2000 Device Driver Book: A Guide for Programmers (2nd Edition)

Windows System Programming (4th Edition) (Addison-Wesley Microsoft Technology Series)

Developing Drivers with the Windows Driver Foundation (Pro Developer)

Windowsu00ae Internals: Including Windows Server 2008 and Windows Vista, Fifth Edition (Pro Developer)