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
-
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).
-
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.
-
Compile and link your driver.
-
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
-
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.
-
Compile and link your revised driver.
-
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
-
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.
-
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.
-
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.
-
Compile and link your revised driver.
-
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.