Hive-M internals
Xilinx ZynqMP, Yocto build (aionyx-image-minimal), RAUC A/B rootfs, uwm updater.
Project layout
| Path | Content |
|---|---|
project.json | Config: yocto manifests, clone repos, buildsign, secure_boot. |
container/ | Dockerfile: Ubuntu 24.04 Yocto host, repo tool, user ntl (uid 1000). |
images/ | system.xsa input + build outputs (sd.wic, Image, update archives). |
sources/sdt/ | System Device Tree (zynqmp*.dtsi, dt-bindings/), input to gen-machine-conf. Bitstream from configure xsa/configure bitstream lands here. |
sources/<repo> | Clones from sw.clone (uwm, udm, drivers). |
sw/meta-ntl-hive/ | The NTL Yocto layer, copied into the build tree on every run. |
sdk/ | Dockerfile + examples for the SDK image (export sw --image). |
oot/<module>/ | Out-of-tree module build dirs (created by build module). |
keys/ | Secure-boot keys (after secure-boot keygen, gitignored). |
Docker volumes
| Volume | Mount | Scope |
|---|---|---|
ntl_buildhive_sw_<name>_<id> | /work (.repo, build/) | per project |
ntl_buildhive_sstate | /mnt/ntl_sstate (sstate-cache/, downloads/) | shared across all hive-m projects; removed only by clean --all |
kdevsrc volume | /kernel-devsrc | per project, filled by build kernel-devsrc |
Bind mounts: sw/ → /sw (ro), sources/sdt → /custom_sources/sdt, keys/ → /keys (ro, if present), each clone repo → /custom_sources/<name>.
Init script
Prepended to every containerized hive-m command:
- Exports
BUILDHIVE_VERSION(tool version) viaBB_ENV_PASSTHROUGH_ADDITIONS- default version for update/migration archives. - First run:
repo init -u <yocto_manifests.url> -b <branch>,repo sync. source edf-init-build-env.- Reconciles
conf/bblayers.confwith layers on disk (removes stale, adds missing). - First run only: sets
SSTATE_DIR/DL_DIRinlocal.conf, runsgen-machine-conf parse-sdt --hw-description /custom_sources/sdt --machine-name zynqmp-ntl-hive-m-sdt, creates+addsmeta-ntl-hive, setsMACHINE ??= ntl-hive-m. - Every run: re-copies
/sw/meta-ntl-hive/*into../sources/meta-ntl-hive/- layer edits in the project take effect on the next build. - Adds/removes the
secure-boot.confrequire line inlocal.confpersw.secure_boot. - Rewrites the
# >>> buildhive sstate mirrorblock inlocal.confpersw.sstate_mirror; credentials fromSSTATE_MIRROR_AUTHenv or the secrets server, written to~/.netrc. Mirror misses fall back to the localntl_buildhive_sstatecache and a normal build. - Rewrites the
# >>> buildhive rm_workblock:INHERIT += "rm_work"withRM_WORK_EXCLUDE += "linux-xlnx aionyx-image-minimal". Work dirs are deleted after packaging (tmp/shrinks several times), sodevshell/tmp/workinspection only works for the excluded recipes;kernel-devsrcandexport sdk(busybox from the imagerootfs/) keep working. Existing projects reclaim space on the next build.
Repos in sw.clone are pulled in parallel before the container starts; --no-update skips this.
meta-ntl-hive layer
conf/layer.conf: priority 7, compatible scarthgap, depends on aionyx-hive. IMAGE_INSTALL:append adds extra packages to the image (template: ptp4l-config); edit with buildhive packages add|remove or the GUI Configure page, see commands.md.
| Group | Content |
|---|---|
recipes-apps | uwm (Rust server + loader, systemd uwm-loader.service), udm, ptp4l + configs, phc2sys-sync (systemd service syncing system clock from PHC, device set via /etc/default/phc2sys), sw-conf. uwm/udm fetch from /custom_sources/* with SRCREV=${AUTOREV}. |
recipes-bsp | device-tree bbappend (FIT pubkey injection), u-boot bbappends + boot script. |
recipes-core | base-files, update bundles (updates.md), image bbappend, legacy migration, persistent-data, rauc config, systemd-conf. |
recipes-devtools | bun-native, mdbook-native (uwm frontend/docs), wic fix. |
recipes-kernel | linux-xlnx bbappend + bsp.cfg. |
recipes-security | hive-ca, hive-jwt-secret. |
wic/rauc-zynqmp.wks | Disk layout (below). |
conf/machine/ntl-hive-m.conf requires the SDT-generated machine and backs /etc, /opt/ntl, /usr/lib/firmware overlays with /data.
Serial console lock
recipes-bsp/u-boot/u-boot-xlnx/console.cfg + patch 0002-configs-zynqmp-restrict-stored-env-*. Contract consumed by uwm (docs/src/security/serial-console.md in the uwm repo).
| Layer | Mechanism |
|---|---|
| U-Boot prompt | CONFIG_AUTOBOOT_KEYED + CONFIG_AUTOBOOT_ENCRYPTION: autoboot stops only on the password whose salted SHA256 is CONFIG_AUTOBOOT_STOP_STR_SHA256 (salt:hash). Hash lives in the signed u-boot binary, not in the env. |
| Prompt on/off | bootdelay in uboot.env (/boot, fw_setenv): 2 = password prompt, -2 = boot straight through. preboot maps -1 to 2; -1 would otherwise drop to the shell without a password check. |
| Stored env | CONFIG_ENV_WRITEABLE_LIST: uboot.env may only override bootdelay, BOOT_ORDER, BOOT_A_LEFT, BOOT_B_LEFT (CFG_ENV_FLAGS_LIST_STATIC). bootcmd, preboot, bootstopkeysha256 always come from the default env. Add new persisted vars to the list or u-boot silently drops them. |
| Linux login | serial-getty@ttyPS0.service (from SERIAL_CONSOLES), enable symlink in the /etc overlay. systemctl disable --now / enable --now by uwm. |
FSBL, PMUFW, ATF and U-Boot still print on UART0. Output only, no input path. JTAG not covered.
New password:
SALT=$(openssl rand -hex 4); printf '%s%s' "$SALT" "$PASSWORD" | sha256sum | awk -v s="$SALT" '{print s":"$1}'
Put the result in CONFIG_AUTOBOOT_STOP_STR_SHA256, rebuild u-boot. At the prompt type the password (no echo) during the delay.
SDK image
export sw --image wraps the populate_sdk installer into a standalone container for application developers: no Yocto tree, no buildhive.
| Item | Value |
|---|---|
| Image | ghcr.io/nettimelogic/hive-m-sdk:<version> (+ latest), linux/amd64 only (Yocto SDKMACHINE default) |
| Base | ubuntu:24.04 + build-essential (kernel HOSTCC), git, kmod, pkg-config, python3. cmake/ninja/ctest/qemu come from the SDK. |
| SDK | installer bind-mounted at build (RUN --mount), -y -d /opt/ntl-sdk; nativesdk locale-archive, non-aarch64 qemu-system-*/qemu-* and their firmware removed |
| Env | /etc/profile.d/ntl-sdk.sh sources environment-setup-* and sets CMAKE_CROSSCOMPILING_EMULATOR; pulled in by the entrypoint, /etc/bash.bashrc and login shells (docker run, docker exec, GitHub Actions bash -l) |
| qemu | qemu-aarch64 user-mode from nativesdk-qemu-xilinx; ntl-run <binary> wrapper, ctest transparent, QEMU_LD_PREFIX set so direct execution works on hosts with aarch64 binfmt_misc (Docker Desktop). System emulation uses Ubuntu qemu-system-arm (has virtfs/9p, the Xilinx build does not); Xilinx qemu-system-* and firmware removed. |
| User | ntl (uid 1000, passwordless sudo), WORKDIR /src |
| Kernel | kernel-devsrc in $SDKTARGETSYSROOT/usr/src/kernel, scripts prepare modules_prepare run at image build |
| Examples | /opt/ntl-sdk/examples/ (hello: C, CMake, Makefile; hello-module: out-of-tree kernel module) |
| modtest | ntl-modtest [-d overlay.dtso] [-r regs.txt] [-k] [-v] <module.ko>...: boots /opt/ntl-sdk/Image (copied from images/Image) in qemu-system-aarch64 -M virt -m 512M, initramfs = modtest/init (C, PID 1) + ld-linux + libc from the sysroot. Exit 0 on MODTEST: PASS. |
| shell | ntl-shell [-d overlay] [-r regs] [-m mod.ko]... [dir]: interactive busybox shell on the same kernel and mock. Base initramfs (/opt/ntl-sdk/busybox, copied from the image rootfs by export sw since target-sdk-provides-dummy keeps shells out of the SDK sysroot; usr/lib/*.so*, linuxptp, ntl-reg) cached in ~/.cache/ntl-shell, session archive (init script, overlay, regs, modules, /src copy) appended, one gzip stream, rootfstype=ramfs (tmpfs rootfs hits ENOSPC while unpacking). /src over 9p and eth0 via user networking when .config has 9P_FS/VIRTIO_NET. |
| PHC mock | ntl-shell -p <base>: ntl-mockphc daemon in the guest serving the ntl_phc register protocol (modtest/ntl-mockphc.c, header comment has the map): done bit permanent, time regs ticking from CLOCK_MONOTONIC + offset, settime/adjtime/adjfine command bits consumed. Makes phc_ctl, phc2sys, ptp4l usable without hardware. |
| HW mock | Kernel booted with mem=448M: 0x5c000000..0x5fffffff is guest RAM outside the kernel map, so ioremap and /dev/mem work on it. init writes regs.txt values there, applies the overlay via /sys/kernel/config/device-tree/overlays (target &{/}), loads modules, reports bound devices from /sys/module/<m>/drivers, dumps registers, unloads. Verified with ntl_phc (two cores probe, PTP clocks register). Registers are inert memory: no counters, no side effects. |
Build context is a temp dir under images/ holding only sdk/ and the installer (hardlinked when possible), so the wic files never reach the docker daemon. aionyx-image-minimal.bbappend sets TOOLCHAIN_HOST_TASK (+nativesdk-cmake/ninja/pkgconfig, makes the env script export CMAKE_TOOLCHAIN_FILE), TOOLCHAIN_TARGET_TASK (+kernel-devsrc) and SDKIMAGE_FEATURES = "dev-pkgs" (no dbg-pkgs/src-pkgs, ~1 GB less).
Kernel module: make -C $SDKTARGETSYSROOT/usr/src/kernel M=$PWD modules, see /opt/ntl-sdk/examples/hello-module. Module vermagic matches the kernel of the build the SDK came from; keep SDK version and firmware version paired. kernel-devsrc has no git metadata, so the Dockerfile derives the -g<sha>[-dirty] suffix from usr/lib/modules/<release> and passes it as LOCALVERSION to make prepare (exported in the profile too), failing the build on mismatch.
Usage:
docker run --rm -it -v "$PWD":/src ghcr.io/nettimelogic/hive-m-sdk:0.5.1 # shell
docker run --rm -v "$PWD":/src ghcr.io/nettimelogic/hive-m-sdk:0.5.1 make # one-shot
docker run --rm -v "$PWD":/src ghcr.io/nettimelogic/hive-m-sdk:0.5.1 sh -c 'cmake -B build -G Ninja && cmake --build build'
GitHub Actions (container: jobs skip the entrypoint, use a login shell):
jobs:
build:
runs-on: ubuntu-latest
container: ghcr.io/nettimelogic/hive-m-sdk:0.5.1
defaults: { run: { shell: bash -leo pipefail {0} } }
steps:
- uses: actions/checkout@v4
- run: cmake -B build -G Ninja && cmake --build build && ctest --test-dir build
Publish flow: buildhive export sw --push (or --no-build --push on an existing installer) after docker login ghcr.io. Match --version to the firmware release the SDK was built from.
recipes-kernel/linux/linux-xlnx/qemu.cfg adds virtio-mmio/pci, virtio-net/blk/console, 9p and PCI_HOST_GENERIC to the Hive-M kernel. Inert on the board, lets ntl-shell share /src and network under -M virt; virtio-blk is the path to booting sd.wic in qemu later. export sw also copies rootfs/usr/bin/busybox to images/busybox for the guest shell.
Disk layout (rauc-zynqmp.wks)
| Partition | Size | Content |
|---|---|---|
p1 /boot | 512M FAT | BOOT.bin, kernel, dtb, boot.scr, uEnv.txt, uboot.env |
| p2 root_a | 2G | squashfs, RAUC slot A |
| p3 root_b | 2G | empty, RAUC slot B |
p4 /data | 512M ext4 | persistent overlay upper (/etc, /opt/ntl); never updated |
p5 /home | ext4 | growable (x-systemd.growfs) |
Kernel development
build kernel-devsrc -> /kernel-devsrc volume: kernel source + build tree + env.sh
build module <name> -> oot/<name>/*.ko (scaffolds Kbuild Makefile, builds against /kernel-devsrc)
build kernel-oot -> images/Image (make -j Image modules, no bitbake)
shell --module <name> -> bash with env.sh sourced, cwd oot/<name>
code -> VS Code attached to dev container (/kernel-devsrc, /oot, /drivers)
env.sh sets ARCH, CROSS_COMPILE and the toolchain PATH. Module sources live in sources/drivers/staging/ntl/<name>/ (from the drivers clone repo). Re-run build kernel-devsrc after kernel config changes.