4.4.4 Starting Development

It is easier to develop any driver that is coded in stages, so here is a “cookbook” for getting a skeleton NT driver up and running quickly.

Getting a Skeleton Driver to Load

  1. Write a DriverEntry routine that calls IoCreateDevice to create a single named device object (see Chapter 3 for how to set up a device object).

  2. Next, write a skeleton Dispatch routine for IRP_MJ_CREATE requests (see Section 4.4.3 for a description of the minimum work a DispatchCreate routine must do), and revise the DriverEntry routine to set the DispatchCreate entry point in the driver object (see Chapter 2 for how to set an entry point in the driver object).

    For any request, a higher-level NT driver’s DriverEntry routine must establish a connection to a next-lower-level driver (see Chapter 5), and its Dispatch routine must set up the next-lower driver’s I/O stack location in IRPs.

  3. Compile and link your driver.

  4. Test this skeleton driver by doing the following:

    • Set up a symbolic link between a Windows NT logical device name (such as LPT1 or drive E:) and the target device object’s name, in the registry. Use regedt32 to add a new symbolic link (of type REG_SZ) between a Win32-visible device name and the target device object’s name in the registry key \HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control
      \Session Manager\DOS Devices
      .

      You must reboot the machine for this to take effect, but the newly created symbolic link is persistent across system boots thereafter in your machine. Later, the retail driver should set up these symbolic links with the corresponding protected subsystem(s)’ device names in the registry. For more information about how to set up these symbolic links, see Chapter 16.

    • Write a simple test program to call the Win32 function CreateFile with the Win32 name for the appropriate file object to be sure your driver was loaded and that the NT I/O Manager can send IRPs to your driver’s DispatchCreate routine.

Supporting an Additional Request to More Target Device Objects

  1. Revise the DriverEntry routine to create other named device objects, if any, that the driver will need and to set up a Dispatch entry point for IRP_MJ_CLOSE requests.

    Note that the skeleton Dispatch routine for IRP_MJ_CREATE requests can usually handle IRP_MJ_CLOSE requests for an NT device or intermediate driver, so whether you write a separate DispatchClose routine is entirely up to you.

  2. Compile and link your revised driver.

  3. Test the driver as described in the preceding subsection to be sure that all the driver’s new device objects can be opened and closed with calls to the Win32 CreateFile and CloseHandle functions.

Supporting Device I/O Requests

  1. Add another Dispatch routine for an IRP_MJ_XXX that requires more processing than a create/close request.

    For example, write a DispatchRead routine that reads data from the device into memory or that intercepts read requests for a set of lower drivers, that is, handles IRP_MJ_READ requests.

  2. Revise the DriverEntry routine to set the DispatchRead entry point in the driver object and to set up any NT objects that handling read requests will require. Write any additional driver routines necessary to process a read request.

    • A lowest-level driver would need to have at least skeleton StartIo, ISR, and DpcForIsr routines, would probably need to have a SynchCritSection routine, and would need to have a skeleton AdapterControl routine if the device uses DMA.

      The DriverEntry routine must set the StartIo entry point in the driver object, and set up the driver’s other new routines. The DriverEntry routine must register the ISR (interrupt object) and would need to set up an adapter object for DMA. It also must initialize the physical device. Note that the ISR could be called immediately following a successful return from IoConnectInterrupt, so the device-initialization code might need to disable interrupts at the device before the DriverEntry routine registers the ISR (and re-enable interrupts at the device later).

    • A higher-level driver could have a skeleton IoCompletion routine that, at least, checks the I/O status block for STATUS_SUCCESS and calls IoCompleteRequest with the IRP.

      The DriverEntry routine must set the DispatchRead entry point in the driver object. The DispatchRead routine must call IoSetCompletionRoutine to set the driver’s new IoCompletion routine in an IRP.

  3. Revise the internal structure of the device extension, as necessary:

    • For such a lowest-level driver, the device extension should provide storage for an interrupt object pointer, for any context area to be shared between the ISR and StartIo or DpcForIsr routines, plus storage for adapter object information if the device uses DMA.

    • For such a higher-level driver, the device extension must provide storage for the next-lower-level driver’s device object pointer. Otherwise, its contents and structure are driver-defined.

  4. Compile and link your revised driver.

  5. Test this set of routines by calling the Win32 CreateFile, ReadFile, and CloseHandle functions, and by using the NT kernel debugger to trace the path of IRPs through the driver’s new standard routines (see Section 4.2 or 4.3).

Continue to add functionality to your driver by coding new Dispatch routines and, in higher-level drivers, possibly new IoCompletion routines to handle the set of required IRP_MJ_XXX. Revise the DriverEntry routine and device extension (or other driver-allocated storage) as required. Flesh out existing routines in the driver as you add new Dispatch routines, such as adding error logging. Test the driver at each development stage, and use the debugger to trace IRPs through your driver to discover any bugs.

For more information about logging I/O errors, see Chapter 16. For more information about debugging NT drivers, see the Programmer’s Guide.