The machine I most need to fix is always the one with no working network stack, sat in a cupboard two floors down, displaying a BIOS prompt I cannot reach. SSH is no help when the box hasn't got as far as userspace. What you want in that moment is a hand on the keyboard and an eye on the screen, and what you have is neither.
Commercial IP-KVMs solve this and they are very good, but the decent ones cost more than the servers I'm rescuing. So I built one out of a Pi 4 that was otherwise gathering dust, and it has paid for itself in saved trips down the stairs.
the bill of materials
The whole thing is three parts plus cables:
- A Raspberry Pi 4 (2GB is plenty).
- A cheap USB HDMI capture dongle, the MS2109-based sort that shows up as a UVC video device. About a tenner.
- A CH9329 serial-to-HID module, which pretends to be a USB keyboard and mouse and takes its marching orders over a UART.
The capture stick handles the seeing. You plug the target machine's HDMI output into it, and it appears on the Pi as /dev/video0, a perfectly ordinary V4L2 device. The CH9329 handles the typing. The Pi drives it over the serial pins, the CH9329 plugs into the target's USB port, and the target sees a boring keyboard. Crucially, none of this needs a driver or an agent on the machine being rescued, which is the whole point. It works at the BIOS prompt because to the target it is just a monitor and a keyboard.
wiring the CH9329
The serial side is the only bit with any soldering, and barely that. Three wires from the Pi's GPIO header to the module: ground to ground, the Pi's TX to the module's RX, and the Pi's RX to the module's TX. Cross the data lines over, as you always do with UART, then disable the serial login console so Linux stops trying to use those pins as a terminal:
# raspi-config nonint do_serial_hw 0
# raspi-config nonint do_serial_cons 1
That enables the hardware UART and disables the console on it. After a reboot you get /dev/ttyS0, and the CH9329 is listening on it at 9600 baud by default. From there it's a documented packet format: a header, a command byte for "send these key codes", the HID scancodes, and a checksum. There are a couple of small Python libraries that wrap it so you're not hand-rolling checksums at two in the morning.
stitching it together
For the screen, ffmpeg will pull frames off /dev/video0 and I serve them as MJPEG over a tiny web page on the Pi. It's not pretty and the latency is around 200ms, but it is more than enough to read a kernel panic or pick a boot device. The keyboard half is a small handler that turns key presses in the browser into CH9329 packets out of the serial port.
ffmpeg -f v4l2 -framerate 30 -video_size 1280x720 \
-i /dev/video0 -f mpjpeg -q:v 5 -
The honest caveats: there's no power control, so I can't hard-cycle the box from here, only type into it. The HDMI capture stick is fussy about odd resolutions and will sometimes show a black frame until the target settles on a mode it likes. And it is, unmistakably, a Pi with two dongles hanging off it held together by a 3D-printed bracket I am not proud of.
But it boots, it survives a reboot of the target, and last week it let me fix a misconfigured boot order from the sofa instead of the cupboard. For something cobbled together from a spare board and twenty quid of dongles, that's a good afternoon's work.