Claude Code / AI Shows Potential For Reverse-Engineering Hardware For Linux Support
Claude Code Reverse-Engineered a Scanner: the 80% of hardware RE that is tedious
Phoronix’s report is short on hype and long on evidence: Christian Schaller of Red Hat — a long-time Linux driver reverse engineer — used Claude Code to reverse-engineer the Pustek 8200i negative-photo scanner (a niche device with no Linux driver) into a working SANE driver, then wrote a Rust GNOME app to use it. The method is the durable part: Claude Code drove a Windows VM running the vendor’s own driver, captured the USB traffic, and reverse-engineered the protocol from the captures.
The tedious 80% of hardware reverse engineering — labeling hundreds of captured transactions, finding the pattern in the noise — is exactly the work an LLM is good at. The creative 20% — the stall, the timeout, the reset mid-transaction the vendor never documented — stayed a human job. This is the first credible data point for a split every Go developer should know how to draw: the LLM writes the happy-path client from the decoded protocol; the human supplies the edge-case recovery. The Go lens below is concrete — the client half of a SANE-style scanner protocol is a small, stdlib-only program, and we will show the whole thing: the protocol model, the fake device it runs against in tests, and the one-line rule for where the AI stops and the human starts.
The five phases of bringing a device up on Linux (and where the cost sits)
A new Linux hardware driver is not one task; it is five phases, and the cost distribution across them is the whole story for the AI question:
- Capture the protocol. The device talks over a bus (USB, I2C, SPI, MIPI). You capture the raw traffic on a system where the vendor’s driver works — usually a Windows VM. That capture is the raw material; it is not optional.
- Decode the protocol. Raw bytes are not a protocol; the meaning of the bytes is. Which transactions are commands, which are data, where do the fields end, what is the checksum. This is the tedious 80% — the part an LLM does in an afternoon that used to take days of hex-dump staring.
- Write the driver. The decoded protocol becomes code that speaks it and exposes a Linux interface. The LLM writes the happy path well; the edge-case recovery (a device that stalls, a reset mid-transaction) is the creative 20% it cannot invent.
- Test against the real device. The capture is a sample, not a spec. The device does things in the real world the capture never recorded. This phase is a human staring at a device and a log.
- Upstream. A social process, not a technical one. The LLM can prepare the patch series; the reviewers are still humans.
flowchart TB
A["1. Capture the protocol (Windows VM + vendor driver)"] --> B["2. Decode (the tedious 80%: the LLM's job)"]
B --> C["3. Write the driver (happy path: LLM; edge-case recovery: human)"]
C --> D["4. Test against the real device (cannot be automated)"]
D --> E["5. Upstream (the social gate)"]
style B fill:#1a3a1a
style C fill:#3a2a1a
style D fill:#3a3a1a
The one-line summary of the scanner case: the LLM did the tedious decode, the human supplied the recovery, and the driver works because the two halves are the right size for the right actor.
The Go lens: the client half of the protocol is ~40 lines
The Phoronix story is usually told as “AI wrote a C driver.” The Go framing is sharper: the client half of a SANE-style scanner protocol — a request/response frame stream over a bidirectional endpoint — is a small, readable, stdlib-only Go program. The AI wrote the driver (the C backend that turns libusb calls into frames); the client only has to speak the frames. That is the shape of the code:
| |
The proof that the protocol model is sound is the test against a fake device — the same loop runs without a scanner wired to the machine:
| |
Both pass (go test ./usbagent). The point is not that this is a real SANE driver; it is that the shape of the client is the shape Claude Code would write from a decoded capture, and that the human additions are the io.EOF branch and the status != 0 check — the two edge cases the vendor never documented.
The exchange, as a sequence
The protocol is so small it is worth drawing: one request byte goes out, a stream of reply records comes back, and the only state the client keeps is “is the next record a continuation?”
sequenceDiagram
participant C as SaneClient (Go, ~40 lines)
participant D as Scanner (Pustek 8200i)
C-->>D: 0x01 OPEN
D--->>C: frame{status=0x00} (ok: it is a scanner)
C-->>D: 0x02 SCAN
D--->>C: frame{status=0x80} (continuation: more lines follow)
D--->>C: frame{status=0x80} (continuation)
D--->>C: frame{status=0x00} (final line, continuation cleared)
Note over C: client stops at the final frame; never hard-codes a count
The practical rule for a new USB device on Linux
- Capture the traffic on the vendor’s system. The vendor driver is the oracle; a
usbmoncapture on a Windows VM is a one-command setup, not a project. - Let the LLM decode the capture — label the transactions, infer the command set — and verify the decode against the capture (the LLM can be wrong about a field boundary).
- Budget the edge-case recovery as the human’s job. The LLM writes the happy path from the decoded protocol; the stall, the timeout, the reset mid-transaction are the 20% that decide “production-grade” vs “toy.”
- Upstream is a social process. The LLM gets you to the review; the kernel/SANE community decides.
| Hardware RE step | Human cost (pre-AI) | What the AI changed |
|---|---|---|
| Find the datasheet / pinout | hours of web digging | seconds (searches, cross-references) |
| Map registers / decode transactions | days of trial-and-error reads | the model proposes the next probe; the human verifies |
| Write the happy-path driver | weeks | the AI drafts it from the decoded protocol |
| Edge-case recovery (the 20%) | the expensive part, always | unchanged — the human’s job |
The last row is the whole post: the AI did not replace the driver engineer; it removed the dead time between reads, and the human’s job shrank from “do all the work” to “check the work and pick the next read.”
Note: an AI is a hypothesis generator, not a verifier. Every register value or frame boundary it “identifies” must be confirmed by a second independent read (a different tool, a different path). The fake-device test above does exactly that: it replays the captured bytes, not the model’s claim.
Warning: a model that reverse-engineers a chip from one data source can be confidently wrong — a chip that reports an ID string the model saw in training is easy to mistake for a real identification. Cross-check against a second source (the vendor’s register map, a different bus, a manual) before you trust a mapping.
Takeaways
- The LLM did the tedious 80% — decoding captured USB traffic and reading driver source — and the human supplied the creative 20% (the edge-case recovery the vendor never documented). The Schaller scanner case is the first credible data point that this split works.
- The client half of the protocol is a small, stdlib-only Go program. The
Deviceinterface is the seam between “real libusb endpoint” and “ring buffer in a test,” and the Go type system is what makes the fake the test. - The vendor driver on a Windows VM is the oracle. Capturing the traffic it produces is phase 1, and it is a one-command setup. The decoded protocol is the LLM’s work; the recovery is the human’s.
- Upstream is a social gate. A driver that works on your machine is not a driver that is in mainline; the LLM helps you get to the review, the community decides.
Next steps
- Read the Phoronix report for Schaller’s specific Pustek 8200i example and the companion MIPI-camera fix — a register-level bug found by reading the driver source, the second kind of tedious work an LLM is good at.
- If you are bringing up a new USB device, start with a
usbmoncapture on the vendor’s system, feed the capture to an LLM for the decode, and verify the decode against the capture before you write a line of driver. - Write the client as a small stdlib program with a fake-device test — the
Deviceinterface above is the entire test surface. When you add the real libusb backend, nothing about the protocol client changes. - Budget the 20% explicitly. The stall, the timeout, and the reset mid-transaction are the lines you add by hand after the AI’s draft; that is where the driver earns “production-grade.”