Tech News
← Back to articles

Writing a basic Linux device driver when you know nothing about Linux drivers

read original related products more articles

A couple of months ago I bought the Nanoleaf Pegboard Desk Dock, the latest and greatest in USB-hub-with-RGB-LEDs-and-hooks-for-gadgets technology. This invention unfortunately only supports the real gamer operating systems of Windows and macOS, which necessitated the development of a Linux driver.

Over the past few posts I’ve set up a Windows VM with USB passthrough, and attempted to reverse-engineer the official drivers, As I was doing that, I also thought I’d message the vendor and ask them if they could share any specifications or docs regarding their protocol. To my surprise, Nanoleaf tech support responded to me within 4 hours, with a full description of the protocol that’s used both by the Desk Dock as well as their RGB strips. The docs mostly confirmed what I had already discovered independently, but there were a couple of other minor features as well (like power and brightness management) that I did not know about, which was helpful.

Today, we’re going to take a crack at writing a driver based on the (reverse-engineered) protocol, while also keeping the official documentation at hand. One small problem, though: I’ve never written a Linux device driver before, nor interacted with any USB device as anything else but a user.

Skip to the good part? Lots of yapping ahead. If you just want to see the code, click here.

Starting from scratch

Most Linux distros ship with lsusb , a simple utility that will enumerate all USB devices connected to the system. Since I had no clue where to start from, I figured I might as well run this to see if the device appears in the listing.

$ lsusb Bus 001 Device 062: ID 37fa:8201 JW25021301515 Nanoleaf Pegboard Desk Dock

Well, good news, it’s definitely there. But, how can the kernel know that what I have plugged in is the “Nanoleaf Pegboard Desk Dock”? The kernel (presumably) has no knowledge of this device’s existence, yet the second I plug it in to my computer it receives power, turns on and gets identified by the kernel.

As it turns out, we actually already have a driver! It’s just a very stupid one. If we run lsusb in verbose mode and request the information just for this specific device, we will get a lot more details about it:

$ lsusb -d 37fa:8201 -v Bus 001 Device 091: ID 37fa:8201 JW25021301515 Nanoleaf Pegboard Desk Dock Negotiated speed: Full Speed (12Mbps) Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 1.10 bDeviceClass 0 [unknown] bDeviceSubClass 0 [unknown] bDeviceProtocol 0 bMaxPacketSize0 64 idVendor 0x37fa JW25021301515 idProduct 0x8201 Nanoleaf Pegboard Desk Dock bcdDevice 1.09 iManufacturer 1 JW25021301515 iProduct 2 Nanoleaf Pegboard Desk Dock iSerial 3 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 0x0029 bNumInterfaces 1 bConfigurationValue 1 iConfiguration 4 Nanoleaf Pegboard Desk Dock bmAttributes 0xa0 (Bus Powered) Remote Wakeup MaxPower 70mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 2 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 0 [unknown] bInterfaceProtocol 0 iInterface 5 Nanoleaf Pegboard Desk Dock HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.00 bCountryCode 0 Not supported bNumDescriptors 1 bDescriptorType 34 (null) wDescriptorLength 34 Report Descriptors: ** UNAVAILABLE ** Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x82 EP 2 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 1 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x02 EP 2 OUT bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 1 Device Status: 0x0000 (Bus Powered)

... continue reading