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