At work recently, I had a new PCI device that I needed to experiment with. I was dreading writing a Linux kernel driver to talk to it. It turns out, Linux makes it possible to read and write to a PCI device's memory space without a driver! Woohoo!
Linux provides a sysfs interface to PCI devices. From that
interface, the memory space can be
mmaped and then read and written. No
As a quick example, we can use
lspci to get information about a particular
$ vendor="10ee" # Use your device ID $ device="7014" # Use your vendor ID $ lspci -d $vendor:$device -nvv 04:00.0 1180: 10ee:7014 ... Region 0: Memory at f7300000 (32-bit, non-prefetchable) [size=128K]
Then we can look at the sysfs interface, at
/sys/bus/pci/devices/. The first
bit of data in the output of
lspci gives the location of the device on the
bus, that we can use when traversing the sysfs interface.
$ ls -alF /sys/bus/pci/devices/0000\:04\:00.0/ total 0 drwxr-xr-x 3 root root 0 Jul 1 12:42 ./ drwxr-xr-x 8 root root 0 Jul 1 12:42 ../ -rw-r--r-- 1 root root 4096 Jul 9 12:48 broken_parity_status -r--r--r-- 1 root root 4096 Jul 1 12:42 class -rw-r--r-- 1 root root 4096 Jul 9 12:44 config -r--r--r-- 1 root root 4096 Jul 1 12:42 device ... -r--r--r-- 1 root root 4096 Jul 1 12:43 resource -rw------- 1 root root 131072 Jul 1 12:43 resource0 ... -r--r--r-- 1 root root 4096 Jul 1 12:42 vendor
This interface has some useful files like
device that confirm
that we have the right device. These are also useful for programatically
finding the correct device, rather than using
$ cat /sys/bus/pci/devices/0000\:04\:00.0/vendor 0x10ee $ cat /sys/bus/pci/devices/0000\:04\:00.0/device 0x7014
Looking back at the
lspci output, we can also find memory resources and
addresses. These are represented as
resourceN in the sysfs
interface. That's what we use to get access to the PCI memory space.
resource0 file (which can be some number other than 0 depending on
int fd = open("/sys/bus/pci/devices/0000:04:00.0/resource0", O_RDWR | O_SYNC);
Then use the memory address and size from the
lspci output to
void* base_address = (void*)0xf7300000; size_t size = 128 * 1024; // 128K void* void_memory = mmap(base_address, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); uint16_t* memory = (uint16_t*)void_memory;
memory provides direct access to read and write the PCI memory space.
We can hack away!
// Read the value of the first register uint16_t first_register = memory; // Write a value to the third register memory = 0x0007;
Now, this isn't the perfect scenario. For one, we need to be
root to access
this memory space. For two, there's no sign of interrupt handling anywhere.
But for basic poking around on a new device, it works pretty slick. No kernel module development required.