Hive-M internals

Xilinx ZynqMP, Yocto build (aionyx-image-minimal), RAUC A/B rootfs, uwm updater.

Project layout

PathContent
project.jsonConfig: 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

VolumeMountScope
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-devsrcper 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:

  1. Exports BUILDHIVE_VERSION (tool version) via BB_ENV_PASSTHROUGH_ADDITIONS - default version for update/migration archives.
  2. First run: repo init -u <yocto_manifests.url> -b <branch>, repo sync.
  3. source edf-init-build-env.
  4. Reconciles conf/bblayers.conf with layers on disk (removes stale, adds missing).
  5. First run only: sets SSTATE_DIR/DL_DIR in local.conf, runs gen-machine-conf parse-sdt --hw-description /custom_sources/sdt --machine-name zynqmp-ntl-hive-m-sdt, creates+adds meta-ntl-hive, sets MACHINE ??= ntl-hive-m.
  6. Every run: re-copies /sw/meta-ntl-hive/* into ../sources/meta-ntl-hive/ - layer edits in the project take effect on the next build.
  7. Adds/removes the secure-boot.conf require line in local.conf per sw.secure_boot.
  8. Rewrites the # >>> buildhive sstate mirror block in local.conf per sw.sstate_mirror; credentials from SSTATE_MIRROR_AUTH env or the secrets server, written to ~/.netrc. Mirror misses fall back to the local ntl_buildhive_sstate cache and a normal build.
  9. Rewrites the # >>> buildhive rm_work block: INHERIT += "rm_work" with RM_WORK_EXCLUDE += "linux-xlnx aionyx-image-minimal". Work dirs are deleted after packaging (tmp/ shrinks several times), so devshell/tmp/work inspection only works for the excluded recipes; kernel-devsrc and export sdk (busybox from the image rootfs/) 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.

GroupContent
recipes-appsuwm (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-bspdevice-tree bbappend (FIT pubkey injection), u-boot bbappends + boot script.
recipes-corebase-files, update bundles (updates.md), image bbappend, legacy migration, persistent-data, rauc config, systemd-conf.
recipes-devtoolsbun-native, mdbook-native (uwm frontend/docs), wic fix.
recipes-kernellinux-xlnx bbappend + bsp.cfg.
recipes-securityhive-ca, hive-jwt-secret.
wic/rauc-zynqmp.wksDisk 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).

LayerMechanism
U-Boot promptCONFIG_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/offbootdelay 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 envCONFIG_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 loginserial-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.

ItemValue
Imageghcr.io/nettimelogic/hive-m-sdk:<version> (+ latest), linux/amd64 only (Yocto SDKMACHINE default)
Baseubuntu:24.04 + build-essential (kernel HOSTCC), git, kmod, pkg-config, python3. cmake/ninja/ctest/qemu come from the SDK.
SDKinstaller 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)
qemuqemu-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.
Userntl (uid 1000, passwordless sudo), WORKDIR /src
Kernelkernel-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)
modtestntl-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.
shellntl-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 mockntl-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 mockKernel 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)

PartitionSizeContent
p1 /boot512M FATBOOT.bin, kernel, dtb, boot.scr, uEnv.txt, uboot.env
p2 root_a2Gsquashfs, RAUC slot A
p3 root_b2Gempty, RAUC slot B
p4 /data512M ext4persistent overlay upper (/etc, /opt/ntl); never updated
p5 /homeext4growable (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.