The following suggests a general set of design criteria that any NT driver writer can use to start designing a driver.
Which I/O Requests?
Before writing any code, see the Kernel-mode Driver Reference to determine which IRP major function codes your driver must handle:
·If you are designing a device driver, your driver must handle the same set of IRP_MJ_XXX and device I/O control codes as every other NT driver for the same type of peripheral device.
·If you are designing an intermediate NT driver, identify the underlying physical device or devices your driver will be layered over, because a higher-level driver must have Dispatch entry points for most IRP_MJ_XXX that the drivers under it must handle. A higher-level NT driver must set up the next-lower driver's I/O stack location in IRPs and pass them on to lower drivers with IoCallDriver when the parameters in its own I/O stack location of each such IRP are valid. Consequently, any intermediate NT driver must set Dispatch entry points in its driver object for those IRP_MJ_XXX to be passed on.
How Many Dispatch Routines?
When you have identified the IRP_MJ_XXX your driver must handle, you can determine the upper limit on the number of Dispatch routines your driver might have. You can also start considering whether to combine particular IRP_MJ_XXX into convenient subsets to be handled by particular Dispatch routines.
For example, most lowest-level and intermediate NT drivers need do little or no device or IoCompletion processing for an IRP_MJ_CREATE (equivalent to an "open target device" request for these types of drivers) or IRP_MJ_CLOSE request. Except for underlying disk devices and NT drivers with pageable-image sections, IRPs containing IRP_MJ_CREATE usually just establish the existence of the target device object for higher-level drivers and for user-mode protected subsystems via the I/O Manager's system services. For lower-level drivers, IRPs containing IRP_MJ_CLOSE usually indicate that a user-mode subsystem on behalf of an application has closed the file object handle for the device to which it was sending I/O requests.
For create/close requests, many NT device and intermediate drivers simply set the IRP's I/O status block with STATUS_SUCCESS in the Status member and zero in the Information member and, then, call IoCompleteRequest with the IRP from their Dispatch routines. Consequently, many lowest-level and intermediate NT drivers have a combined Dispatch routine for create/close requests.
However, a serial device driver usually resets its device for a create request; it can lock down a pageable-image section when it processes create requests and unlock its pageable-image section when it processes close requests. Disk device drivers are called with create requests only when a higher-level driver calls IoGetDeviceObjectPointer or IoAttachDevice; because any disk driver might control the device that holds the system page file, they are not called with close requests. On the other hand, NT file system drivers do considerably more processing for create/close requests.
If you are designing a higher-level NT driver, consider the set of IRP major function codes your driver must handle and determine the set of requests for which you might need to implement an IoCompletion routine.
As a general rule, a higher-level NT driver need not have an IoCompletion routine for each kind of request. It must have an IoCompletion routine to dispose of the IRPs the driver allocates, if any. Otherwise, it needs IoCompletion routines only for those IRP_MJ_XXX that could require further processing in the higher-level driver, depending on how lower drivers handle a given IRP_MJ_XXX request.
How Many Device Objects?
Next, consider how many named device objects your driver must create:
·A device driver must create a named device object for each physical or logical device that could be the target of an I/O request. Some lowest-level NT drivers must create an indeterminate number of device objects, as mentioned in Chapter 2. For example, a disk driver must create a device object for each physical disk and additional device objects for partitions on its disks.
·A higher-level driver must create a named device object to represent its own virtual device, so that still-higher-level drivers can connect their device objects to this driver's device object(s). In addition, a higher-level driver usually creates a set of virtual or logical device objects that correspond to the named device objects created by the next-lower driver(s).
You can develop your driver in stages, as outlined in Section 4.4.4, so an under-development driver need not create every device object that the fully developed driver will have. Nevertheless, determining how many device objects the driver must eventually create helps to identify any synchronization problems the driver writer must solve.
Determining the number of necessary or possible device objects also can help a driver writer in defining the driver-determined contents and structure of the device extension(s) for the driver's device object(s). For example, if the device extension will contain context shared between a device driver's ISR and its other routines, consider setting up the device extension to isolate all the shared context in a part of the device extension. The driver's nonISR routines can then access other areas in the device extension without having to call KeSynchronizeExecution with the SynchCritSection routine, discussed in Chapter 10.
As another example, if one nonISR routine shares an area with another, the driver's device extension would likely contain an executive spin lock, used to protect that area from simultaneous access by both routines. For more information about excecutive spin locks, see Chapter 16.
StartIo Routine or Queue Management in Driver Thread Context?
If you are writing a driver for a physical device, determine whether setting up the device for I/O requires the driver to wait for longer than around 50 microseconds for device state changes:
·If so, your driver should have a device-dedicated thread or, in a file system driver, possibly one or more worker-thread callback routines. Such a driver must manage its own internal queues of IRPs, as the system floppy controller driver mentioned in Chapter 2 does.
·Otherwise, your driver can have a StartIo routine and call IoStartPacket from its Dispatch routines and IoStartNextPacket or IoStartNextPacketByKey from its DpcForIsr (or CustomDpc) routine, as described in Section 4.2, thereby relying on the I/O Manager to manage the queueing of IRPs for your driver.