HOWTO: Create a PCI Device Driver for Windows NT

ID: Q152044


The information in this article applies to:


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:

  1. 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.


  2. 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.


  3. 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.


  4. 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.


  5. 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.


  6. 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