How to Reverse Engineer PCI Drivers

This page collects some notes I made whilst attempting to reverse engineer the Intel Precise Touch (as found in the Microsoft Surface Pro 4). No driver ever came out of this, but I found myself with some rather easy instructions on how to get QEMU set up for 'sniffing' PCI traffic and using UIO_PCI_GENERIC.

To see how to start up talking to a PCI card, you can refer to my perl script (requires a kernel with UIO_PCI_GENERIC support) for hints.

Related Links

Development

To try and work out how the Microsoft Windows driver works, we tie the PCI card directly to a Win10 QEMU VM and record the requests that pass through, from there we stretch our heads and try to work out what means what and where.

Thanks go out to Olivier Fauchon for noting down the setting up of QEMU sparing me from having to do so.

Windows 10

You will need to download:

We also require an EFI BIOS (VFIO seems to stall otherwise):

sudo apt-get install rpm2cpio
wget https://www.kraxel.org/repos/jenkins/edk2/edk2.git-ovmf-x64-0-20160129.b1452.g62c9131.noarch.rpm
rpm2cpio edk2.git-ovmf-x64-0-20160129.b1452.g62c9131.noarch.rpm > payload.cpio
cpio -id < payload.cpio
cp usr/share/edk2.git/ovmf-x64/OVMF{,_VARS}-pure-efi.fd .

Now install Windows 10 with:

sudo apt-get install qemu-system-x86 qemu-utils spice-client
qemu-img create -f qcow2 -o size=40G,lazy_refcounts=on mswin10.qcow2
/usr/src/qemu/x86_64-softmmu/qemu-system-x86_64 \
    -machine accel=kvm \
    -cpu host -smp 2 -m 2G \
    -drive if=pflash,format=raw,readonly,file=OVMF-pure-efi.fd \
    -drive if=pflash,format=raw,file=OVMF_VARS-pure-efi.fd \
    -vga qxl -spice addr=127.0.0.1,port=5901,disable-ticketing \
    -drive file=mswin10.qcow2,if=virtio,snapshot=off \
    -net nic,model=virtio -net user \
    -device usb-ehci,id=ehci -usbdevice tablet \
    -soundhw hda \
    -rtc base=localtime \
    -drive file=Win10_1511_English_x64.iso,media=cdrom \
    -drive file=virtio-win.iso,media=cdrom \
    -boot once=d

You can access the VM with:

spicec -h 127.0.0.1 -p 5901

I recommend you get all the regular disk, video, network and sound drivers working, optionally applying any Windows 10 updates, once done run:

qemu-img convert -c -p -O qcow2 mswin10.qcow2 mswin10.compressed.qcow2
mv mswin10.compressed.qcow2 mswin10.qcow2
qemu-img snapshot -c fresh mswin10.qcow2

Now you have a snapshot point you can start from each time.

Kernel

You will require a kernel with CONFIG_VFIO_PCI and CONFIG_INTEL_IOMMU enabled (Debian and probably other distros already do), plus you will need to add intel_iommu=on to your kernel parameters list; remember to reboot.

QEMU

Although you can install and use Windows 10 in QEMU with Debian's packages, for the VFIO support, you will need a recent QEMU from git:

cd /usr/src
git clone git://git.qemu-project.org/qemu.git
cd qemu
./configure --python=/usr/bin/python2 --enable-trace-backends=stderr --target-list=x86_64-softmmu --enable-spice
make -j$(($(getconf _NPROCESSORS_ONLN)+1))

PCI

Now we need to perform the slightly tricky process of reserving the target PCI card for QEMU, tricky as you will also need to grab any parent PCI devices.

For example on the Microsoft Surface Pro 4, the Intel Precise Touch has the PCI ID 8086:9d3e and hangs off the parent PCI card at 00:16.0:

$ lspci -k
[snipped]
00:16.0 Communication controller [0780]: Intel Corporation Device [8086:9d3a] (rev 21)
        Subsystem: Intel Corporation Device [8086:7270]
        Kernel driver in use: mei_me
        Kernel modules: mei_me
00:16.4 Communication controller [0780]: Intel Corporation Device [8086:9d3e] (rev 21)

To reserve the PCI cards, you have to first free them up from any drivers that may have grabbed them already; here you can see that mei_me is in use, so will need need unloading:

sudo modprobe -r mei_me

Now we can grab the PCI card (and its parent) for use with QEMU with:

sudo modprobe vfio-pci ids=8086:9d3a,8086:9d3e

Running

Now you should have everything you need so you can spin up QEMU and look at the tracing output:

cat /usr/src/qemu/trace-events | sed -n 's/^\(vfio_[^(]*\).*/\1/ p' > events
sudo /usr/src/qemu/x86_64-softmmu/qemu-system-x86_64 \
    -machine accel=kvm \
    -cpu host -smp 2 -m 2G \
    -drive if=pflash,format=raw,readonly,file=OVMF-pure-efi.fd \
    -drive if=pflash,format=raw,file=OVMF_VARS-pure-efi.fd \
    -vga qxl -spice addr=127.0.0.1,port=5901,disable-ticketing \
    -drive file=mswin10.qcow2,if=virtio,snapshot=off \
    -net nic,model=virtio -net user \
    -device usb-ehci,id=ehci -usbdevice tablet \
    -soundhw hda \
    -rtc base=localtime \
    -device vfio-pci,host=00:16.4,x-no-mmap=on \
    -trace events=events 2>&1 | tee trace

N.B. has to run with sudo so QEMU can directly access the PCI card

You will need to go to Device Manager and search for drivers online for your new unknown PCI communications device; Windows 10 will then download the driver and set up the card. If everything works, you should be able to touch the screen now and move the mouse around in the VM.

Remember, if you want to run the userspace tool, you will first need to free up the PCI card with:

sudo modprobe -r vfio-pci