7.2.1 StartIo Routines in NT Device Drivers
As its name suggests, a StartIo routine in an NT device driver is responsible for starting an I/O operation on the physical device.
When an NT device driver’s StartIo routine is called, it can assume that the target device represented by the input device object is not busy. Either one of that device driver’s Dispatch routines has just called IoStartPacket and the IRP was not inserted into the device queue associated with the target device object, or the driver’s DpcForIsr routine is completing another request and has just called IoStartNextPacket.
Before the StartIo routine in a highest-level NT device driver is called, that driver’s Dispatch routine must have probed and locked down the user buffer, if necessary, to set up valid mapped buffer addresses in the IRP queued to its StartIo routine. Such a highest-level device driver that sets up its device objects for direct I/O (or for neither buffered nor direct I/O) cannot defer locking down a user buffer to the driver’s StartIo routine because every StartIo routine is called in an arbitrary thread context at IRQL DISPATCH_LEVEL.
In other words, NT driver designers should keep in mind the following fact:
Any buffer memory to be accessed by an NT driver’s StartIo routine must be locked down or allocated from resident, system-space memory and must be accessible in an arbitrary thread context.
NT drivers that set up their device objects for buffered I/O can rely on the I/O Manager to pass valid buffers in all IRPs sent to such a driver. Lower-level NT drivers that set up device objects for direct I/O can rely on the highest-level NT driver in their chain to pass valid buffers in all IRPs sent through any intermediate drivers to the underlying device driver.
In general, any NT device driver’s StartIo routine is responsible for calling IoGetCurrentIrpStackLocation with the input IRP and then doing whatever request-specific processing is necessary to start the I/O operation on its device, which can include the following:
·Setting up or updating any state information about the current request that the driver maintains in the device extension of the target device object or elsewhere in nonpaged pool allocated by the driver
For example, if a device driver maintains an InterruptExpected Boolean about the current transfer operation, its StartIo routine might set this variable to TRUE. If the driver maintains a time-out counter for the current operation, its StartIo routine might set up this value, or the StartIo routine might queue the driver’s CustomTimerDpc routine.
Note that if this state information or any other resource is shared with other driver routines, the state area or resource must be protected by a spin lock. If the StartIo routine shares state or resources with other nonISR routines, it must acquire the spin lock that protects the state or resource before accessing it. If the StartIo routine shares state with the driver’s ISR, StartIo must call KeSynchronizeExecution with a driver-supplied SynchCritSection routine that sets up the state.
For more information about CustomTimerDpc routines, see Chapter 14. For more information about the ISR and SynchCritSection routines, see Chapters 8 and 10, respectively. For more information about using spin locks, see Chapter 16.
·Assigning a sequence number to the IRP in case the driver must log a device I/O error while processing the IRP
For more information about logging I/O errors, see Chapter 16.
·If necessary, translating the parameters in the driver’s I/O stack location into device-specific values
For example, a disk driver might need to calculate the starting sector and/or byte offset to the physical disk address for a transfer operation, and whether the requested length of the transfer will cross a particular sector boundary or exceed the transfer capacity of its physical device.
·If the driver controls a removable-media device, checking for media changes before programming the device for I/O and notifying its overlying file system if the media has changed
For more information about handling removable media, see Chapter 16.
·If the device uses DMA, checking whether the requested Length (number of bytes to be transferred, found in the driver’s I/O stack location of the IRP) should be split into partial-transfer operations, as explained in Chapter 3, assuming a closely coupled higher-level driver does not pre-split large transfers for the device driver
The StartIo routine of such a device driver also can be responsible for calling KeFlushIoBuffers and, if the driver uses packet-based DMA, for calling IoAllocateAdapterChannel with the driver’s AdapterControl routine.
For more information about AdapterControl routines, see Chapter 11. For more information about maintaining cache coherency during DMA operations, see also Chapter 16.
·If the device uses PIO, mapping the base virtual address of the buffer, described in the IRP at Irp->MdlAddress, to a system-space address with MmGetSystemAddressForMdl, as explained in Chapter 3
For read requests, the device driver’s StartIo routine can be responsible for calling KeFlushIoBuffers before PIO operations begin. For more information about maintaining cache coherency during PIO, see also Chapter 16.
·If the driver uses a controller object, calling IoAllocateController with its ControllerControl routine
For more information about ControllerControl routines, see Chapter 11.
·If the driver handles cancelable IRPs, checking whether the input IRP has already been cancelled
·If an input IRP can be cancelled before it is processed to completion, the StartIo routine must call IoSetCancelRoutine with the IRP and the entry point of the driver’s Cancel routine. The StartIo routine must acquire the cancel spin lock for its call to IoSetCancelRoutine.
For more information about Cancel routines, see Chapter 12.