[Previous] [Next]

The DriverEntry Routine

In the preceding section, I said that the PnP Manager loads the drivers needed for hardware and calls their AddDevice functions. A given driver might be used for more than one piece of similar hardware, and there's some global initialization that the driver needs to perform only once when it's loaded for the first time. That global initialization is the responsibility of the DriverEntry routine.

DriverEntry is the name conventionally given to the main entry point to a kernel-mode driver. The I/O Manager calls the routine as follows:

extern "C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject,
  IN PUNICODE_STRING RegistryPath)
  {
  ...
  }

NOTE
You call the main entry point to a kernel-mode driver "DriverEntry" because the build script—if you use standard procedures—will instruct the linker that DriverEntry is the entry point, and it's best to make your code match this assumption (or else to change the build script, but why bother?).

Before I describe the code you'd write inside DriverEntry, I want to mention a few things about the function prototype itself. Unbeknownst to you and I (unless we look carefully at the compiler options used in the build script), kernel-mode functions and the functions in your driver use the _ _stdcall calling convention when compiled for an x86 computer. This shouldn't affect any of your programming, but it's something to bear in mind when you're debugging. I used the extern "C" directive because, as a rule, I package my code in a C++ compilation unit—mostly to gain the freedom to declare variables wherever I please instead of only immediately after left braces. This directive suppresses the normal C++ decoration of the external name so that the linker can find this function. Thus, an x86 compile produces a function whose external name is _DriverEntry@8.

Another point about the prototype of DriverEntry is those "IN" keywords. IN, OUT, and INOUT are all noise words that the DDK defines as empty strings. By original intention, they perform a documentation function. That is, when you see an IN parameter, you're supposed to infer that it's purely input to your function. An OUT parameter is output by your function, while an INOUT parameter is used for both input and output. As it happens, the DDK headers don't really use these keywords intuitively, and there's not a great deal of point to them. To give you just one example out of many: DriverEntry claims that the DriverObject pointer is IN; indeed, you don't change the pointer, but you will assuredly change the object to which it points.

The last general thing I want you to notice about the prototype is that it declares this function as returning an NTSTATUS value. NTSTATUS is actually just a long integer, but you want to use the typedef name NTSTATUS instead of LONG so that people understand your code better. A great many kernel-mode support routines return NTSTATUS status codes, and you'll find a list of them in the DDK header NTSTATUS.H. I'll have a bit more to say about status codes in the next chapter; for now, just be aware that your DriverEntry function will be returning a status code when it finishes.

Overview of DriverEntry

The first argument to DriverEntry is a pointer to a barely initialized driver object that represents your driver. A WDM driver's DriverEntry function will finish initializing this object and return. Non-WDM drivers have a great deal of extra work to do—they must also detect the hardware for which they're responsible, create device objects to represent the hardware, and do all the configuration and initialization required to make the hardware fully functional. The relatively arduous detection and configuration steps are handled automatically for WDM drivers by the PnP Manager, as I'll discuss in Chapter 6. If you want to know how a non-WDM driver initializes itself, consult Art Baker's The Windows NT Device Driver Book (Prentice Hall, 1997) and Viscarola and Mason's Windows NT Device Driver Development (Macmillan, 1998).

The second argument to DriverEntry is the name of the service key in the registry. This string is not persistent—you must copy it if you plan to use it later.

A WDM driver's main job in DriverEntry is to fill in the various function pointers in the driver object. These pointers indicate to the operating system where to find the subroutines you've decided to place in your driver container. They include these pointer members of the driver object:

A nearly complete DriverEntry routine would, then, look like this:




1 


2 


3 
4 





5 
extern "C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject,
  IN PUNICODE_STRING RegistryPath)
  {
  DriverObject->DriverUnload = DriverUnload;
  DriverObject->DriverExtension->AddDevice = AddDevice;
  DriverObject->DriverStartIo = StartIo;
  DriverObject->MajorFunction[IRP_MJ_PNP] = DispatchPnp;
  DriverObject->MajorFunction[IRP_MJ_POWER] = DispatchPower;
  DriverObject->MajorFunction[IRP_MJ_SYSTEM_CONTROL] = DispatchWmi;
  ...
  servkey.Buffer = (PWSTR) ExAllocatePool(PagedPool,
    RegistryPath->Length + sizeof(WCHAR));
  if (!servkey.Buffer)
    return STATUS_INSUFFICIENT_RESOURCES;
  servkey.MaximumLength = RegistryPath->Length + sizeof(WCHAR);
  RtlCopyUnicodeString(&servkey, RegistryPath);
  return STATUS_SUCCESS;
  }

  1. These three statements set the function pointers for entry points elsewhere in the driver. I elected to give them simple names indicative of their function: DriverUnload, AddDevice, and StartIo.
  2. Every WDM driver must handle PNP, POWER, and SYSTEM_CONTROL I/O requests; this is where you'd specify your dispatch functions for these requests. What's now IRP_MJ_SYSTEM_CONTROL was called IRP_MJ_WMI in some early beta releases of the Windows 2000 DDK, which is why I called my dispatch function DispatchWmi.
  3. In place of this ellipsis, you'd have code to set several additional MajorFunction pointers.
  4. If you ever need to access the service registry key elsewhere in your driver, it's a good idea to make a copy of the RegistryPath string here. If you're going to be acting as a WMI (Windows Management Instrumentation) provider (as I discuss in Chapter 10, "Windows Management Instrumentation"), you'll need to have this string around, for example. I've assumed that you declared a global variable named servkey as a UNICODE_STRING elsewhere. I'll explain the mechanics of working with Unicode strings in the next chapter.
  5. Returning STATUS_SUCCESS is how you indicate success. If you were to discover something wrong, you'd return an error code chosen from the standard set in NTSTATUS.H or from a set of error codes that you define yourself. STATUS_SUCCESS happens to be numerically 0.

DriverUnload

The purpose of a WDM driver's DriverUnload function is to clean up after any global initialization that DriverEntry might have done. There's almost nothing to do. If you made a copy of the RegistryPath string in DriverEntry, though, DriverUnload would be the place to release the memory used for the copy:

VOID DriverUnload(PDRIVER_OBJECT DriverObject)
  {
  RtlFreeUnicodeString(&servkey);
  }

If your DriverEntry routine returns a failure status, the system does not call your DriverUnload routine. Therefore, if DriverEntry generates any side effects that need cleaning up prior to returning an error status, DriverEntry has to perform the cleanup.

Driver Reinitialization Routine

The I/O Manager provides a service function, IoRegisterDriverReinitialization, that solves a peculiar problem for non-WDM drivers, and I want to explain what it does so you'll know why you don't need to worry about it. Non-WDM drivers need to enumerate their hardware at DriverEntry time. It might happen that a non-WDM driver must load and initialize before all possible instances of its own hardware have been identified. This is true for mouse and keyboard devices, for example. But, if DriverEntry is supposed to enumerate all the mice or keyboards and create device objects for them, these drivers can't do their work properly if their DriverEntry routine runs too soon. They use IoRegisterDriverReinitialization to register a routine that the I/O Manager will call back the next time someone detects new hardware. The reinitialization routine can then try again and, potentially, register itself for even later callbacks.

WDM drivers shouldn't need to register reinitialization routines because they don't rely on their own resources to detect hardware. The PnP Manager will automatically match up newly arrived hardware to the right WDM driver and call that driver's AddDevice routine (the subject of the next section) to do all the necessary initialization work.