The DriverEntry Routine
In preceding sections, 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:
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 change the build script, but why bother?).
Sample Code
You can experiment with the ideas discussed in this chapter using the STUPID sample driver. STUPID implements DriverEntry and AddDevice but nothing else. It s similar to the very first driver I attempted to write when I was learning.
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 me (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 and OUT are both 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 IN OUT parameter is used for both input and output. As it happens, the DDK headers don t always 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 and Jerry Lozano s The Windows 2000 Device Driver Book (Prentice Hall, 2d ed., 2001) 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. In a WDM driver, the only use I ve ever made of this string is as part of WMI registration. (See Chapter 10.)
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:
Set this to point to whatever cleanup routine you create. The I/O Manager will call this routine just prior to unloading the driver. If there s nothing to clean up, you need to have a DriverUnload function for the system to be able to unload your driver dynamically.
Set this to point to your AddDevice function. The PnP Manager will call AddDevice once for each hardware instance you re responsible for. Since AddDevice is so important to the way WDM drivers work, I ve devoted the next main section of this chapter ( The AddDevice Routine ) to explaining what it does.
If your driver uses the standard method of queuing I/O requests, you ll set this member of the driver object to point to your StartIo routine. Don t worry (yet, that is) if you don t understand what I mean by the standard queuing method; all will become clear in Chapter 5, where you ll discover that WDM drivers shouldn t use it.
The I/O Manager initializes this vector of function pointers to point to a dummy dispatch function that fails every request. You re presumably going to be handling certain types of IRPs otherwise, your driver is basically going to be deaf and inert so you ll set at least some of these pointers to your own dispatch functions. Chapter 5 discusses IRPs and dispatch functions in detail. For now, all you need to know is that you must handle two kinds of IRPs and that you ll probably be handling several other kinds as well.
A nearly complete DriverEntry routine will, then, look like this:
extern "C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath) { DriverObject->DriverUnload = DriverUnload; DriverObject->DriverExtension->AddDevice = AddDevice; 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); servkey.Buffer[RegistryPath->Length/sizeof(WCHAR)] = 0; return STATUS_SUCCESS; }
These two statements set the function pointers for entry points elsewhere in the driver. I elected to give them simple names indicative of their function: DriverUnload and AddDevice.
Every WDM driver must handle PNP, POWER, and SYSTEM_CONTROL I/O requests, and it should handle SYSTEM_CONTROL I/O requests. This is where you 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 XP DDK, which is why I called my dispatch function DispatchWmi.
In place of this ellipsis, you ll have code to set several additional MajorFunction pointers.
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. 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.
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.
Subroutine Naming
Many driver writers give the subroutines in their drivers names that include the name of the driver. For example, instead of defining AddDevice and DriverUnload functions, many programmers would define Stupid_AddDevice and Stupid_DriverUnload. I m told that earlier versions of Microsoft s WinDbg debugger forced a convention like this onto (possibly unwilling) programmers because it had just one global namespace. Later versions of this debugger don t have that limitation, but you ll observe that the DDK sample drivers still follow the convention.Now, I m a great fan of code reuse and an indifferent typist. For me, it has seemed much simpler to have short subroutine names that are exactly the same from one project to the next. That way, I can just lift a body of code from one driver and paste it into another without needing to make a bunch of name changes. I can also compare one driver with another without having extraneous name differences clutter up the comparison results.
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 doesn t 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.