smolmux
A portable C11 device multiplexer. Holds a device connection open (serial UART, GDB stub) and multiplexes access to multiple clients over Unix sockets using newline-delimited JSON.
Single static binary, low latency, small footprint - built for daily serial and
GDB bring-up on Linux.
New here? docs/START-HERE.md is a one-screen router - find your intent (run it, bring up a new board with an AI agent, understand the architecture, hack on the code) and it points you to the right doc.
Example: probe an unknown board
One broker holds SWD; smolmux-gdb-mcp runs probe_unknown_board. On a
SAM C21 Xplained Pro that path decoded Cortex-M0+ from CPUID, named the
part via SAM DSU DID, rejected a false STM32 match, and wrote a starter
*.gdb-profile.json. Tools, register values, and profile shape:
docs/demo-samc21-probe-transcript.md.
Day-to-day serial (multi-client, U-Boot break-in, flasher handoff):
docs/daily-driver.md.
Features
- Serial UART, GDB MI, and serial-over-TCP (telnet + RFC2217) device links via vtable polymorphism
- Multiple concurrent clients over Unix sockets with role-based access (observer/controller/takeover)
- Expect engine - concurrent regex matching on the device byte stream with timeouts
- Anomaly detection - pattern-based crash/error detection with cooldown and incident tracking
- Output history - timestamped ring buffer for replay by late-joining clients
- Structured logging - JSONL I/O log + human-readable text log with rotation
(the I/O log records everything sent to and from the device, including
anything typed at a login prompt β it is created
0600 under your private
state directory, and smolmux refuses to write it through a symlink or to a
file owned by another user)
- Network sinks - TCP and WebSocket for remote access (loopback by default).
The wire protocol is cleartext, and any client that completes the handshake
gets full control of the device β console writes, pins, BREAK, SysRq, GDB.
On loopback without
--auth-token, the broker generates a token into a
0600 file in $XDG_RUNTIME_DIR (or /tmp), so other local users and processes
cannot connect; your own smolmux-monitor / smolmux-mcp read it
automatically, and smolmux-cli token prints it. For remote use, keep the
bind on loopback and reach it over an SSH tunnel or WireGuard rather than
exposing the port; smolmux refuses to serve a non-loopback TCP bind with no
--auth-token. --insecure-no-auth turns both protections off.
- MCP servers - standalone
smolmux-mcp / smolmux-gdb-mcp attach to a running broker; optional in-process --mcp sink for single-process stdio
- Boot tracking & autoresponder - ordered boot stages, stall events, standing expect->send rules
- Autoboot interrupt - broker-side key flood (and optional DTR/RTS reset) for
bootdelay=0 U-Boot
- Device profiles / board manifests - JSON configs for prompts, anomalies, multi-wire boards
- Auto-reconnect - exponential backoff recovery on USB-serial disconnect
- Build-time feature selection - Kconfig-based; UART-only builds carry no GDB/TCP/WebSocket code
Quick start
cmake -B build && cmake --build build -j$(nproc)
./build/smolmux /dev/ttyUSB0
Connect a client:
./build/smolmux-monitor /dev/ttyUSB0
Day-to-day workflows (profiles, logs, U-Boot break-in, multi-wire boards):
docs/daily-driver.md. Intent router: docs/START-HERE.md.
Build
cmake -B build && cmake --build build -j$(nproc)
ctest --test-dir build
Feature profiles
cp configs/defconfig.minimal .config && cmake -B build
cp configs/defconfig.uart .config && cmake -B build
cp configs/defconfig.embedded .config && cmake -B build
cp configs/defconfig.full .config && cmake -B build
Or toggle features directly:
cmake -B build -DSM_ENABLE_GDB=OFF -DSM_ENABLE_SINK_WS=OFF
Developer benchmarks (e.g. the output coalescer harness) are off by default:
cmake -B build -DSM_BUILD_BENCH=ON && cmake --build build --target bench_coalesce
Interactive configuration:
cmake --build build --target menuconfig
Static builds
cmake -B build -DSM_MUSL_STATIC=ON
cmake -B build -DSM_STATIC=ON
Usage
smolmux <port> [options]
Options:
-b, --baud <rate> Baud rate (default: 115200)
-s, --socket <path> Unix socket path
-l, --log-dir <dir> I/O log directory
(default: $XDG_STATE_HOME/smolmux or
~/.local/state/smolmux)
-t, --text-log-dir <dir> Text log directory
-p, --profile <path> Device profile JSON file
--board <name> Group this wire under a board (for discovery)
--role <label> This wire's role on the board (console, swd, ...)
--gdb Use GDB MI link instead of UART
--gdb-path <path> Path to gdb binary (default: gdb)
--gdb-target <spec> GDB target (e.g., localhost:3333)
--serial-tcp <host:port> Connect to a serial-over-TCP device server
(ser2net, socat, terminal server; telnet +
RFC2217 baud/DTR/RTS/break control)
--mcp Enable in-process MCP stdio sink (prefer standalone
smolmux-mcp against a daemon broker for daily use)
--tcp-port <port> Enable TCP sink (default: 5555)
--tcp-bind <addr> TCP bind address (default: 127.0.0.1)
--auth-token <token> Require token in hello from TCP clients
(prefer env SMOLMUX_AUTH_TOKEN - hidden from ps)
--auth-token-file <path> Read the token from a file
--insecure-no-auth Serve TCP/WS with no token. Without it, a
loopback listener gets a generated token
(0600 file; smolmux-cli token prints it) and
a non-loopback --tcp-bind is refused.
--ws-port <port> Enable WebSocket sink (default: 5556)
--no-text-log Disable text log
--no-io-log Disable JSONL I/O log
--no-reconnect Don't auto-reconnect on disconnect
--wait-device <seconds> Wait for the device path before open
--gdb-allow-shell Permit GDB shell/python/eval (off by default)
--list-ports List available serial ports and exit
--list-profiles List available device profiles and exit
--help-protocol Show wire protocol documentation
-v, --verbose Enable debug logging
-V, --version Show version
-h, --help Show this help
Wire protocol
Newline-delimited JSON over Unix sockets (also TCP/WS sinks). Binary data is
base64-encoded. Message set covers session control, expect, history, anomaly,
boot stages, autoboot flood, and autoresponder.
Full reference: ./build/smolmux --help-protocol (always matches this binary).
Client -> Broker: hello, send, send_expect, takeover, release, status, pin_control, set_baud, suspend, resume, history_request, incidents_request, configure_anomaly, interrupt_autoboot, configure_autoresponder, autoresponders_request
Broker -> Client: welcome, output, input_echo, expect_result, status_response, error, history_response, incidents_response, anomaly, autoboot_result, boot_stage, boot_stall, autoresponders_response, autoresponder_fired, suspended, resumed, link_down, link_up
Example session:
-> {"type":"hello","name":"my-tool","role":"controller","protocol_version":1}
<- {"type":"welcome","broker_version":"x.y.z","protocol_version":1,"port":"/dev/ttyUSB0","baud":115200,"your_role":"controller"}
-> {"type":"send","id":"1","data":"dW5hbWUgLWEK"}
<- {"type":"output","data":"TGludXggNC4xOS4w...","timestamp":1709654321.123}
Architecture
βββββββββββββ βββββββββββββ
β uart link β β gdb link β <- device-facing, compiled in/out via Kconfig
βββββββ¬ββββββ βββββββ¬ββββββ
β β
βΌ βΌ
ββββββββββββββββββββββββββββββββ
β smolmux core β <- epoll event loop, message bus,
β history Β· logging Β· anomaly β role enforcement, expect engine
ββββ¬ββββββββ¬ββββββββ¬ββββββββ¬ββββ
β β β β
βΌ βΌ βΌ βΌ
ββββββββββββββββββββββββββββββ
β unix ββ tcp ββ ws ββ mcp β <- sinks, also compiled in/out
βsocketββsink ββsink ββ sink β
ββββββββββββββββββββββββββββββ
See DESIGN.md for full architecture documentation.
Dependencies
Required: cJSON (vendored, single file)
Auto-detected: PCRE2 (libpcre2-8). Used as the regex engine when
present, because it bounds backtracking internally; the build falls back to
POSIX ERE automatically when it is absent, so it is never required. Force
either way with -DSM_ENABLE_PCRE2=ON (error if missing) or
-DSM_ENABLE_PCRE2=OFF.
Core has zero required external dependencies beyond POSIX + cJSON.
- smolmux-cli - command-line client (send commands, read output;
with-port <cmd> suspends the port, runs an external tool like a flasher, then always resumes)
- smolmux-monitor - interactive terminal client with escape sequences (prefix key then a command; prefix defaults to Ctrl-], change with
-e, e.g. -e esc or -e ^A)
- smolmux-mcp - standalone MCP server: connects to a running broker and exposes serial tools (
serial_send_command, serial_read, serial_boot_status, serial_add_autoresponder, ...). Alternative: broker --mcp sink embeds MCP in-process.
- smolmux-gdb-mcp - standalone MCP server for GDB debugging: 21 tools over a
broker holding a
--gdb link - breakpoints, stepping, backtrace, name-labeled
registers, memory, expression eval, fault-register decode (where the core has
them), peripheral reads, gdb_interrupt, and unknown-board probing on
ARM Cortex-M today (gdb_identify_target / gdb_generate_profile; other
architectures are planned). Resources and prompts include
smolmux-gdb://board-probing and probe_unknown_board. Built when
SM_ENABLE_GDB is on; chip-ID validated on a SAM C21 Xplained Pro.
- smolmux-watcher - daemon that monitors for anomalies and saves incident reports to disk
- Start here - One-screen intent router (daily use, new board, architecture, hacking).
- MCP setup - Register
smolmux-mcp / smolmux-gdb-mcp with Claude Code, Claude Desktop, or Cursor.
- Daily driver - Recommended build, runtime flags, U-Boot break-in, boot stages, boards, coexistence with flashers.
- Board Exploration Workflow - Runbook for a fresh board (manually or with an AI agent): wires, SWD identify, console, peripherals.
- Board Bring-up Template - Copy-per-board fact-capture template.
- Persistent Serial Device Names - Stable names for UART dongles (
/dev/serial/rpi-console etc.).
- Hardware validation matrix - What is validated on real hardware vs not yet proven.
- DESIGN.md / CLAUDE.md - Architecture and contributor map.
Free vs Pro
Everything in this repository is MIT-licensed - full source, wire protocol,
generic device profiles, build system. Build it yourself and you have the
complete product.
smolmux Pro is convenience, not a feature gate: prebuilt static binaries
(x86_64 + aarch64, musl, zero runtime dependencies), the curated profile pack
with per-profile notes, and 6 months of email support. One-time purchase
($79). MCP setup for zip paths is docs/MCP-SETUP-FULL.md (also in
this public tree).
Buy smolmux Pro - $79 one-time -
download includes current static binaries and the profile pack.
License
MIT. cJSON is vendored under its own MIT license
(deps/cJSON/LICENSE).