52964.fb2
This chapter looks at how to install WDM drivers using installation INF files and describes the process of finding INF files using Hardware and Compatible IDs. The chapter then looks at how to install NT style (non-WDM) drivers. Example Win32 installation code is listed.
A WDM driver is installed if a new device is detected by a bus driver, or if you install a device using the Add New Hardware wizard in the Control Panel. A later section describes how the right driver is found. In both cases, the Plug and Play (PnP) Manager adds entries for the device and its driver to its configuration tables in the registry.
A driver is installed by following the instructions in an INF file. The driver executable is copied to the right location, usually the Windows System32\Drivers directory. Then various registry entries have to be made.
Some devices may now need to have some system resources assigned to them, such as I/O Port addresses and interrupt numbers. The PnP Manager may need to juggle the resources that have been assigned to existing devices to make the desired resources available to the new device. If necessary, existing devices are stopped (if they agree) and their resources reassigned.
If the driver is not already present, it is loaded and its DriverEntry routine is called. The driver AddDevice routine is called for the new device. Various Plug and Play IRPs are then sent to the device as detailed in Chapter 9. This process culminates when the Start Device PnP IRP is sent to tell a driver which resources it has been assigned. The driver uses these resource assignments to start its device. Normal I/O requests can then proceed.
The INF file that was used is copied to the Windows INF subdirectory. In Windows 98, they are put in the INF\OTHER subdirectory, with the INF filename changed to include the Manufacturer name. In Windows 2000, the INF file is copied to the next available OEM*.INF filename in the INF directory (e.g., OEM4.INF). If you reinstall a driver with the Have Disk option, the new INF file is copied to a new OEM*. INF file. It is probably best if you delete any out-of-date OEM*.INF files.
Please note three important points about installation.
• The driver executable must have an 8.3 filename in Windows 98.
• Windows will not use the INF file if it contains invalid section details.
• Section names must be 28 characters or less to work in W98.
If you want to install a driver that is required in the text-mode setup phase of W2000 installation, you must provide a txtsetup.oem file on a floppy disk. See the DDK for details.
An INF file contains all the necessary information to install a WDM device driver, including a list of the files to copy, the registry entries to make, etc.
Windows provides a standard installer for most classes of device and a default installer. The default class installer processes INF files in the manner described in the rest of this chapter. Installers for other classes of device may perform some additional installation steps.
An INF file is a text file that looks like an old INI file. It is made up of sections, each starting with a line with the section name in square brackets (e.g., [Version]). The section contents follow. Each line either has a simple entry (e.g., entry) or sets a value (e.g., entry=value). Sections can be in any order. The order of lines in a section is sometimes important. Comments can be added after a semicolon character. Section and entry names are not case sensitive. A backslash at the end of a line in a section indicates that the line continues onto the next. Double-quotes are used to ensure the correct interpretation of characters (e.g., a backslash in a filename or leading or trailing spaces in strings). INF files can be in Unicode or plain ANSI characters. In some sections, you can opt to include other INF files needed for the installation.
If you need to customize the installation process, consider writing a class installer or — for Windows 2000 — a class coinstaller or a device coinstaller. A class installer takes over the installation of a whole class of devices; it can choose which installation jobs it handles and which it leaves to the default class installer. A coinstaller helps an existing class installer to do its job. A class coinstaller helps install all new devices of a particular class, while a device coinstaller helps just the installation of the device currently being installed. Coinstallers should not interact with the user. Please see the W2000 DDK for details of how to write these DLLs.
Table 11.1 shows the typical sections of an INF file. Sections that you must name are given in italics. The DDK has full details of these sections and the other ones that you can include. Most section names may also exist in a decorated form, as described in the following text. For example, Strings.LangIdSubLangId indicates a locale and SectionName.NT indicates a platform.
The Version section is usually placed at the start of the INF file. Any of the given values for Signature work for both Windows 98 and Windows 2000. See later for details of how to write cross-platform INF files for both Windows 98 and Windows 2000.
Once your driver has passed the Microsoft Hardware Compatibility Tests, you should include a reference to your digital signature file in the CatalogFile entry.
Table 11.1 Typical sections for an INF file
Section | Entry | Value description |
---|---|---|
Version | Signature | $Windows NT$, $Windows 95$ or $Chicago$ |
Provider | INF file creator | |
Class | One of the system-defined classnames (listed in the DDK) or your new class name. | |
ClassGuid | The matching class GUID. | |
DriverVer | mm/dd/yyyy [,a.b.c.d]. | |
CatalogFile[.NTetc] | Digitally-signed catalog file. | |
Strings | %string%="value" | Specifies a string. |
SourceDisksNames | For each distribution floppy disk or CD-ROM, specifies its descriptionand possibly the cabinet file and directory. | |
SourceDisksFiles | Specifies the filename, the source disk ID and optionally the subdirectory and file size. This section can be empty if all the files are in the root directory. | |
DestinationDirs | DefaultDestDir=dirid[,subdir] filelist=dirid[,subdir] | Specifies the directory ID, and optionally the subdirectory, for default file copies and file copies in the filelist section. The dirid is a number indicating in which standard location to put the files (see the following). |
Manufacturer | %manufacturer_name%=models | Specifies the manufacturer name and the name of the corresponding models section. |
models | Specifies a product name, the name of the corresponding install section, a Hardware ID, and zero or more Compatible IDs. | |
install.Interfaces | List of device interfaces to add. Further AddInterface sections can specify more details, such as registry entries to add to the device interface key. | |
install | CopyFiles=@filename | filelist | Specifies a file to copy, or the name of the filelist section where the files are listed. |
AddReg=addreg | Specifies the name of the addreg section. | |
LogConfig=logconfig | For legacy devices, specifies the name of the logconfig section, in which I/O addresses, IRQ configurations, etc., are detailed. | |
DriverVer | mm/dd/yyyy [,a.b.c.d] | |
ProfileItems | List of ProfileItem sections specifying items to add to the Start Menu. | |
filelist | A list of the files to be installed | |
addreg | Add new keys and values. | |
logconfig | Legacy device configurations. | |
install.AddService | ServiceType=1 StartType=start-code ErrorControl=error-control-level ServiceBinary=path-to-driver etc. | For W2000 drivers, specifies the driver service details. |
The Strings section defines strings that are substituted wherever else they are used. For example, if the Strings section looks like this:
[Strings]
Msft="Microsoft"
Any instance elsewhere of %Msft% is replaced with Microsoft. Strings are particularly useful for representing GUIDs, but can be used for any type of string, even bit values.
You can internationalize your INF file using strings in two ways. One way is to create a base INF file without any strings in an .inx file, and have a separate .txt file for each locale with the appropriate Strings section. Alternatively, have a common INF file but different Strings sections for each locale, appending .LangIDSubLangID to detail which language and sublanguage applies to this section. LangID and SubLangID are both two digits, as defined in winnt.h. SubLangID 00 is a neutral sublanguage. The following example shows how a different string is defined for UK English.
[Strings]
PrinterName="Abc Color printer"
[Strings.0902]
PrinterName="Abc Colour printer"
The sections in an INF file are arranged in a hierarchy. Table 11.2 shows that the Version, Strings, SourceDisksNames, SourceDisksFiles, and DestinationDirs sections are at the top level.
The Manufacturer section has a list of manufacturers (i.e., a list of manufacturer names and their section names). Table 11.2 has two manufacturers, Abc Inc and Xyz Ltd. Each manufacturer has a section that lists the product models that this INF file describes. Each product model defines various IDs and an installation section base name.
The Models section for Abc, Inc. is called Abc.Inc. This section has entries for each product that it sells. The entry for Product1 specifies that the Product1.Install section contains the main installation instructions. In this case, an optional Product1.Install.Services section has also been included. More optional install sections can be included.
The Product1.Install section has entries that point to yet more sections. The Product1.CopyFiles section lists the files to copy. The Product1.AddReg section lists the entries that must be made in the registry. Further sections at this level can be added for other installation options.
The Product1.Install.Services section is only used in Windows 2000 to specify a service entry.
Table 11.2 INF file section hierarchy
[Version]
[Strings]
[SourceDisksNames]
[SourceDisksFiles]
[DestinationDirs]
[Manufacturer]
Abc, Inc.=Abc.Inc
Xyz Ltd=Xyz.Ltd
[Abc.Inc]
Product 1=Product1.Install.HardwareId
Product 2=Product2.Install.HardwareId
[Product1.Install]
CopyFiles=Product1.CopyFiles
AddReg=Product1.AddReg
[Product1.CopyFiles]
…
[Product1.AddReg]
…
[Product1.Install.Services]
AddService=Product1,0x00000002,Product1.Service
[Product1.Service]
…
[Product2.Install]
…
…
[Xyz.Ltd]
…
The quickest way to explain how an INF file works is to use a real example. Listing 11.1 shows the INF file for the Wdm1 driver, Wdm1free.Inf.
It kicks off with a Version section, which says that all the drivers and devices installed by this INF file belong to the Unknown device class. The Provider entry is set to %WDMBook%. The Strings section at the end replaces %WDMBook% with its full name, WDM Book.
The SourceDisksNames section lists the installation disks. The first and only disk is labelled "Wdm1 build directory." The SourceDisksFiles section covers W98 and specifies that the only driver file, Wdm1.sys, is found on installation disk 1 and is found in subdirectory obj\i386\free. These options make it easy to install the Wdm1 driver directly from its development location. For a commercial release, it is simpler to put the driver files in the root directory. The SourceDisksFiles.x86 section covers W2000 and specifies that Wdm1.sys is found in the objfre\i386 subdirectory.
The Manufacturer section lists just one manufacturer, again called WDM Book. There is just one product model defined here in the WDM.Book section. The Wdm1 model name %Wdm1% is the one that is shown to the user, "WDM Book: Wdm1 Example, free build". The %Wdm1% model has a Hardware ID of *wdmBook\Wdml. Hardware IDs are covered later.
The Wdm1.Install section has the instructions for installing the Wdm1 driver in Windows 98. The files to copy are listed in the Wdm1.Files.Driver section and the registry entries are listed in the Wdm1.AddReg section.
Legacy non-Plug and Play devices may also have LogConfig sections to specify the resources that a device needs. See Chapter 15 for an example of this.
The Wdm1.Files.Driver section simply lists the files that must be installed. The DestinationDirs section specifies where the files listed in the Wdm1.Files.Driver section should go.
The Wdm1.AddReg section specifies that two registry entries should be created for the driver, DevLoader with *ntkern and NTMPDriver with Wdm1.sys. These entries are described in detail later.
That wraps it up for a Windows 98 installation. I shall look at the remaining Windows 2000 sections later.
Listing 11.1 Wdm1free.inf
; Wdm1free.Inf – install information file
; Copyright © 1998,1999 Chris Cant, PHD Computer Consultants Ltd
[Version]
Signature="$Chicago$"
Class=Unknown
Provider=%WDMBook%
DriverVer=04/26/1999,1.0.6.0
[Manufacturer]
%WDMBook% = WDM.Book
[WDM.Book]
%Wdm1%=Wdm1.Install, *wdmBook\Wdm1
[DestinationDirs]
Wdm1.Files.Driver=10,System32\Drivers
Wdm1.Files.Driver.NTx86=10,System32\Drivers
[SourceDisksNames]
1="Wdml build directory",,,
[SourceDisksFiles]
Wdm1.sys=1,obj\i386\free
[SourceDisksFiles.x86]
Wdm1.sys=1,objfre\i386
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Windows 98
[Wdm1.Install]
CopyFiles=Wdm1.Files.Driver
AddReg=Wdm1.AddReg
[Wdm1.AddReg]
HKR,,DevLoader,,*ntkern
HKR,,NTMPDriver,,Wdm1.sys
[Wdm1.Files.Driver]
Wdm1.sys
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Windows 2000
[Wdm1.Install.NTx86]
CopyFiles=Wdm1.Files.Driver.NTx86
[Wdm1.Files.Driver.NTx86]
Wdm1.sys,,,%COPYFLG_NOSKIP%
[Wdm1.Install.NTx86.Services]
AddService = Wdm1, %SPSVCINST_ASSOCSERVICE%, Wdm1.Service
[Wdm1.Service]
DisplayName = %Wdm1.ServiceName%
ServiceType = %SERVICE_KERNEL_DRIVER%
StartType = %SERVICE_DEMAND_START%
ErrorControl = %SERVICE_ERROR_NORMAL%
ServiceBinary = %10%\System32\Drivers\Wdm1.sys
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Strings
[Strings]
WDMBook="WDM Book"
Wdm1="WDM Book: Wdm1 Example, free build"
Wdm1.ServiceName="WDM Book Wdm1 Driver"
SPSVCINST_ASSOCSERVICE=0x00000002 ; Driver service is associated with device being installed
COPYFLG_NOSKIP=2 ; Do not allow user to skip file
SERVICE_KERNEL_DRIVER=1
SERVICE_AUTO_START=2
SERVICE_DEMAND_START=3
SERVICE_ERROR_NORMAL=1
DestinationDirs Section
Each line in the DestinationDirs section specifies the base directory for files listed in a CopyFiles section.
The W2000 DDK lists the directory codes. For Windows 2000, code 12 means System32\ drivers in the Windows directory, but in Windows 98, it means System32\ioSubsys. However, WDM drivers must be installed in the Windows System32\drivers directory in both versions of Windows. Therefore, code 10 (for the Windows directory) must be used, with System32\Drivers specified as a subdirectory.
The Wdm1.Files.Driver entry is therefore set to 10,System32\Drivers, where 10 is code for the Windows directory (e.g., C:\Windows or C:\WINNT). The second field is the subdirectory to add to this base directory. Therefore, the Wdm1 driver files are stored in the Windows System32\Drivers directory.
AddReg Section
An AddReg section specifies what entries to add to the registry. Each line specifies an entry of the form:
reg_root,[subkey],[value_entry_name],[flags],[value]
reg_root specifies the root registry key, from the list in Table 11.3. The optional subkey field specifies a subkey off the root key. value_entry_name is the entry name to add, flags indicates the entry type, and value is its value, flags can be omitted for REG_SZ values.
Table 11.3 Possible AddReg reg_root values
HKCR | HKEY_CLASSES_ROOT |
HKCU | HKEY_CURRENT_USER |
HKLM | HKEY_LOCAL_MACHINE |
HKU | HKEY_USERS |
HKR | The most relevant relative registry key |
HKR is the most useful root registry key. It specifies the "relevant" registry entry for the section in which it appears. In the case of the Wdm1.AddReg section, it is the driver key. In Windows 98 for the Wdm1 driver, this key is HKLM\System\CurrentControlSet\Services\Class\Unknown\0000. The 0000 will be replaced with the appropriate Unknown device number.
For the Wdm1.AddReg section, the default installer consequently adds DevLoader and NTMP-Driver values to this key. The DDK does not state why these particular entries are necessary; just do it. This driver key ends up with other values, as you can see if you inspect it using RegEdit.
In this INF file, Windows 2000 does not use the Wdm1.AddReg section. Instead, as I shall show, the Wdm1.Service section makes some different registry entries for the driver (in HKLM\System\CurrentControlSet\Services\Wdm1).
Other Registry Entries
The installation process also creates various entries for the Wdm1 device that was just installed. The INF file does not have to alter or add to these entries.
In Windows 98, the device has a registry key at HKLM\Enum\Root\Unknown\0000, with 0000 changed to a different Unknown device number, if appropriate. In Windows 2000, the device registry key is HKLM\System\CurrentControlSet\Control\Class\{4D36E97E…}\0000, where {4D36E97E… } is the GUID for Unknown type devices.
When the Wdm1 driver runs, it also registers its device interface with GUID {C0CF0640-5F6E-11d2-B677-00C0DFE4C1F3}, WDM1_GUID. In both Windows 98 and Windows 2000, the registry key for this is HKLM\System\CurrentControlSet\Control\DeviceClasses\{C0CF0640…}. There are various subkeys for each device and their associated symbolic links.
Windows 98 remembers that the INF file has been installed in the registry. The HKLM\Software\Microsoft\Windows\CurrentVersion\Setup\SetupX\INF\OEM Name key has a value C:\W98\INF\OTHER\WDM BookWDM1.INF set to "WDM BookWDM1.INF". This entry is not deleted if you remove the Wdm1 device.
The InfEdit program in the Windows 98 DDK can be used to edit simple INF files. InfEdit does not display its main menu in Windows 2000 and is, therefore, unusable there.
As Figure 11.1 shows, InfEdit displays the section names as folders in a tree in its left-hand pane. The entries in each section are shown in the right-hand pane. InfEdit lists all the possible section names and entries. Or, at least, all the ones that were possible when the program was written.
The InfEdit program has some distinct drawbacks, so I would advise against its use. I asked it to read the Wdm1Free.INF file. When it wrote the file out again, it had lost the SourceDisksNames subdirectory information. All the strings were replaced with new unhelpful names. It displayed all the Windows 2000 specific sections in a Miscellaneous Sections category. Worse, it did not recognize the strings within these sections and deleted them from the Strings section.
Figure 11.1 InfEdit program
You can create a single INF file that handles all the necessary details for the different Windows 2000 platforms and Windows 98.
The Version section Signature entry value is not used to determine the supported platforms. Instead, each platform has a different install section indicating what to do for that platform. The sections have a common base name, with additional "decoration" characters at the end of the section name to indicate the platform. If there are no section platform variants, the basic section covers all W2000 platforms and W98.
If there are sections with different platform variants, the following rules apply. If there is no decoration, the section covers W98. .NTx86 covers the W2000 x86 platform. .NTalpha covers the W2000 alpha platform. . NT covers all W2000 platforms. You need to supply only the relevant sections.
This install section covers all platforms:
[Abc_Install]
…
These two sections handle Windows 98 and Windows 2000 x86 cases.
[Def_Install] ; Windows 98
…
[Def_Install .NTx86]
; Windows 2000 x86 platform
…
Note that the Windows 98 setup code needs commas for all the optional values, while Windows 2000 can survive without them. For example, in the SourceDisksFiles section, each entry is documented as
filename = diskid[,[subdir][,size]]
The filename entry has two optional values. If you are going to leave these out in an INF file, make sure that you put two commas in, so that it works in Windows 98.
filename=diskid,,
The Wdm1free.INF installation file in Listing 11.1 has separate installation instructions for Windows 2000.
The base section name for the Wdm1 device installation is Wdm1.Install. As there is a section named Wdm1.Install.NTx86 present, this section's instructions are used for the Windows 2000 x86 platform.
Wdm1.Install.NTx86 has just a CopyFiles directive. The Wdm1.Files. Driver.NTx86 section lists just Wdm1.sys, as before. This time, %COPYFLG_NOSKIP% is specified in the fourth field.
This indicates that the user cannot skip this file. There is no point in skipping the driver file in a Wdm1 installation. Note how the COPYFLG_NOSKIP string is used to hide the value 2.
[Wdm1.Files.Driver.NTx86]
Wdm1.sys,,,%COPYFLG_NOSKIP%
[Strings]
COPYFLG_NOSKIP=2 ; Do not allow user to skip file
Windows 2000 Service Registry Entries
A Windows 2000 device driver needs to be installed as a service. This means creating a registry key with the same name as the driver executable in HKLM\System\CurrentControlSet\Services\.
The default installer finds the Wdm1.Install.NTx86.Services section. It first adds the relevant decoration .NTx86 and then adds .Services. This section lists the services that must be added. The service is called Wdm1. The %SPSVCINST_ASSOCSERVICE% value means that the service is associated with a device. The final field says that the Wdm1.Service section has the service registry details.
[Wdm1.Install.NTx86.Services]
AddService = Wdm1, %SPSVCINST_ASSOCSERVICE%, Wdm1.Service
[Wdm1.Service]
DisplayName = %Wdm1.ServiceName%
ServiceType =%SERVICE_KERNEL_DRIVER%
StartType = %SERVICE_DEMAND_START%
ErrorControl = %SERVICE_ERROR_NORMAL%
ServiceBinary = %10%\System32\Drivers\Wdm1.sys
A Windows 2000 service key must have ServiceType, StartType, ErrorControl, and ServiceBinary entries. These eventually appear as Type, Start, ErrorControl, and ImagePath registry entries, respectively. A DisplayName value and registry entry are usually added, as well.
The ServiceType entry must be %SERVICE_KERNEL_DRIVER% (1) for a kernel mode driver. Various other bits can be set such as %SERVICE_FILE_SYSTEM_DRIVER% (2).
The StartType entry specifies when the driver must be started, as shown in the list in Table 11.4. WDM device drivers should specify %SERVICE_DEMAND_START% (3).
Table 11.4 Service StartType values
Value | Constant | Description |
---|---|---|
0 | SERVICE_BOOT_START | Start when W2000 is loaded. |
1 | SERVICE_SYSTEM_START | Start when W2000 is initializing itself. |
2 | SERVICE_AUTO_START | Start when W2000 is up and running. |
3 | SERVICE_DEMAND_START | Start manually or when an associated device is added. |
4 | SERVICE_DISABLED | Never start. |
The ErrorControl entry specifies how the system should respond if the driver cannot load, as shown in the list in Table 11.5. The Wdm1 installation opts for normal error logging.
Table 11.5 Service ErrorControl values
Value | Constant | Description |
---|---|---|
0 | SERVICE_ERROR_IGNORE | Log error but do not display a message to the user. |
1 | SERVICE_ERROR_NORMAL | Log error and display a warning message. |
2 | SERVICE_ERROR_SEVERE | Log error and restart with last known good configuration. |
3 | SERVICE_ERROR_CRITICAL | Log error. Try last known good configuration. If this fails, force a bugcheck. |
The AddService directive may also contain a further field, which specifies a section that is used to install NT event logging registries. I found that this did not work in the Beta 2 version of Windows 2000.
Several other entries may appear in a Services section that let you insert further registry entries. Some of these are used to determine the load order of drivers that are auto-started, as described in the following text.
The Windows New Device Wizard is called into action whenever a new device is found: on power up, when a hot-pluggable device is installed, or when a device is installed from the Control Panel. It scans through all the available INF files trying to find an appropriate driver.
Windows uses the device Hardware ID or device Compatible ID to select which driver to load. The bus driver that found the device provides the Hardware ID and optionally one or more Compatible IDs. Some bus drivers provide fixed IDs. Otherwise, the bus driver must interrogate the device to find its IDs.
The Hardware and Compatible IDs that an INF file supports are listed for each product in a manufacturer's Models section. Each product is defined in a line in this section, as follows.
device_description=install,hardware_id[,compatible_id…]
The device_description is displayed to the user. install is the name of the section with the installation instructions. One hardware_id and zero or more compatible_ids can then be given.
In the Wdm1 INF file, only one Hardware ID is given, *wdmBook\Wdm1. For Unknown devices installed from the Control Panel, only a Hardware ID is needed because the device is always installed.
%Wdm1%=Wdm1.Install, *wdmBook\Wdm1
The rules for selecting a device are quite complicated. By preference, Windows selects a driver for a device whose Hardware IDs match. Otherwise, it selects the driver whose Compatible ID best matches the device's Compatible ID, or prompts the user for an alternative driver INF file.
In Windows 2000, if more than one INF file has identical Hardware IDs or Compatible IDs, the DriverVer directive is used to work out the most recent driver. The INF file with the most recent date (in USAn format) is used. The version number is only used for display purposes. A DriverVer directive in the Version section provides the default for the whole file. A DriverVer directive in an Install section overrides this default. The wustamp tool can be used to insert DriverVer directives into INF files.
Hardware IDs come in almost any form. They can be *DeviceID, where DeviceID is a three-letter company ID followed by a 4 hex digit device ID. Many Windows Hardware IDs use pnp as the company ID (e.g., *pnp0700 is a standard floppy disk controller).
Other Hardware IDs are in a form specific to the enumerator and often start with an enumerator code and a backslash. Here are typical Hardware IDs.
PCI\VEN_1011&DEV_0021 ; DEC 21052 PCI to PCI bridge
USB\VID_045E&PID_000B ; Microsoft USB Keyboard
USB\VID_046A&PID_0001 ; Cherry USB Keyboard
HID\VID_046A&PID_0001 ; Cherry USB Keyboard
Gameport\SideWinder3dPro ; Microsoft Sidewinder 3D Pro
PCMCIA\ACCT0N-EN2212-C817 ; Accton EN2212 Ethernet PCMCIA Adapter
ISAPNP\ICI1995 ; CoreLogic NetViper-100 ISDN Adapter
These Hardware IDs often include the vendor code and a device or product ID. These Hardware IDs can come in versions that are more specific. For example, USB devices can have REV_ revision numbers, MC_ multi-configuration IDs or MI_ multi-interface numbers added to the basic Hardware ID. Complicated rules apply if a USB device has more than one configuration or interface[27]. In a simple case, if you produce a new driver for your version two USB device, use REV_ in the Hardware ID to specify the latest driver. Make sure that the most desirable Hardware ID appears first in the Models list.
[models]
%USBDevice_V2%=V2Install,USB\VID_ABCD&PID_EF01&REV_0002
%USBDevice_V1%=V1Install,USB\VID_ABCD&PID_EF01
Compatible IDs are used if a matching Hardware ID is not found. If a suitable driver is not found for a device's Hardware ID, its Compatible ID is used in the lookup process. A standard driver might be able to control this device. If the Compatible IDs match, the standard driver is used.
Compatible IDs are primarily used by USB devices. A USB device has a vendor ID and product ID that are used to form its Hardware ID. However, a USB device should also have class, subclass, and protocol information for each interface it supports. For example, a USB HID device has an interface class code of 03 (constant USB_DEVICE_CLASS_HUMAN_INTERFACE). This forms a Compatible ID of USB\Class_03.
In Windows 2000, the INPUT.INF file has a line that matches this HID Compatible ID.
%HID.DeviceDesc% = HID_Inst,GENERIC_HID_DEVICE, USB\Class_03&SubClass_01,USB\Class_03
In Windows 98, this case is caught in HIDDEV.INF.
%USB\Class_03%=USBHIDDevice,USB\Class_03
When a new USB HID keyboard (with standard capabilities) is attached to the computer, the default installer might not match the keyboard's Hardware ID, but it should match the Compatible ID. The standard HID keyboard driver is used, as described in the two previous cases. The HID USB keyboard should, therefore, install satisfactorily.
When locating a driver for a HID device where the Hardware ID is not recognized, Compatible IDs are not used. Instead, KEYBOARD. INF uses the HID_DEVICE_SYSTEM_KEYBOARD Hardware ID to locate the default driver.
A new device may require several driver-loading steps before it becomes fully operational.
Consider what happens when a Cherry HID USB keyboard is inserted for the first time. The USB class drivers should already be up and running. They will detect the new device and interrogate it to obtain its vendor ID and product ID, along with various class values. The following basic device Hardware ID is formed.
USB\VID_046A&PID_0001
In Windows 2000, this Hardware ID is found in INPUT. INF. The installation section prompts the loading of the HID class drivers and the HID USB minidriver.
The HID USB minidriver is loaded for the new device. It generates a new Hardware ID to represent the HID device.
HID\VID_046A&PID_0001
In Windows 2000, this Hardware ID is found in KEYBOARD.INF. The installation section loads the system keyboard driver and the driver that provides the keyboard interface to the HID class drivers.
In Windows 98, the Cherry HID USB keyboard is installed in one step, because the KEYBOARD. INF detects the USB\VID_046A&PID_0001 Hardware ID.
NT style (non-WDM) kernel mode drivers for NT 3.51, NT 4, and Windows 2000[28] can be installed by hand. This means copying the driver executable to the Windows System32\Drivers directory, making the right registry settings and rebooting the system. However, it is far safer to write an installation program to do the job[29].
You need to use the appropriate Win32 function calls to copy files and alter the registry. In addition, you must use the Windows 2000 Service Control Manager functions.
The code in install.cpp (on the book's CD-ROM) shows how to install an NT style driver. This is not a complete Win32 program. You must embed it in your own installation application.
The InstallDriver routine controls the whole installation process. It calls CreateDriver and StartDriver, when appropriate. FindInMultiSz is used to see if the driver name is in a REG_MULTI_SZ value. The code follows the logic laid out in the following sections to install a driver called "AbcDriver".
install.cpp installs two sets of registry values that I have not mentioned before.
• Event log registry entries are added. See Chapter 13 for details.
• The driver has a Parameters subkey. The values in this subkey are used to control some global features of the driver. You might like to write a Control Panel applet or other control application to let users modify these values.
1. Get the Windows System32 directory using GetSystemDirectory. Use CopyFile to copy your driver to the System32\Drivers directory.
2. Create the driver service (or stop the existing driver) — see the following.
3. Make the appropriate driver registry key, e.g., HKLM\SYSTEM\CurrentControlSet\Services\AbcDriver using RegCreateKeyEx.
Set ErrorControl, Start, and Type registry values in the previous key using RegSetValueEx.
If desired, set Group, DependOnGroup, and DependOnService registry values, etc. as described later.
4. If desired, make a Parameters registry key (e.g., HKLM\SYSTEM\CurrentControlSet\Services\AbcDriver\Parameters) and set any appropriate values.
5. Open the event log registry key at HKLM\SYSTEM\CurrentControlSet\Services\EventLog\System.
Read the Sources value from this key.
See if your driver is in this list. If not, add your null-terminated driver name to Sources and write it back to the registry.
6. Create the driver event log registry key at HKLM\SYSTEM\CurrentControlSet\Services\EventLog\System\AbcDriver.
Set the TypesSupported and EventMessageFile values.
7. Start the driver — see the following.
Creating or Stopping a Driver Service
1. Open the Service Control Manager using OpenSCManager.
2. Try to open your driver service using OpenService.
If OpenService exists, see if it is currently running using ControlService SERVICE_CONTROL_INTERROGATE.
If OpenService is running, stop it using ControlService SERVICE_CONTROL_STOP.
Give the driver 10 seconds to stop, checking every second with ControlService SERVICE_CONTROL_INTERROGATE.
Close the driver service handle using CloseServiceHandle. Return.
3. Create the driver service using CreateService.
4. Close the Service Control Manager using CloseServiceHandle.
Starting a Driver
1. Open the Service Control Manager using OpenSCManager.
2. Open your driver service using OpenService.
3. Get the driver run state using ControlService SERVICE_CONTROL_INTERROGATE.
4. If need be, call StartService to start your driver.
Give it 10 seconds to start, checking every second with ControlService SERVICE_CONTROL_INTERROGATE.
5. Close the driver service and Service Control Manager using CloseServiceHandle.
In NT 3.51, NT 4, and Windows 2000 various registry entries affect the load order of drivers. While these entries can be used for WDM device drivers, the Plug and Plug enumeration process usually ensures that drivers are loaded in the right order.
Each driver can be put in a group. Tags determine the driver load order within a group. Each driver can insist that it is loaded after a particular group or driver has loaded. Groups are loaded in a ServiceGroupOrder.
The DependOnGroup entry in a driver's service registry key is a REG_MULTI_SZ stating which groups must be loaded before this driver. Similarly, DependOnService is a REG_MULTI_SZ listing the drivers and services that must be loaded before this driver.
The Group entry is a REG_SZ giving the driver's group name. Tag is a REG_DWORD giving the driver tag number. The HKLM\SYSTEM\CurrentControlSet\Control\GroupOrderList registry key has a REG_BINARY entry for each group. The first byte of this binary data is the tag count. The following DWORDs contain the tags of the drivers in the order that they should be loaded. Three NULL bytes pad out the binary data.
The HKLM\SYSTEM\CurrentControlSet\Control\ServiceOrderList registry key has a REG_ MULTI_SZ entry called List. The strings in the list are the driver group names in the order that they should be loaded.
A driver's Start setting, shown in Table 11.4, will override all these driver loading rules.
In NT 4 and NT 3.51, the Control Panel Devices applet can be used to start and stop drivers and set the Startup option. Figure 11.2 shows the Devices applet in action.
Figure 11.2 NT 4 Control Panel Devices
In Windows 2000, the recommended tool for most device management tasks is the Computer Management console. This is found in the Start menu Programs+Administrative tools+Computer Management option. The Device Manager is also available from the Control Panel System applet.
Figure 11.3 shows the Computer Management console running on my computer with the Device Manager Devices selected. You can get this same view from the Control Panel System applet; select the Hardware tab and click on Device Manager.
You can see Wdm1 and Wdm2 devices in the "Other Devices" (Unknown) category, along with the DebugPrint driver. Right-click on the driver name to uninstall it or update the driver from its properties box.
You can see any non-WDM drivers by right-clicking on Devices. Select View in the pop up menu. Check the "Show hidden devices" option. The devices display now includes a "Non-Plug and Play Drivers" category. You can start and stop the NT style drivers and change their startup options.
Figure 11.3 W2000 Computer Management console
The book software includes a small tool called Servicer in the Servicer subdirectory. Run this to see the display shown in Figure 11.4. "Parallel" has been typed into the Driver name box and the Lookup button has been pressed. The Parallel driver is found to be running. You can stop and start the Parallel driver using the appropriate buttons.
Servicer could be enhanced. With a bit of ingenuity, it could list all the available drivers in a list box. It could also be enhanced to change the Startup attributes in the same way as the NT 4 Devices Control Panel applet.
Do not try to stop a WDM driver that has a device attached. It will not work.
Figure 11.4 Servicer program
The familiar Device Manager display is used to manage WDM devices in Windows 98. Start the System applet in the Control Panel. The Device Manager tab shows a display very similar to the right-hand pane of the Computer Management Console Devices windows shown in Figure 11.3.
The Control Panel Add New Hardware wizard is used to add some types of new device. The Add New Hardware wizard is run automatically if a new device is detected by a bus driver.
A quick and dirty way of installing a series of registry settings is to use .REG files. Use RegEdit to export a branch of the registry. Run RegEdit on another computer to import the . REG file to create the same registry structure, entries, and values.
Two other Windows 2000 command line tools also do the same job. regdmp writes registry information to a REG file, regini imports a REG file.
REG files help when you only have to do one or two driver installations and you do not want to write a complete installation program. Another possible use is in product support; ask a customer to use RegEdit to export a portion of their registry to send to you.
It is often possible to use an NT style driver in Windows 98. The NT style PHDIo driver described in Chapters 1.5-19 can be run in Windows 98 as well as the NT and Windows 2000 x86 platforms. PHDIo is a generic driver for handling simple devices and it is useful to be able to install it in Windows 98.
The DebugPrint driver described in Chapter 14 is currently installed as a WDM Plug and Play device. However, it would be perfectly sensible for it to be installed as an NT style driver. Quite a bit of its infrastructure would change. It would not have an AddDevice routine and it would not receive Plug and Play IRPs. This would actually make the driver quite a bit easier!
Contrary to my expectations, an NT style driver can use various kernel calls that are not supposed to be available to it (e.g., HalTranslateBusAddress for bus address translation, HalGetInterruptVector to get an interrupt vector, and IoReportResourceUsage to grab resource assignments). With these routines, an NT style driver such as PHDIo can use I/O ports and interrupts. You will have to experiment to see if DMA resources can also be used.
An NT style driver can use other techniques to talk to hardware. It could attach itself to another driver device and interact with hardware that way. Alternatively, I assume that it could use the available VxDs to interact with hardware.
You have to install an NT style driver "by hand" in Windows 98. In other words, the Add New Hardware wizard will not process an INF file nicely for you. Instead, a relatively straightforward program will be needed to copy the driver executable, set some registry values, and reboot the computer. For example, if you were trying to install a driver called AbcXyz, you would use the following steps.
• Copy the driver executable, AbcXyz.sys, to the Windows System32\Drivers directory.
• Make a new registry key HKLM\System\CurrentControl Set\Services\AbcXyz\, replacing AbcXyz with your driver name.
• In this registry key, set the values according to Table 11.6.
• Restart Windows 98.
Table 11.6 Windows 98 registry values
Value | Type | Contents |
---|---|---|
Type | DWORD | 1 |
Start | DWORD | 2 as per Table 11.4 |
DisplayName | REG_SZ | AbcXyz" driver name |
ErrorControl | DWORD | 1 as per Table 11.5 |
This chapter first looked at how to install WDM device drivers using INF files. It then described the installation process for NT style kernel mode drivers. The device driver management tools were discussed. The Servicer program can start and stop NT style drivers.
Our tour of the core device driver functionality continues in the next chapter by looking at how to interact with the Windows Management Instrumentation system.
Listing 11.2 NTMnstall.Cpp
// install.Inf – NT driver install program
// Copyright © 1998 Chris Cant, PHD Computer Consultants Ltd
// This is not a complete program
void InstallDriver(CString DriverName, CString DriverFromPath) {
//////////////////////////////////////////////////////////////////
// Get System32 directory
_TCHAR System32Directory[_MAX_PATH];
if (0==GetSystemDirectory(System32Directory,_MAX_PATH)) {
AfxMessageBox("Could not find Windows system directory");
return;
}
///////////////////////////////////////////////////////////////////
// Copy driver.sys file across
CString DriverFullPath = System32Directory+"\\Drivers\\"+DriverName+".sys";
if (0==CopyFile( DriverFromPath, DriverFullPath, FALSE)) // Overwrite OK
{
CString Msg;
Msg.Format("Could not copy %s to %s", DriverFullPath, Drivers);
AfxMessageBox(Msg);
return;
}
///////////////////////////////////////////////////////////////////
// Create driver (or stop existing driver)
if (!CreateDriver(DriverName, DriverFullPath)) return;
///////////////////////////////////////////////////////////////////
// Create/Open driver registry key and set its values
//Overwrite registry values written in driver creation
HKEY mru;
DWORD disposition;
if (RegCreateKeyEx(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Services\\"+DriverName,
0, "", 0, KEY_ALL_ACCESS, NULL, &mru, &disposition) != ERROR_SUCCESS) {
AfxMessageBox("Could not create driver registry key");
return;
}
// Delete ImagePath
RegDeleteValue(mru,"ImagePath");
// Delete DisplayName
RegDeleteValue(mru, "DisplayName");
// ErrorControl
DWORD dwRegValue = SERVICE_ERROR_NORMAL;
if (RegSetValueEx(mru, "ErrorControl", 0, REG_DWORD, (CONST BYTE*)&dwRegValue, sizeof(DWORD)) != ERROR_SUCCESS) {
AfxMessageBox("Could not create driver registry value ErrorControl");
return;
}
// Start
dwRegValue = SERVICE_AUTO_START;
if (RegSetValueEx(mru, "Start" , 0, REG_DWORD, (CONST BYTE*)&dwRegValue, sizeof(DWORD)) != ERROR_SUCCESS) {
AfxMessageBox("Could not create driver registry value Start");
return;
}
// Type
dwRegValue = SERVICE_KERNEL_DRIVER;
if (RegSetValueEx(mru, "Type", 0, REG_DWORD, (CONST BYTE*)&dwRegValue, sizeof(DWORD)) != ERROR_SUCCESS) {
AfxMessageBox("Could not create driver registry value Type");
return;
}
// DependOnGroup
_TCHAR DependOnGroup[] = "Parallel arbitrator\0\0";
if (RegSetValueEx(mru, "DependOnGroup", 0, REG_MULTI_SZ,
(CONST BYTE*)&DependOnGroup, strlen(DependOnGroup)+2) != ERROR_SUCCESS) {
AfxMessageBox("Could not create driver registry value DependOnGroup");
return;
}
// DependOnService
_TCHAR DependOnService[] = "parport\0\0";
if (RegSetValueEx(mru, "DependOnService", 0, REG_MULTI_SZ,
(CONST BYTE*) &DependOnService, strlen(DependOnService)+2) != ERROR_SUCCESS) {
AfxMessageBox("Could not create driver registry value DependOnService");
return;
}
RegCloseKey(mru);
//////////////////////////////////////////////////////////////////
// Create/Open driver\Parameters registry key and set its values
if (RegCreateKeyEx(HKEY_LOCAL_MACHIME, "SYSTEM\\CurrentControlSet\\Services\\'+DriverName+'\\Parameters',
0, "", 0, KEY_ALL_ACCESS, NULL, &mru, &disposition) != ERROR_SUCCESS) {
AfxMessageBox("Could not create driver\\Parameters registry key");
return;
}
// EventLogLevel
dwRegValue = 1;
if (RegSetValueEx(mru, "EventLogLevel", 0, REG_DWORD, (CONST BYTE*)&dwRegValue, sizeof(DWORD)) != ERROR_SUCCESS) {
AfxMessageBox("Could not create driver\\Parameters registry value EventLogLevel");
return;
}
// Default or No Name
CString DefaultName = DriverName;
int DeviceNameLen = DefaultName.GetLength()+1;
LPTSTR lpDefaultName = DefaultName.GetBuffer(DeviceNameLen);
if (RegSetValueEx(mru, "", 0, REG_SZ, (CONST BYTE*)lpDefaultName, DeviceNameLen) != ERROR_SUCCESS) {
AfxMessageBox("Could not create driver\\Parameters default registry value");
return;
}
DefaultName.ReleaseBuffer(0);
RegCloseKey(mru);
//////////////////////////////////////////////////////////////////
// Open EventLog\System registry key and set its values
if (RegCreateKeyEx(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Services\\EventLog\\System",
0, "", 0, KEY_ALL_ACCESS, NULL, &mru, &disposition) != ERROR SUCCESS) {
AfxMessageBox("Could not open EventLog\\System registry key");
return;
}
// get Sources size
DWORD DataSize = 0;
DWORD Type;
if (RegQueryValueEx(mru, "Sources", NULL, &Type, NULL, &DataSize) ! = ERROR_SUCCESS) {
AfxMessageBox("Could not read size of EventLog\\System registry value Sources");
return;
}
// read Sources
int DriverNameLen = strlen(DriverName);
DataSize += DriverNameLen+1;
LPTSTR Sources = new _TCHAR[DataSize];
if (RegQueryValueEx(mru, "Sources", NULL, &Type, (LPBYTE)Sources, &DataSize) != ERROR_SUCCESS) {
AfxMessageBox("Could not read EventLog\\System registry value Sources");
return;
}
// If driver not there, add and write
if (FindInMultiSz(Sources, DataSize, DriverName )==-1) {
strcpy(Sources+DataSize-1,DriverName);
DataSize += DriverNameLen;
*(Sources+DataSize) = '\0';
if (RegSetValueEx(mru, "Sources", 0, REG_MULTI_SZ, (CONST BYTE*)Sources.DataSize) != ERROR_SUCCESS) {
AfxMessageBox("Could not create driver registry value Sources");
return;
}
}
///////////////////////////////////////////////////////////////////
// Create/Open EventLog\System\driver registry key and set its values
if (RegCreateKeyEx(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Services\\EventLog\\System\\"+DriverName,
0, "", 0, KEY_ALL_ACCESS, NULL, &mru, &disposition) != ERROR_SUCCESS) {
AfxMessageBox("Could not create EventLog\\System\\driver registry key");
return;
}
// TypesSupported dwRegValue = 7;
if (RegSetValueEx(mru,"TypesSupported", 0, REG_DWORD, (CONST BYTE*)&dwRegValue, sizeof(DWORD)) != ERROR_SUCCESS) {
AfxMessageBox("Could not create EventLog\\System\\driver registry value TypesSupported");
return;
}
// EventMessageFile
LPTSTR EventMessageFile = "%SystemRoot%\\System32\\IoLogMsg.dll;%SystemRoot%\\System32\\Drivers\\"+DriverName+".sys";
if (RegSetValueEx(mru, "EventMessageFile", 0, REG_EXPAND_SZ,
(CONST BYTE*)EventMessageFile, strlen(EventMessageFile)+1) != ERROR_SUCCESS) {
AfxMessageBox("Could not create EventLog\\System\\driver registry value EventMessageFile");
return;
}
RegCloseKey(mru);
////////////////////////////////////////////////////////////////////
// Start driver service
if (!StartDriver(DriverName)) return;
}
////////////////////////////////////////////////////////////////
BOOL CreateDriver( CString DriverName, CString FullDriver) {
///////////////////////////////////////////////////////////////
// Open service control manager
SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
if (hSCManager=NULL) {
AfxMessageBox("Could not open Service Control Manager");
return FALSE;
}
//////////////////////////////////////////////////////////////////////
// If driver is running, stop it
SC_HANDLE hDriver = OpenService(hSCManager, DriverName, SERVICE_ALL_ACCESS);
if (hDriver!=NULL) {
SERVICE_STATUS ss;
if (ControlService(hDriver, SERVICE_CONTROL_INTERROGATE, &ss)) {
if (ss.dwCurrentState != SERVICE_STOPPED) {
if (!ControlService(hDriver, SERVICE_CONTROL_STOP, &ss)) {
AfxMessageBox("Could not stop driver");
CloseServiceHandle(hSCManager);
CloseServiceHandle(hDriver);
return FALSE;
}
// Give it 10 seconds to stop
BOOL Stopped = FALSE;
for (int seconds=0; seconds<10; seconds++) {
Sleep(1000);
if (ControlService(hDriver, SERVICE_CONTROL_INTERROGATE, &ss) && ss.dwCurrentState==SERVICE_STOPPED) {
Stopped = TRUE;
break;
}
}
if (!Stopped) {
AfxMessageBox("Could not stop driver");
CloseServiceHandle(hSCManager);
CloseServiceHandle(hDriver);
return FALSE;
}
}
CloseServiceHandle(hDriver);
}
return TRUE;
}
//////////////////////////////////////////////////////////////////////
// Create driver service
hDriver = CreateService(hSCManager, DriverName.DriverName, SERVICE_ALL_ACCESS, SERVICE_KERNEL_DRIVER, SERVICE_AUTO_START,
SERVICE_ERROR_NORMAL, Drivers, NULL, NULL,"parport\0\0", NULL, NULL);
if (hDriver==NULL) {
AfxMessageBox("Could not install driver with Service Control Manager");
CloseServiceHandle(hSCManager);
return FALSE;
}
//////////////////////////////////////////////////////////////////////
CloseServiceHandle(hSCManager);
return TRUE;
}
///////////////////////////////////////////////////////////////////////
BOOL StartDriver(CString DriverName) {
//////////////////////////////////////////////////////////////////////
// Open service control manager
SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
if (hSCManager==NULL) {
AfxMessageBox("Could not open Service Control Manager");
return FALSE;
}
//////////////////////////////////////////////////////////////////////
// Driver isn't there
SC_HANDLE hDriver = OpenService(hSCManager,DriverName,SERVICE_ALL_ACCESS);
if (hDriver==NULL) {
AfxMessageBox("Could not open driver service");
CloseServiceHandle(hSCManager);
return FALSE;
}
SERVICE_STATUS ss;
if (!ControlService(hDriver, SERVICE_CONTROL_INTERROGATE, &ss) ss.dwCurrentState!=SERVICE_STOPPED) {
AfxMessageBox("Could not interrogate driver service");
CloseServiceHandle(hSCManager);
CloseServiceHandle(hDriver);
return FALSE;
}
if (!StartService(hDriver, 0, NULL)) {
AfxMessageBox("Could not start driver");
CloseServiceHandle(hSCManager);
CloseServiceHandle(hDriver);
return FALSE;
}
// Give it 10 seconds to start
BOOL Started = FALSE;
for (int seconds=0; seconds<10; seconds++) {
Sleep(1000);
if (ControlService(hDriver, SERVICE_CONTROL_INTERROGATE, &ss) & ss.dwCurrentState==SERVICE_RUNNING) {
Started = TRUE;
break;
}
}
if (!Started) {
AfxMessageBox("Could not start driver");
CloseServiceHandle(hSCManager);
CloseServiceHandle(hDriver);
return FALSE;
}
CloseServiceHandle(hDriver);
CloseServiceHandle(hSCManager);
return TRUE;
}
///////////////////////////////////////////////////////////////////////
//Try to find Match in MultiSz, including Match's terminating \0
int FindInMultiSz(LPTSTR MultiSz, int MultiSzLen, LPTSTR Match) {
int MatchLen = strlen(Match);
_TCHAR FirstChar = *Match;
for (int i=0; i<MultiSzLen-MatchLen; i++) {
if (*MultiSz++ == FirstChar) {
BOOL Found = TRUE;
LPTSTR Try = MultiSz;
for (int j=1; j<=MatchLen; j++)
if (*Try++ != Match[j]) {
Found = FALSE;
break;
}
if (Found) return i;
}
}
return –1;
}
///////////////////////////////////////////////////////////////////////
Note: A revised version of NTNinstall.cpp is available on the book's web site, www.phdcc.com/wdmbook. This updated version fixes a problem to make it work in W2000.
See the article on the Microsoft web site entitled "USB Plug and Play IDs and Selecting Device Drivers to Load", http://www.microsoft.com/hwdev/busbios/usbpnp.htm.
NT style drivers may well work in Windows 98, as well. See the next section for details.
NT 4 driver writers can write an OEMSETUP.INF or TXTSETUP.OEM script to install their drivers. See the NT 4 DDK for details.