PCI Explorer - A PCI Bus Browser/Editor Utility

Many times when debugging both PCI hardware and software it is very useful to easily be able to browse the available PCI devices in the system without having to use WinDbg or a similar kernel debugger. The PCI Explorer application described in this article enables you to graphically view all the PCI devices and the buses they reside on accordingly to the actual hierarchy of the various devices on the buses. The PCI Explorer application also enables you to easily edit memory,  I/O registers and Configuration Space Registers assigned to any PCI device in your system.

The PCI Explorer application (32-bit) can be downloaded here (NT4, W2K, XP, 2003). PCI Explorer is currently not supported on Windows 9x/ME.

By John Gulbrandsen
John.Gulbrandsen@SummitSoftConsulting.com

Introduction

The PCI Explorer is 32-bit C++ Windows application developed in Visual Studio 2003 with MFC. Figure 1 below shows a screen shot of the main screen. The top pane hierarchically displays all PCI Devices found on all PCI buses in the system. PCI Functions that reside in multi-function devices are grouped under multi-function device nodes. The bottom pane shows the values of the various registers in the PCI Configuration Space. The bottom pane also decodes the meaning of the Configuration Space registers. By double-clicking on a row in the Configuration Space Pane the value of the clicked register can be edited. Changes are written back to the PCI Function when the enter key is pressed.

Figure 1. The main screen of the PCI Explorer application.

The bottom pane displays a 'BAR' tab for each of the implemented Base Address Registers (BARs) of the PCI Function selected in the upper pane. Figure 1 above shows that the Host/PCI Bridge has a single BAR implemented which, when clicked, will allow editing of the memory or I/O locations assigned to any device on the downstream PCI buses. Of course, normally you would only edit memory or I/O locations assigned to your own PCI device because editing memory of unknown devices may very well lock these devices up.

PCI Explorer knows to decode information of the three types of PCI Configuration Space Headers defined in the PCI specification version 2.2, namely non-bridge devices, PCI/PCI bridges and Cardbus bridges. The Vendor and Device ID registers are decoded into string form by using a stand-alone database text file. You can download the latest PCI device file here. Simply overwrite the existing pcidevs.txt file in the directory you installed PCI Explorer in and restart PCI Explorer to pick up new Device and Vendor IDs.

Both the "Configuration Space" and 'BAR' panes in the lower view allows you to view the Register values in either hexadecimal or decimal format by selecting the correct format in the right-click context menu. The BAR panes context menu also allows you to view the displayed data in 8, 16 or 32 bit widths.

The PCI Explorer application uses a kernel-mode device driver to retrieve the displayed information directly from the Host/PCI bridge. By directly programming the Host/PCI bridge the bridge is made to generate PCI Configuration Cycles on its downstream buses which allows us to read and write any location in any PCI device's Configuration Address Space.

The size of a BAR space is retrieved from the Operating System on Windows 2000, XP and newer platforms. Older platforms such as NT 4.0 requires that a full BAR size discovery is made in order to find the size of the BARs. Since a BAR discovery potentially disrupts the PCI function being interrogated, the BAR discovery is only done when the 'BAR' panes are clicked and if the BAR sizes have not already been retrieved from the Operating System. A warning will also be given before a PCI BAR is probed.

Implementation

The PCI Explorer application is implemented in Visual C++ using the Microsoft Foundation Class Library (MFC). It was decided for several reasons that C++ was a better language to implement the application in than C#. The first reason was that MFC provides a strong framework for writing desktop applications that greatly simplifies implementing an application like the PCI Explorer (i.e. the document-view architecture). Using Windows forms and C# would have required that this framework would have had to been emulated from scratch. The second reason that C++ was chosen was that the DeviceIoControl calls to the kernel-mode device driver is much simpler to do in a native Win32 application than in a managed C# application. A third reason was also that a C# application requires that the .NET runtime be installed on the target machine, a proposition probably not too appealing to the average Device Driver Developer (which likely thinks MFC is too bloated anyways).

The upper and lower views in the PCI Explorer main window are implemented using the MFC CTreeView and CListView classes. These classes makes it straightforward to implement a tree view and to display lists of data. In fact, we have inherited specialized classes from the CTreeView and CListView classes in order to encapsulate more intelligence in these classes. These inherited classes are called CPciBusView and CPciFunctionView respectively. For instance, when a PCI Function is selected in the upper view the CPciBusView class will automatically send a notification to the lower CPciFunctionView which knows how to display the information related to the selected PCI Function.

Information related to a PCI Function is contained in a class called CPciFunction. The CPciFunction class itself knows how to recursively enumerate the PCI buses on the system. The root CPciFunction object is created by passing a handle to the kernel-mode device driver to the CPciFunction constructor. This tells the CPciFunction object that it is the root Host/PCI bus bridge so it enumerates all PCI Functions on its secondary bus by making DeviceIoControl calls into the device driver. If the found PCI Functions are bridge devices the corresponding bridge CPciFunction objects will themselves recursively search their secondary buses. This process goes on until an in-memory hierarchical representation of all PCI Functions has been created. By later enumerating all in-memory CPciFunction objects we can easily add nodes to the CPciBusView Tree.

The CPciFunction object gets information about the PCI Functions on the local system from the kernel-mode driver 'pciexdrv.sys' (PCI Explorer Driver). The device driver bypasses the operating system and directly programs the Host/PCI Bridge to generate PCI Configuration Space Read cycles. This allows us to read and write any location in any device's Configuration Space. An alternative approach could have been to use the HalGetBbusData and HalSetBusData HAL functions which most likely uses the exact same method to get the information from the Host/PCI Bridge. The current approach was chosen because it makes it easier to add support for Windows 9x/Me (i.e. to create a VxD which supports similar IOCTL calls as our current driver). The current driver is an NT-style driver that is automatically started with the system.

The CPciFunction objects will, after having read in their information from the PCI Configuration Space, parse the text file pcidevs.txt that resides in the same directory as the application executable PciExplorer.exe. This text file contains clear-text information for the Device and Vendor IDs used by the PCI Headers read in for the PCI Functions. The translated information is used in the upper tree view as well as in the lower Configuration Space pane. You can add your custom Vendor and Device IDs to the pcidevs.txt file if you so wish. Please see the instructions on how to submit your changes in the comment at the beginning of the pcidevs.txt file. You can download the latest pcidevs.txt file here.

Each PCI Function node that is added to the upper CPciBusView has its CPciFunction object hanging off of it (i.e. the lParam of the tree item points to the CPciFunction that contains information about the PCI Function displayed). This is an important architectural detail because it allows the CPciBusView view to itself tell the lower CPciFunctionView which PCI Function to display. The upper CPciBusView does this by broadcasting the CPciFunction object pointers to all views in the system via the Document::CUpdateAllViews function when a new treeview node is selected. The end result is that the lower view will get notified when to display new data and what data to display whenever a new PCI function is selected in the upper view.

When the information in a selected CPciFunction is displayed in the lower view the PCI Configuration Space Header of the selected PCI Function is inspected to find out if the PCI Function implements any Base Address Registers (BARs). BARs that are non-zero are assumed to be implemented. For each implemented BAR the lower CPciFunctionView class will create a new tab called "BAR 0", "BAR 1" etc. Up to 6 BARs tabs can be added to the lower pane (the maximum number of BARs are 6 for a PCI Function).

The tabs in the lower view are implemented by a class inherited from the MFC CTabCtrl class. The inherited class, CCustomTabCtrl, overrides the drawing of the tabs to give them the look of more modern GUIs. The CCustomTabPane also allows the objects that implement the tab panes to be treated in a polymorphic way by dealing with the base class that the tab pane classes inherit from. There are two tab pane classes, CConfigSpacePane and CBarPane. Both of these inherit from the CAbstractTabPane class which defines the interface that a Pane object must support to be able to plug in to the lower tabbed view. These mandatory Pane object functions include functions to show and hide the pane when the tab control is selected/deselected, to show information related to a CPciFunction, to resize the pane properly etc. By using a standardized interface other panes can easily be added to the lower view should this be needed.

Since the BAR panes potentially need to display millions of items (each BAR range can be many MB in size) a normal list control could not be used simply because it would be too slow. Therefore the PCI Explorer application uses a virtual list view. A virtual list view calls its owner back for each item that it needs to display. In our case, we tell the list view how many items it needs to display for a BAR and the list view will call the application back for each item currently displayed. Because only a few hundred items are ever displayed at any time the refresh of the BAR panes is very fast even when scrolling through all the data in the BAR pane.

To give the application a more modern look than provided by the default MFC menus we are using a custom, ownerdrawn menu instead of the default menu. The replacement menu has the newer white look instead of the older gray look. The replacement menu also supports inline images similar to those used by Microsoft Office.

Because the PCI Explorer application needs the MFC and Visual Studio 7 runtime DLLs we decided to create an Installshield installation package. The Installshield installer also installs and starts the 'pciexdrv.sys' device driver by calling the Service Control Manager. The 'pciexdrv.sys' driver is configured to start automatically as your system starts so once installed, PCI Explorer will always be able to retrieve PCI bus information on your system. Note that due to an issue with the Service Control Manager you may have to reboot your system if you want to reinstall the PCI Explorer application after having uninstalled it. If this is not done the driver may not be installed correctly and you'll get an error message from the PCI Explorer application at startup.

Source Code

The source code for PCI Explorer can be downloaded here. The zip file contains the kernel-mode driver ("pciexdrv"), a test application for the kernel-mode driver ("PciExDrvTestApp"), the PCI Explorer source code ("PciExplorer") as well as the Installshield project ("Installer"). Build the project in the order listed above. You most likely have to adjust various include and linker paths depending on where you have your DDK directory installed on your local machine.

Further Reading

1) The PCI Specification version 2.2 describes the information the PCI Explorer application displays. The PCI specification can be be ordered from http://www.pcisig.com/specifications.

2) Also pick up a copy of "PCI System Architecture" by Tom Shanley/Don Anderson. ISBN 0-201-30974-2 (4th edition).

3) For a quicker introduction to the PCI bus "PCI Bus Demystified" by Doug Abbot is strongly recommended. ISBN 1-878707-54-X.

About the Author

John Gulbrandsen is the founder and president of Summit Soft Consulting. John has a formal background in Microprocessor-, digital- and analog- electronics design as well as in embedded and Windows systems development. John has programmed Windows since 1992 (Windows 3.0). He is as comfortable with programming Windows applications and web systems in C++, C# and VB as he is writing and debugging Windows kernel mode device drivers in  SoftIce.  

To contact John drop him an email: John.Gulbrandsen@SummitSoftConsulting.com

About Summit Soft Consulting

Summit Soft Consulting is a Southern California-based consulting firm specializing in Microsoft's operating systems and core technologies. Our specialty is Windows Systems Development including kernel mode and NT internals programming.

To visit Summit Soft Consulting on the web: http://www.summitsoftconsulting.com