An open, self-built firmware for the Sipeed NanoKVM-Pro (Axera AX630C,
dual Cortex-A53, aarch64/glibc), packaged as a Nix flake. The boot chain, Linux
kernel, video/encode backend, and the KVM application are built from source;
Axera's redistributable media libraries (libax_*.so, BSD-3) and the prebuilt
ax_*.ko media kernel modules are pinned as binary inputs.
The result is a reproducible .axp firmware image that boots and runs the full
web KVM on real hardware, driven by our own open libkvm.so capture/encode
backend instead of Sipeed's withheld closed glue.
Status: working.
nix build .#firmware-imageproduces a flashable.axp; flashed via AXDL it boots our from-source kernel + boot chain and auto-starts the web KVM (HTTPS on :80/:443) with our libkvm doing HDMI capture, H.264/MJPEG encode, and Opus audio. Verified end-to-end on a NanoKVM-Pro Desk.
# Build the flashable firmware image (aarch64, cross-built from x86_64).
nix build .#firmware-image
ls result/ # AX630C_emmc_arm64_k419_sipeed_nanokvm-selfbuilt.axp
# Flash it over USB (device in AXDL download mode — see docs/flashing-and-recovery.md).
nix run .#axdl -- --file result/*-selfbuilt.axp --wait-for-device
# Then open the web UI and set a password:
# https://<device-ip>/Everything you need beyond this lives in docs/:
| Doc | What's in it |
|---|---|
| docs/architecture.md | Boot chain, partition layout, the video pipeline, our libkvm, and the two vendor app stacks (why we run nanokvm, not kvmcomm) |
| docs/building.md | Every package, the build DAG, pinned hashes, cross-compile notes |
| docs/flashing-and-recovery.md | AXDL USB flashing, the User-button recovery path, full backup/restore, non-destructive SD-card boot |
| docs/updates.md | Our own OTA/update system: CI builds images + web-update packages; the web-UI "update" button pulls from our GitHub Releases, not Sipeed |
| docs/mini-display.md | The built-in screen: driven fully from source (kernel drivers built from our tree + our open Python status daemon; no kvm_ui, no .ko blobs), incl. sleep/wake on the knob button |
The deep on-device reverse-engineering log (UART maps, efuse/secure-boot findings, the resolved MIPI/VIN/VENC capture config, per-test results) lives in
../PLAN.md. This repo'sdocs/is the distilled, buildable reference.
| Component | Provenance | License |
|---|---|---|
| FSBL/SPL + DDR init, TF-A 2.7, OP-TEE 3.21, U-Boot 2020.04 | from source (maix_ax620e_sdk) |
GPL/BSD |
| Linux 4.19.125 kernel + NanoKVM-Pro DTS | from source (maix_ax620e_sdk_kernel) |
GPL-2.0 |
lt6911_manage.ko (HDMI-in bridge driver) |
from source | GPL-2.0 |
Mini-display stack: fbtft/fb_jd9853/gpio_keys/rotary_encoder drivers + nanokvm-display status daemon |
from source (drivers from the SDK kernel tree; daemon is ours, fonts generated from source-built terminus_font) |
GPL-2.0 / GPL-3.0 |
libkvm.so (our capture + H.264/MJPEG + Opus backend) |
from source — our open reimplementation over the Axera MPI | ours (GPL-3 app) |
| NanoKVM-Server (Go) + web UI (React) | from source (NanoKVM-Pro) |
GPL-3.0 |
ax_*.ko media modules (venc/mipi/proton/ivps/…) |
pinned blob (in the kernel repo) | GPL-tagged, source not published |
libax_*.so, libsns_dummy.so |
pinned blob (maix_ax620e_sdk_msp) |
BSD-3, redistributable |
| Rootfs base | pinned vendor Ubuntu 22.04 arm64 (from the v1.0.15 base .axp) |
mixed |
The design goal is an open, reproducible firmware that links the accepted Axera blobs — not a blob-free build. See docs/architecture.md for the rationale and the full blob audit.
Deliberately excluded: the vendor's closed mini-display app
kvm_ui(no source published). The built-in screen is instead driven by our own open stack — from-source kernel drivers + a small Python status daemon with sleep/wake on the knob button (details).
Cross-compiled from x86_64-linux via nixpkgs pkgsCross.aarch64-multiplatform
(stock aarch64 glibc GCC — no exotic toolchain). Outputs are keyed off the
dev/build system; the firmware target is always aarch64.
firmware-image (.axp) ◄── image.nix: streaming zip-rewrite of the vendor base .axp,
▲ swapping in our signed partitions + overlaid rootfs
│
├── boot (SPL/DDR-init + ATF + OP-TEE + U-Boot, one from-source build)
├── kernel-slot-image (Image → ax_gzip -9 + signed header)
├── dtb-slot-image (patched DTB → ax_gzip -9 + signed header)
└── rootfs (vendor Ubuntu base + our libkvm.so + merged/depmod'd modules,
edited in-place with debugfs — no root/mount needed)
▲
├── kvm-encoder → libkvm.so (MIPI_RX → VIN → ISP-bypass → AX_VENC)
├── kernel → /lib/modules + lt6911_manage.ko
└── ax-ko-blobs → prebuilt ax_*.ko (merged, depmod'd against our kernel)
Full package list and the dependency DAG are in docs/building.md.
nix build .#sd-image produces a dd-able microSD image that boots the entire
from-source stack from the SD/TF slot, leaving eMMC untouched — hold the User
button while applying power to select it, power on normally to revert. This is the
safe way to try changes without touching the installed firmware. Details and the
strap/boot-source caveats are in
docs/flashing-and-recovery.md.
We build NanoKVM-Server from source, so it's patched to fetch updates from
our GitHub Releases instead of cdn.sipeed.com. Tag a release (git tag v2.0.0 && git push --tags) and .github/workflows/release.yml builds the .axp
image and a web-update package, publishing both as Release assets. Every
device then sees the new version in the web UI's update button and pulls it
from us. Full design + setup (including the one-time updateBaseUrl step) is in
docs/updates.md.
The AX630C's mask-ROM USB download mode is unbrickable: hold User ~10 s to
enter it, then re-flash any .axp (ours or the stock vendor image) with
nix run .#axdl. Make a full backup first — see
docs/flashing-and-recovery.md.
GPL-3.0 — Copyright (C) 2026 GoogleBot42.
Some bundled and pinned components keep their own licenses: the Axera
libax_*.so / libsns_dummy.so are BSD-3, the ax_*.ko modules are GPL-tagged,
and the Ubuntu rootfs base is its own mix. See the
table above.