HOWTO: Create a PCI Device Driver for Windows NT
ID: Q152044
|
The information in this article applies to:
-
Microsoft Win32 Device Driver Kit (DDK) for Windows NT, versions 3.5, 3.51, 4.0
SUMMARY
This article describes how to create a PCI Device driver for Windows NT. A
device driver must perform many functions, such as creating a device
object, etc. In addition, there are many more functions that a driver must
perform during driver initialization to support a PCI device. Note that
some of the functions mentioned below are also used in non-PCI drivers.
MORE INFORMATION
Following are the steps necessary to accomplish this task:
- Find the PCI device with HalGetBusData:
Scan all the buses and slots to find the location of your PCI device. In
the buffer returned by HalGetBusData, look for a match to your device's
PCI VendorId and DeviceId.
If your device driver requires additional information from the PCI
configuration space, use HalGetBusData or HalGetBusDataByOffset. Fields
in the PCI configuration space can be set with HalSetBusData or
HalSetBusDataByOffset.
IMPORTANT: Do not set the base address registers (BARs) or the interrupt
information in the PCI configuration space. Also, do not expect the BARs
original settings to be those that will be used for your device. Since
PCI is dynamically configurable, the operating system reserves the right
to move the BARs as it sees fit.
Find the PCI device with 3 FOR loops: one for the bus, one for the
device number, and one for the function number. Note that the structure
PCI_SLOT_NUMBER contains the device number and function number
information.
- Claim the device resources with HalAssignSlotResources:
Once the PCI device is located, pass the bus and slot information to
HalAssignSlotResources. This API ensures that there are no conflicts
with the resources specified in the configuration space. When the call
to HalAssignSlotResources is made, the HAL might move the device's
memory or port ranges, so do not use the values directly from the
configuration space. Instead, use the values returned in the resource
list and translate them as indicated in steps 4 and 5 below. The HAL
will not move resources for a device spontaneously, it moves resources
only as a result of the HalAssignSlotResources call.
Sometimes the call to HalAssignSlotResources causes problems when the
HAL moves the PCI device resources on top of another PCI device that has
not yet been claimed. You can usually prevent this by adding the
PCILOCK option to the Boot.ini file. For information on the PCILOCK
option, please see the following article in the Microsoft Knowledge
Base:
Q148501
Preventing PCI Resource Conflicts on Intel-Based Computers
If adding the PCILOCK option does not work, the PCI device
resources should be claimed with IoReportResourceUsage.
Using IoReportResourceUsage instead of HalAssignSlotResources:
Occasionally, PCI devices cease functioning when the Base Address
Registers are reprogrammed or the PCI devices only work with specific
resource assignments. On these PCI devices, HalAssignSlotResources
frequently fails. In these cases, use IoReportResourceUsage instead of
HalAssignSlotResources.
To use IoReportResourceUsage, get the current device settings by reading
the PCI device's configuration space through
HalGetBusData/HalGetBusDataByOffset. Build a resource list based on the
information in the PCI configuration space and claim the resources using
IoReportResourceUsage. It is important to make sure that the resource
list InterfaceType is correctly set to PCIBus. Also, PCI interrupts are
always considered level sensitive and shared, so make sure the proper
flags are set in the resource list.
The interrupt value returned from the configuration space might look out
of range when the driver is running on a checked build. This is because
the checked HAL will exclusive-or (XOR) the true interrupt value with
0x2B. Use the value returned by HalGetBusData/HalGetBusDataByOffset
unmodified because the HAL will know how to interpret this value. Use
this returned value in the resource list for IoReportResourceUsage as
well as for the BusInterruptLevel and BusInterruptVector parameters for
the HalGetInterruptVector call.
- Parse the resource list for the device resources:
HalAssignSlotResources will claim the PCI device resources and return
the claimed resources in a CM_RESOURCE_LIST structure. The driver will
have to parse this resource list to get the interrupt, memory range, and
I/O range information. Note that a single PCI device can have multiple
memory and I/O ranges. If necessary, save this information in the
driver's device extension for later translation. Do not use the raw
resource information to access the I/O or memory spaces.
At this point, if the driver were to examine the PCI configuration
space, the BARs should be the same as those returned in the
CM_RESOURCE_LIST structure. However, use the information returned in the
resource list.
If the PCI resources were claimed with IoReportResourceUsage, parse the
resource list the driver built and store the information in an
appropriate driver location.
- For each memory range claimed, call HalTranslateBusAddress and
MmMapIoSpace:
HalTranslateBusAddress translates a bus-specific address into the
corresponding system-logical address. After the call to this API, check
the fourth parameter. On entry to the API, zero indicates memory space.
On exit from the API, if the value is still zero, as is the normal case
for memory translation, the driver must also call MmMapIoSpace.
Save the translated memory range information in a driver-accessible area
like the device extension. Use the translated range to access the memory
space.
- For each I/O range claimed, call HalTranslateBusAddress, and if needed,
MmMapIoSpace:
Mapping is similar to that for memory space. However, on entry to
HalTranslateBusAddress, the fourth parameter is one, which indicates I/O
space. On exit, the API may have changed this parameter to zero, in
which case the driver must also call MmMapIoSpace.
Save the translated I/O range information in a driver-accessible area
like the device extension. Use the translated range to access the I/O
space.
On some RISC-based systems, there is no I/O space: I/O space is mapped
to memory space. In this situation, the fourth parameter will change to
indicate that the I/O space has been mapped to memory space. On an x86-
based system, translating an I/O space will use HalTranslateBusAddress,
but not MmMapIoSpace. On some RISC-based systems, both
HalTranslateBusAddress and MmMapIoSpace might be called. It is important
to check the fourth parameter of HalTranslateBusAddress after the call
to this API.
This is a common problem when porting from x86-based machines to RISC
platforms. Driver writers sometimes assume that if they are translating
I/O ranges, they will never need to call MmMapIoSpace. This is an
incorrect assumption.
- If the device supports interrupts, call HalGetInterruptVector and
IoConnectInterrupt:
Once IoConnectInterrupt has been called, the driver's interrupt service
routine (ISR) might be called if the PCI device is interrupting. For
this reason, it is best to connect the interrupt after translating all
the device registers. If the previous translation has not occurred or
the information is not available to the ISR (if, for example, the
translated memory and I/O ranges are not saved in the device extension),
the ISR will not be able to check the PCI device registers and clear the
interrupt. If the interrupt is not cleared, the system will hang.
REFERENCES
Please see the Windows NT Device Driver Kit, Kernel-mode Drivers, Design
Guide for an overview of Windows NT device drivers. The APIs mentioned
above are explained in detail in the Windows NT Device Driver Kit, Kernel-
mode Drivers, Reference section.
Additional query words:
PCI HAL
Keywords : NTDDKKMode
Version : WINNT:3.5,3.51;
Platform : winnt
Issue type : kbhowto
Last Reviewed: March 3, 1999