A component pin bump counted as a platform change: the layer content hash in the platform identity covered the recipe carrying the SRCREV, the platform is folded into every acceptance fingerprint, so every image that carried any component update invalidated the whole catalog (dev image 20260816191951: every test domain-changed after a one-line forgectrl bump; the two manifests differ only in platform.layers.meta-forgefirm). The component entry already identifies the pinned source file by file; the pin double-counted it. Component pins now live in <recipe>-pin.inc (SRCREV and the PV that moves with it, nothing else) - forgectrl, grblhal-glowforge and forgefirm-app here, the BSP components in meta-openglow - and forgefirm-image-manifest.bbclass leaves *-pin.inc out of the layer content (FORGEFIRM_MANIFEST_PIN_SUFFIX). Recipe bodies, patches, config fragments, init scripts and third-party pins with no manifest entry stay layer content; a pin written into a recipe body still hashes (the safe direction). manifest-from-tree.py mirrors the rule and reads pins through the recipe's requires; test_tree_manifest.py proves both (pin bump: hash unchanged; recipe body or inline pin: changed). Bitbake resolves the same SRCREV/PV for every pinned recipe. Docs: ACCEPTANCE.md (what layer content is), kas/README.md (the pin files in the push order), BRINGUP.md (the finding and the bench consequence: the first image built with the pin files is itself a platform change, so its campaign is a full one; pin bumps inherit after it). No catalog consequence: nothing in the image's behavior changes; the change is to the acceptance identity computation, proven by the unit tests and the CI lint on the tree manifest.
13 KiB
Building ForgeFIRM with kas
The forgefirm repo is the base of the project: it controls the build, the resulting firmware images land here, and all build/install docs live here. It uses kas to manage Yocto layers and drive the build.
Baseline
| Yocto release | Scarthgap 5.0 LTS |
| Kernel | linux-fslc 6.12 (mainline LTS, from meta-freescale) |
| Machine | glowforge (i.MX6 Solo SOM — Basic/Plus/Pro) |
| Distro | forgefirm |
| Image | forgefirm-image |
Layout
openglow-forgefirm/
├── forgefirm/ ← THIS repo, the base
│ ├── kas/
│ │ ├── forgefirm-glowforge.yml ← build entry point
│ │ └── README.md ← this file
│ ├── meta-forgefirm/ ← the forgefirm layer (this repo)
│ ├── BUILD.md / INSTALL.md / SERIAL.md
│ ├── layers/ ← kas-cloned upstreams (gitignored)
│ ├── build/ ← bitbake output incl. images (gitignored)
│ ├── downloads/ sstate-cache/ ← caches (gitignored)
│ └── .gitignore
└── meta-openglow/ ← Glowforge BSP layers (local sibling checkout)
meta-openglow is referenced as a local sibling (../meta-openglow), so
its in-place edits are what gets built. The commented pinned-remote block in
forgefirm-glowforge.yml makes the forgefirm repo fully self-contained when
flipped on. The source repos the recipes build (kernel-module-glowforge,
grblHAL-glowforge, forgectrl, python3-gfhardware, Glowforge-Utilities)
are fetched by pinned SRCREV and are not needed as local checkouts.
Prerequisites
A Linux build host, or WSL2 on Windows (officially supported by Yocto).
WSL2 note: keep this whole tree on the WSL2 native ext4 filesystem (e.g.
~/dev/openglow-forgefirm), not under/mnt/c/.... The Windows mount breaks case-sensitivity/permissions and is very slow for Yocto. Give the WSL2 VM plenty of RAM and disk in.wslconfig.
pipx install kas # or: pip install kas
Build
Run from the forgefirm repo root so outputs land inside it:
cd forgefirm
kas build kas/forgefirm-glowforge.yml # fetch layers + full build
kas shell kas/forgefirm-glowforge.yml # interactive bitbake environment
kas dump kas/forgefirm-glowforge.yml # print the resolved config
The bootable image lands in build/tmp/deploy/images/glowforge/. Flashing /
dual-boot install steps are in ../INSTALL.md.
Container build (optional, reproducible host)
cd forgefirm
kas-container build kas/forgefirm-glowforge.yml
Pinning exact versions (reproducible builds)
The config tracks the scarthgap branch of each upstream layer. To lock
every layer to an exact commit:
kas lock kas/forgefirm-glowforge.yml # writes kas/forgefirm-glowforge.lock.yml
kas auto-loads the lockfile on subsequent runs. Commit it; refresh deliberately.
Push & release order (source-of-truth sequencing)
The build is only reproducible when recipe pins, layer branches, and the kas config move in the right order. The sequence, with current status:
- Source repos pushed & pinned — DONE. Every source repo
(
kernel-module-glowforge,python3-gfhardware,Glowforge-Utilities,grblHAL-glowforge,forgectrl) is on GitHub and its recipe pins an exactSRCREV— noAUTOREVanywhere. Whenever a source repo changes: push it, then bump the pin deliberately (BSP recipes in meta-openglow, ForgeFIRM components in meta-forgefirm) and re-verify withbitbake -c fetch <recipe>. A component'sSRCREV(and thePVthat moves with it) lives in<recipe>-pin.incnext to the recipe, nothing else goes in that file: the image manifest leaves*-pin.incout of the layer content hash, so a pin bump changes the component's fingerprint and only that (docs/ACCEPTANCE.md) — a pin written into the recipe body still builds, but counts as a platform change and forces a full acceptance campaign. - meta-openglow pushed — DONE. The Scarthgap port lives on
the
scarthgapbranch (Yocto layer convention; the Dunfell-eramasteris untouched). Development continues on the local sibling checkout; push / fast-forwardscarthgapas work lands. - forgefirm pushed with the kas config and a
kas locklockfile pinning the upstream layers (poky, meta-openembedded, meta-freescale, meta-freescale-distro). - At release time:
- flip
meta-openglowinforgefirm-glowforge.ymlfrom the local-sibling block to the pinned-remote block (commented FUTURE block in the file); - refresh
kas lock, tag all repos, and prove self-containment by building from a fresh clone.
- flip
- GitHub release: run
scripts/release.sh <version>on the build host. It gates (version single-source, rootfs-vs-slot size, installer-embedded pubkey vs the signing key, factory-era fwup verification, and the acceptance gate - the committedreleases/v<version>/acceptance.jsonfrom the bench campaign must authorize the built rootfs,docs/ACCEPTANCE.md), builds, packs and signsforgefirm.fw, stages the assets withsha256sums.txt, and prints thegh release createcommand. Assets and their exact names (the installer and the update manager download them verbatim):forgefirm.fw,sha256sums.txt,forgefirm-image-glowforge.rootfs.wic.gz, plusacceptance.jsonandacceptance.md. The release tagv<version>=FORGEFIRM_RELEASE= the rootfs/etc/forgefirm-version= the.fwmeta-version;release.shenforces the agreement.
All recipes fetch their pinned revision from GitHub, so an image build is
reproducible from the repos alone. For fast iteration on a source repo, bump
its pin per iteration, or add a local, untracked externalsrc bbappend
pointing at a working checkout — never commit one, or released images stop
matching the pins.
Scarthgap migration backlog
The kas scaffold + LAYERSERIES_COMPAT bumps let the layers be selected under
Scarthgap, but the legacy (Dunfell/Gatesgarth) layers won't build clean until:
-
Override-syntax migration— DONE. All_append/_prepend/_remove/_${PN}override syntax converted to the colon form acrossmeta-forgefirm,meta-openglow-core, andmeta-glowforge-bsp(22 occurrences). -
Kernel forward-port (4.14 → linux-fslc 6.12.20) — the factory NXP vendor kernel (linux-imx 4.14.98) carried 7 out-of-tree changes; these are re-derived against mainline 6.12 in
meta-glowforge-bsp/recipes-kernel/linux/linux-fslc_%.bbappend(the forward-port landing zone), not re-applied as the 4.14 patches.- Foundation — DONE.
linux-fslc6.12.20 builds forglowforgewith a ported device tree (glowforge.dts+openglow_common.dtsioverlaid intoarch/arm/boot/dts/nxp/imx/, registered via a Makefile patch) and deployszImage+glowforge.dtb. Boot-core + mainline-bound peripherals only. - Free wins — DONE. bus-freq disable dropped (no mainline busfreq);
st,lis2hh12×3 +national,lm75b+ti,wl1805+ gpio keys/leds bind to mainline drivers;reg-userspace-consumerenabled viaglowforge.cfg. (SPI-delay / PWM-prescaler dispositions are under Motion polish below.) - Motion path — DONE and hardware-validated (live-fed pulse stream,
real gantry motion, laser fire). The whole chain forward-ports and
compiles on 6.12:
- EPIT API:
epit_api.cinarch/arm/mach-imx(CONFIG_MXC_EPIT_API), in vmlinux, symbols exported;&epit1/&epit2in the DT. - SDMA-expose: re-created
dma-imx-sdma.h+0003-imx-sdma-*.patch(un-static survivors, re-added the glowforge helpers, custom int-callback hook); expose symbols inModule.symvers. glowforge.ko: ported across many 6.12 API changes (tasklet_hrtimer→soft hrtimer,timer_setup, LED-trigger API,pwm_get,spi_delay/controller,filelock.h, void.remove, 1-arg i2c probe). Compiles + links, 0 undefined symbols; the recipe fetches the module by pinnedSRCREV.- DT:
glowforge,cnc/thermal/pic/headre-added withpwms/pwm-namesphandles;glowforge.dtbcompiles with all motion nodes. Motion polish: PWM prescaler (factory 1001) is obsolete — 6.12pwm-imx27auto-computes the prescaler from the requested period. The PIC inter-word SPI delay (factory 1005) is a hardware-bring-up TODO: 6.12spi-imx.chas noPERIODREG/word_delayprogramming, so re-derive it inspi_imx_setupxfer(writeMX51_ECSPI_PERIODREGfromt->word_delay, guarded to ECSPI) and verify the wait-states on a scope. pic.c keeps the inter-transfer delay meanwhile. The factoryglowforge,imx-pwm-audio(buzzer) driver is not part of ForgeFIRM.
- EPIT API:
- Camera — DONE and hardware-validated. The factory
ov5648_mipi.c(NXP's removedv4l2_int_device/mxc_v4l2_capture) is replaced by the mainlineovti,ov5648subdev + imx6imx-media(IPU CSI)imx6-mipi-csi2receiver. The factory CAM_SEL MIPI switch is modeled with the mainlinevideo-mux(gpio-mux ongpio7 10): both sensors → video-mux →mipi_csi→ IPU CSI. Sensorxvclkis the board's 24 MHz fixed oscillator (matching the factory DTB); avdd/dovdd/dvdd rails are in the DT. Both cameras stream live through forgectrl (MJPEG at 15 fps with VPU JPEG encode, full-resolution snapshots, mux arbitration). HD-unit caveat: the DT lists bothovti,ov5648(5 MP) andovti,ov8856(8 MP) at 0x36 so one image covers both, and the driver matching the chip ID wins — but mainlineov8856expects a 19.2 MHz xvclk and only warns at 24 MHz, and the capture path is written to ov5648's SBGGR8 2592×1944 format set. 8 MP "HD" modules therefore bind but do not capture; adding 24 MHz PLL modes plus a sensor-aware capture path is the open work.
- Foundation — DONE.
-
u-boot — DONE. The
glowforgeu-boot is a standaloneu-boot_2020.01.bb(Scarthgap's poky has no u-boot 2020.01 base recipe to extend). It reuses poky'su-boot-common.inc/u-boot.inc, pinsSRCREVto the upstream v2020.01 tag with the matchingLicenses/READMEmd5, and overlays the glowforge board support + arch-Kconfig patch. Builds clean under Scarthgap (GCC 13, no source fixes) and deploysu-boot-glowforge.imx. Remaining: movefw_printenv/fw_setenvfromu-boot-fw-utilstolibubootenv(PREFERRED_PROVIDER_u-boot-fw-utilsinglowforge.inc) when the rootfs needs them. -
Device tree — DONE. The
glowforge.dtsis validated against the linux-fslc 6.12 bindings and against the running board (motion, safety readbacks, cameras, sensors all bind and work). -
Real-time strategy — decided. The kernel runs
CONFIG_PREEMPT=y(factory behavior;imx_v6_v7_defconfigalone gives onlyPREEMPT_VOLUNTARY). PREEMPT_RT is not selectable on arm32 6.12 (noARCH_SUPPORTS_RT) and is not needed for the pulse feeder. The argument is about queue depth, not ring size: the ring drains at 1 byte per EPIT tick (≤200 KB/s even at the 200 kHz ceiling), so the live feeder's bounded queue depth of ~150 ms — a few KB in flight — already rides out worst-case scheduling latency with orders of magnitude to spare (measured: 0.2 ms worst write latency under full CPU + I/O load; the underrun bench ran 100 kHz for 120 s with zero underruns). The ring itself is 16 MiB (thering_mbmodule parameter, backed by the 16 MiB reserved pool): ~84 s of stream at 200 kHz, ~28 min at the 10 kHz cloud-mode tick — a capacity that matters for the whole-job preload of cloud mode, not for latency. Bounded queue depth +SCHED_FIFOfor the feeder is the design; revisit RT only if the underrun bench ever contradicts this arithmetic. -
gfui-client → forgectrl — DONE. The stock
gfui-clientis excluded fromforgefirm-image(IMAGE_INSTALL:remove = "gfui-client"inmeta-forgefirm/recipes-forgefirm/images/forgefirm-image.bb). Its slot is filled byforgectrl(github.com/ScottW514/forgectrl — the machine-services daemon: web control panel, cameras, telemetry, settings, diagnostics, cooling engine, updates, and controller-mode supervision) plus the two controllers it supervises,grblhal-glowforge(Grbl over TCP:23) andgfcloud(the optional Glowforge web-service client, off unless selected).
Image status: forgefirm-image builds end-to-end on the forward-ported
stack and deploys forgefirm-image-glowforge.rootfs.wic.gz (+ zImage,
glowforge.dtb, u-boot-glowforge.imx) under build/tmp/deploy/images/glowforge/.
Build-time prerequisites baked into the config: ACCEPT_FSL_EULA = "1" (NXP
firmware-imx — the image also installs firmware-imx-lic so the EULA text
ships beside the blobs) and the kernel default in glowforge.conf. Every
LICENSE string in the layers (meta-forgefirm, meta-glowforge-bsp,
meta-openglow-core) is SPDX, and the recipes for third-party components
that carry more than one license (wlconf, python3-gfhardware) declare
each of them with a checksum on its license text. The stack
is hardware-validated end to end: motion timing, the laser and safety chain,
the camera pipeline, both controller modes, and the A/B install path.