User SPI Adventures

Last year I started working on a project in the space that I thought would be pretty cool, a Mystery Box. I made a box out of foam board, mounted a servo as a catch. Inside I wired up an Arduino to control the servo. The Arduino connected to a Raspberry Pi over SPI, I used SPI because it was a nice simple protocol to implement on both the Arduino and the Raspbery Pi.

The Mystery Box was going to run a BBS which had control over the servo. My idea was to use the box as a simple CTF target, with the servo giving instant and substantial feedback for success.

Around this time I had been asked to port NewCWV to FreeBSD, we wanted to have more than one implementation of the proposed standard available. Doing some development in the FreeBSD kernel made me want to look at using the operating system in other places.

Up to this point I had been using Linux on the Pi in the Mystery box, but I thought it would be fun to try FreeBSD. FreeBSD on the Pi was in a reasonable state, I didn't have trouble getting the Pi to boot. When it came to controlling the Arduino over SPI I hit a snag.

There wasn't (and still isn't) user space SPI support in FreeBSD. This means that I can control devices to connected to SPI from a kernel driver, but I can't do so from user space. Kernel code is harder to write, not portable and means I can't reuse Linux code. User space code is easier to write and if the interface is similar to existing ones I can reuse a lot of other code.

I was enjoying writing the NewCWV port and I thought to myself: "I should make the world a better place and write a user space SPI layer". And that is exactly what I set out to do over the next few months.


This week, well over 9 months later I finally have a working SPI layer. I can issue, read(2), write(2) and ioctl(2) commands and see the bus burst into life with data flowing across.

On the other end of the bus I have a Trinket Pro (Adafruit arduino clone). The Trinket acts as a SPI slave, when there is activity on the bus it spits on the values from the master over UART and writes the SPI values back onto the bus with 10 added.

The Arduino seems to struggle at the default bus speed of 500KHz, but runs fine when I lower the speed with a sysctl down to 50KHz.


Speaking to the Arduino is okay, but it doesn't make a very exciting demo. I had a couple of devices that can be controlled over SPI, a SSD1306 OLED Screen and a PN532 NFC Reader .

The NFC Reader is supported by libnfc , a quick look shows that libnfc is available in the FreeBSD ports tree. I looked at the libnfc code while writing the user space layer and it seemed pretty straight forward. libnfc opens the SPI device and calls the SPI IOC MESSAGE ioctl to send spi ioc transfer structs to the driver.

The interface I have written is a little different, using an object to describe the transfer similar to the way iic(4) works. To add FreeBSD support I need to create the struct and swap the ioctl(2) call, this should be straight forward.

Using the OLED is a little different. Adafruit have provided a python library to speak to the screen using either i2c or SPI. The python library imports a module to speak to SPI and seems to mostly use read(2) to control the screen. This is going to be harder to port across and get working.

There is also an Adafruit Arduino library for the SSD1306 and Arduino compatible boards. This is C++ that has been written to run on an Arduino rather than on a unix machine. This code could probably be slimmed down, with the calls to fastspiwrite swapped out to calls to the kernel interface.


My implementation is still a little rough around the edges, the code needs to be tidied up, moved into the kernel directly and tested on the latest head. I think that getting user space code working with it will show up any bugs, it will certainly make for more meaningful test cases.

My next step is to get the NFC reader working with libnfc and the raspberry pi, then I can start work on the screen. Once I have some SPI examples working I might even get back to setting up the Mystery Box for a CTF in the space.