I spent quite a lot of time reverse engineering a PCI device made by National Instruments. It was a fun learning experience and a great way to learn how to write device drivers for a PCI device on linux. There are many ways to go about reverse engineering a device, and as John McAfee says, most of reverse engineering is just social engineering. Which means that you have to get in to the mindset of a device driver developer.
Part of getting in to that mindset is reading manuals written by the same manufacturer (if available) for that device, similar devices in the same family, reading manuals, looking at sample code from similar products made by competitors, and finally, reading as much as you can about the chips and technology in use by the device. Example? If you want to reverse engineer the DMA portion of a device, just start reading about DMA in general.
As for PCI versus PCI Express, there is no concern here because there is virtually no difference between PCI and PCIe devices as far as your device driver development is concerned, so everything you read here will easily transfer to PCIe devices.
To start, you need to have some background info on what PCI is and how a PCI device is detected and used by your operating system. I used Linux and the mainline kernel now has a great tool called mmiotrace that has been merged into the mainline kernel starting with a later version of I think 2.6. So if you have anything starting with 3 you should be good. If not, just google mmiotrace and your kernel version to make sure. This tool also should allow you to avoid needing to purchase an expensive PCI Logic Analyzer, which nowadays can be acquired for less than $5,000 (USD).
Here is a quick list of some good resources to check out:
- Linux Device Drivers (3rd edition), by Jonathan Corbet, Alessandro Rubini, and Greg Kroah-Hartman – open source book which I love and purchased anyway. A bit outdated, and the 4th edition is coming out in late 2017.
- mmiotrace – mmiotrace traces all reads and writes to memory mapped I/O by setting the page where each address resides to “not present” forcing a page fault whenever access occurs to that page. Then it saves the address and value, marks the page present and restarts the memory access.
So, when you turn on your computer, the system BIOS will enumerate all PCI/PCIe devices by sending a bunch of commands and identifying all devices, bridges, and devices behind bridges. (Yes, you can have multiple bridges behind each other.) After the BIOS posts and while the operating system is booting up, the kernel looks at each PCI device and its associated Manufacturer ID and Device ID. You can look up some codes on pcidatabase.com. One particular manufacturer id is Intel, whose manufacturer ID is 0x8086. Does that number sound familiar? Anyway, I thought that was pretty cool… The kernel then takes these two numbers and tries to find a kernel module that listens for those devices and loads the module with that device.
Your kernel module can then access the device configuration space. You can either read this region manually, or you can use helper functions to get more information about your device and what resources it uses/has available to you. These resources fit into 3 categories:
- Memory Regions
In the case of the National Instruments PCI-7813 device, I have 3 types of resources that I want to use, one is reading from an indicator and writing to a control, which appears to be an I/O type of access, reading and writing to/from FIFOs, which appears to be a Memory Region type of access, and the raising of Interrupts from the FPGA which then get asserted by the host computer, which is likely an Interrupt type of resource.
Here is a birds-eye view of what I did:
- Install manufacturers linux device drivers
- Figure out a way to unload and reload those drivers at runtime
- Turn on mmiotrace, load manufacturer device drivers, run each sample program
- Sample Programs
- Most simple program that loads an FPGA vi and does absolutely nothing
- More advanced programs:
- Write to a control and read a value
- Write to a FIFO
- Write to a FIFO and read an indicator which counts how many values were read
- Write to a FIFO and read from a FIFO
- Trigger an interrupt assert by either writing to a FIFO or a control
For each sample program I varied the control being written to, the indicator being read from by adding more to my LabVIEW project repeatedly until I started noticing a pattern.
So, for now take a look at the links (above and below) and please submit any questions in the form of comments below, while you wait for my next article.
I spent quite a lot of time reverse engineering a PCI device made by National Instruments. It was a fun learning experi...