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.