docs: kas README is build procedure; status moves to BRINGUP and the log

kas/README.md keeps the build, lockfile, pin and release procedure and a
short build-time facts list; its push/release checklist and Scarthgap
migration backlog are archived verbatim in CAMPAIGN-LOG ("Superseded
status notes"). The real-time argument (PREEMPT, why PREEMPT_RT is not
selectable or needed) joins the BRINGUP hardware facts bank next to the
ring measurements it rests on; the camera pointers go to the 0011-0013
patch headers; the image recipe's gfui-client comment drops its README
pointer.

Docs and a recipe comment only; no catalog consequence.
This commit is contained in:
ScottW514
2026-08-24 14:58:09 -04:00
parent 1e059bb90c
commit dd6f459d7b
5 changed files with 299 additions and 203 deletions
+3 -2
View File
@@ -95,8 +95,9 @@ The control board is common to Glowforge Basic, Plus, and Pro. The 5 MP
(OV5648) camera modules are fully supported and hardware-validated. The 8 MP
(OV8856) modules found in "HD" units have a complete capture path — the kernel
patches, device tree and sensor-aware capture profile they need are all in the
build — but it has never run on an 8 MP machine, so treat it as untested; see
the camera note in [kas/README.md](kas/README.md).
build, but it has never run on an 8 MP machine, so treat it as untested; see
`docs/BRINGUP.md` (Next work, cameras) and the headers of kernel patches
0011-0013 in `meta-glowforge-bsp`.
## Safety
+13 -3
View File
@@ -788,7 +788,15 @@ until `releases/v<version>/acceptance.json` is committed.
counters (`sdma_context`
sc0/1/2 = X/Y/Z steps, sc3 = bytes) match grblHAL exactly. Underrun proof:
100 kHz × 120 s under full load, 150 ms queue, 0.2 ms worst write latency,
zero underruns.
zero underruns. Real time: the kernel runs `CONFIG_PREEMPT=y` (the factory
behavior; `imx_v6_v7_defconfig` alone gives `PREEMPT_VOLUNTARY`).
PREEMPT_RT is not selectable on arm32 6.12 (no `ARCH_SUPPORTS_RT`) and is
not needed: the ring drains at 1 byte per EPIT tick, at most 200 KB/s even at
the 200 kHz ceiling, so the feeder's bounded queue depth of ~150 ms (a few
KB in flight) rides out worst-case scheduling latency with orders of
magnitude to spare. Bounded queue depth plus `SCHED_FIFO` for the feeder is
the design; RT is worth revisiting only if the underrun bench ever
contradicts this arithmetic.
- Byte layout and stream rules: see the UAPI.md feeder contract
(authoritative).
- **Z**: bit 6 SET = lens UP = +Z (hardware-verified). Home = hall trigger at
@@ -1070,7 +1078,8 @@ Open items only. Anything closed is in `CAMPAIGN-LOG.md`.
answer whether the 2-lane RAW8 full-resolution mode locks the D-PHY at
720 Mbps/lane and what exposure/gain the sensor wants; the details, the
reachable-mode reasoning and the factory fallback configuration are in
`kas/README.md` §2. Also unapplied: the factory's **per-unit lens-shading
the headers of kernel patches 0011-0013 (`meta-glowforge-bsp`,
`recipes-kernel/linux/`). Also unapplied: the factory's **per-unit lens-shading
calibration**, an OmniVision LENC register file the factory pushes into the
sensor at every stream start (`load_cam_regs.sh` → a `regs` sysfs attribute
its driver adds; OV8858 `0x58xx` addresses remapped to the OV8856's
@@ -1204,7 +1213,8 @@ Open items only. Anything closed is in `CAMPAIGN-LOG.md`.
Tools that genuinely need a second host (LAN flood, remote auth probes)
stay host-side by design, and the registry marks them so.
13. **Publish.** The kas flip and the first GitHub release, per
`kas/README.md`, once ready to publish. Repoint the core submodule to
`kas/README.md` ("Pins, pushes, and the release flow"), once ready to
publish. Repoint the core submodule to
upstream if the `step_us_min` sizing fix merges.
14. **Update system Phase 5 — recovery refresh.** The remaining phase of
`docs/UPDATE-SYSTEM.md` (a refreshed recovery image in boot0); Phases 0–4
+224
View File
@@ -3885,6 +3885,230 @@ sweep (CLAUDE.md charter, README roadmap, INSTALL/BUILD/kas README)~~
**DONE 2026-08-13.** Remaining: kas flip + first GitHub release per
kas/README.md once ready to publish.
### kas/README.md status sections, as listed 2026-08-24
The two status sections of `kas/README.md` (the push/release checklist with its DONE markers, and the Scarthgap migration backlog), verbatim, before the README was cut back to build procedure and present-state facts; outstanding items are in BRINGUP, and the OV8856 reasoning lives in the 0011-0013 patch headers.
#### 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:
1. **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 exact
`SRCREV` — no `AUTOREV` anywhere. 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 with
`bitbake -c fetch <recipe>`. A component's `SRCREV` (and the `PV` that
moves with it) lives in `<recipe>-pin.inc` next to the recipe, nothing
else goes in that file: the image manifest leaves `*-pin.inc` out 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.
2. **meta-openglow pushed** — **DONE.** The Scarthgap port lives on
the **`scarthgap` branch** (Yocto layer convention; the Dunfell-era `master`
is untouched). Development continues on the local sibling checkout; push /
fast-forward `scarthgap` as work lands.
3. **forgefirm pushed** with the kas config and a `kas lock` lockfile pinning
the upstream layers (poky, meta-openembedded, meta-freescale,
meta-freescale-distro).
4. **At release time**:
- flip `meta-openglow` in `forgefirm-glowforge.yml` from 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**.
5. **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 committed
`releases/v<version>/acceptance.json` from the bench campaign must
authorize the built rootfs, `docs/ACCEPTANCE.md`), builds, packs and
signs `forgefirm.fw`, stages the assets with `sha256sums.txt`, and
prints the `gh release create` command. Assets and their exact names
(the installer and the update manager download them verbatim):
`forgefirm.fw`, `sha256sums.txt`,
`forgefirm-image-glowforge.rootfs.wic.gz`, plus `acceptance.json` and
`acceptance.md`. The release tag
`v<version>` = `FORGEFIRM_RELEASE` = the rootfs `/etc/forgefirm-version`
= the `.fw` meta-version; `release.sh` enforces 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:
1. ~~**Override-syntax migration**~~ — **DONE.** All `_append`/`_prepend`/
`_remove`/`_${PN}` override syntax converted to the colon form across
`meta-forgefirm`, `meta-openglow-core`, and `meta-glowforge-bsp` (22
occurrences).
2. **Kernel forward-port (4.14 to 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-fslc` 6.12.20 builds for `glowforge` with a
ported device tree (`glowforge.dts` + `openglow_common.dtsi` overlaid into
`arch/arm/boot/dts/nxp/imx/`, registered via a Makefile patch) and deploys
`zImage` + `glowforge.dtb`. Boot-core + mainline-bound peripherals only.
- **Free wins: DONE.** bus-freq disable *dropped* (no mainline busfreq);
`st,lis2hh12` x3 + `national,lm75b` + `ti,wl1805` + gpio keys/leds bind to
mainline drivers. The 12 V control rail is a plain always-on fixed
regulator with no userspace consumer node (nothing in the firmware
switches it). The PIC SPI delay and the laser PWM prescaler are layer
patches; see Motion polish below.
- **Config: the board's kernel.** `glowforge.cfg` names this board's driver
set and turns off what `imx_v6_v7_defconfig` adds for the other i.MX
boards, and `conf/machine/glowforge.conf` names the modules and firmware
the rootfs carries (the `kernel-modules` meta-package is not used). Every
line of the fragment is expected to land in the built `.config` as
written; a line that does not means a parent symbol is missing. The
defconfig never names `PM`, the regulator core or ext4 (it had them by
selection from suspend, the PMICs and ext3), so the fragment pins them.
Bench record: BRINGUP item 21, CAMPAIGN-LOG 2026-08-24.
- **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.c` in `arch/arm/mach-imx` (`CONFIG_MXC_EPIT_API`),
in vmlinux, symbols exported; `&epit1/&epit2` in 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 in `Module.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 pinned `SRCREV`.
- **DT**: `glowforge,cnc/thermal/pic/head` re-added with `pwms`/`pwm-names`
phandles; `glowforge.dtb` compiles with all motion nodes.
Motion polish, both carried as layer patches in the bbappend: the laser
PWM prescaler (factory 1001) is patch 0009, `fsl,extra-prescale` on
`pwm-imx27`, set to 13 on `&pwm2`; the cnc engine programs a ~1925 ns
period so the SDMA script writes raw 7-bit power levels into PWMSAR, and
the extra divider stretches the output to ~25 us, the ~40 kHz carrier the
laser PSU expects (mainline `pwm-imx27` alone would run the laser PWM at
~520 kHz, and asking for 25 us directly caps the script's writes at ~8 %
duty). The PIC inter-word SPI delay (factory 1005) is patch 0004: mainline
`spi-imx.c` has no `PERIODREG` support, so the patch programs the ECSPI
sample period from `spi_transfer.delay` (which pic.c sets) and forces
fixed per-word bursts while a delay is requested, so the wait-states land
between words; without it the PIC answers 0x0000 to the ID read. Both are
in every image and hardware-validated (the PIC reads and the laser fires
on them). The factory `glowforge,imx-pwm-audio` (buzzer) driver is not
part of ForgeFIRM.
- **Camera — DONE and hardware-validated.** The factory
`ov5648_mipi.c` (NXP's removed `v4l2_int_device`/`mxc_v4l2_capture`) is
replaced by the mainline `ovti,ov5648` subdev + imx6 `imx-media` (IPU CSI)
+ `imx6-mipi-csi2` receiver. The factory CAM_SEL MIPI switch is modeled
with the mainline `video-mux` (gpio-mux on `gpio7 10`): both sensors →
video-mux → `mipi_csi` → IPU CSI. Sensor `xvclk` is 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 units (8 MP OV8856) — code complete, UNTESTED.** The DT lists both
`ovti,ov5648` (5 MP) and `ovti,ov8856` (8 MP) at 0x36 so one image covers
both, and the driver matching the chip ID wins. Everything the OV8856
needs is in the build: patch 0011 gives it the `get_mbus_config` the
IPU-CSI hard-fails without (the same gap 0006 closes for ov5648), patch
0012 retunes both PLL multipliers for the board's 24 MHz xvclk (mainline's
tables are written for 19.2 MHz, which would run the link 25 % above the
frequency the driver publishes), patch 0013 adds the 2-lane RAW8 modes
(below), the endpoint's `link-frequencies` list carries the driver's whole
2-lane menu (it rejects the endpoint outright if any entry is missing —
the old list omitted 720 MHz, so probe would have failed), and forgectrl
and gfhardware pick geometry and the sensor's control set from whichever
driver bound.
The capture mode is the **full 3264×2448**, reached in RAW8. The
sensor's stock RAW10 full-resolution 2-lane mode asks for 1.44 Gbps/lane
and the i.MX6 CSI-2 D-PHY stops at 1 Gbps (`hsfreq_map` in
`imx6-mipi-csi2.c` ends at 1000 Mbps and `max_mbps_to_hsfreqrange_sel()`
returns `-EINVAL` above it), so `imx6-mipi-csi2` refuses to program it —
but 8-bit samples carry the same frame at half the rate, which puts it on
the 360 MHz link the binned modes already use, at 180 Mpx/s and 15 fps.
Patch 0013 builds those modes from mainline's own 4-lane 3264×2448 and
1632×1224 register lists plus a per-mode delta list: `0x3018` for two
lanes, `0x3031` for 8-bit readout, and double the HTS because half the
lanes carry half a line in the same time. It also makes the sample depth a
mode property, so pixel rate, blanking and exposure ranges follow the mode
instead of a fixed 10. Side effect worth having: the OV8856 path becomes
byte-identical in shape to the OV5648's (8-bit BGGR, one byte per sample),
and 3264 is a multiple of 32 so the NEON superpixel converter applies,
which 1640 did not allow.
The values are the factory firmware's: its own OV8856 driver is RAW8-only
and ships exactly these two resolutions over two lanes with the same
`0x3018`/`0x3031` and the same HTS/VTS pairs. It reaches them through a
different PLL divider chain (`0x0302=0x1e`, `0x0303=0x03`, `0x030f=0x07`,
`0x0312=0x05`, `0x4837=0x58`) that halves the link again to 180 MHz and
the internal SCLK with it — a self-consistent alternative, recorded in the
patch header as the configuration to fall back to if the D-PHY will not
lock at 720 Mbps/lane on real hardware.
Open, and only answerable on an 8 MP machine: whether it streams at all at
720 Mbps/lane, and exposure/gain/white-balance commissioning — the OV8856
driver publishes no red/blue balance controls, so white balance is
uncorrected.
3. **u-boot** — **DONE.** The `glowforge` u-boot is
a standalone `u-boot_2020.01.bb` (Scarthgap's poky has no u-boot 2020.01
base recipe to extend). It reuses poky's
`u-boot-common.inc`/`u-boot.inc`, pins `SRCREV` to the upstream **v2020.01**
tag with the matching `Licenses/README` md5, and overlays the glowforge board
support + arch-Kconfig patch. **Builds clean under Scarthgap (GCC 13, no
source fixes) and deploys `u-boot-glowforge.imx`.** Remaining: move
`fw_printenv`/`fw_setenv` from `u-boot-fw-utils` to `libubootenv`
(`PREFERRED_PROVIDER_u-boot-fw-utils` in `glowforge.inc`) when the rootfs needs
them.
4. **Device tree — DONE.** The `glowforge` `.dts` is validated against the
linux-fslc 6.12 bindings and against the running board (motion, safety
readbacks, cameras, sensors all bind and work).
5. **Real-time strategy — decided.** The kernel runs
`CONFIG_PREEMPT=y` (factory behavior; `imx_v6_v7_defconfig` alone gives only
`PREEMPT_VOLUNTARY`). **PREEMPT_RT is not selectable on arm32 6.12** (no
`ARCH_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 32 MiB (the `ring_mb` module parameter, backed by the 32 MiB
reserved pool, matching the factory ring): ~168 s of stream at 200 kHz,
~56 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_FIFO` for the
feeder is the design; revisit RT only if the underrun bench ever
contradicts this arithmetic.
6. **gfui-client → forgectrl — DONE.** The stock `gfui-client` is excluded
from `forgefirm-image` (`IMAGE_INSTALL:remove = "gfui-client"` in
`meta-forgefirm/recipes-forgefirm/images/forgefirm-image.bb`). Its slot is
filled by `forgectrl` (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) and
`gfcloud` (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.
## Reference notes
### Head-IRQ source validation — the beam-emission hypothesis
+57 -196
View File
@@ -11,7 +11,7 @@ the build.
|---|---|
| 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) |
| Machine | `glowforge` (i.MX6 Solo SOM; Basic/Plus/Pro) |
| Distro | `forgefirm` |
| Image | `forgefirm-image` |
@@ -84,204 +84,65 @@ kas lock kas/forgefirm-glowforge.yml # writes kas/forgefirm-glowforge.lock.
kas auto-loads the lockfile on subsequent runs. Commit it; refresh deliberately.
## Push & release order (source-of-truth sequencing)
## Pins, pushes, and the release flow
The build is only reproducible when recipe pins, layer branches, and the kas
config move in the right order. The sequence, with current status:
The build is reproducible only when recipe pins, layer branches, and the kas
config move in the right order.
1. **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 exact
`SRCREV` — no `AUTOREV` anywhere. 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 with
`bitbake -c fetch <recipe>`. A component's `SRCREV` (and the `PV` that
moves with it) lives in `<recipe>-pin.inc` next to the recipe, nothing
else goes in that file: the image manifest leaves `*-pin.inc` out 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.
2. **meta-openglow pushed** — **DONE.** The Scarthgap port lives on
the **`scarthgap` branch** (Yocto layer convention; the Dunfell-era `master`
is untouched). Development continues on the local sibling checkout; push /
fast-forward `scarthgap` as work lands.
3. **forgefirm pushed** with the kas config and a `kas lock` lockfile pinning
the upstream layers (poky, meta-openembedded, meta-freescale,
meta-freescale-distro).
4. **At release time**:
- flip `meta-openglow` in `forgefirm-glowforge.yml` from 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**.
5. **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 committed
`releases/v<version>/acceptance.json` from the bench campaign must
authorize the built rootfs, `docs/ACCEPTANCE.md`), builds, packs and
signs `forgefirm.fw`, stages the assets with `sha256sums.txt`, and
prints the `gh release create` command. Assets and their exact names
(the installer and the update manager download them verbatim):
`forgefirm.fw`, `sha256sums.txt`,
`forgefirm-image-glowforge.rootfs.wic.gz`, plus `acceptance.json` and
`acceptance.md`. The release tag
`v<version>` = `FORGEFIRM_RELEASE` = the rootfs `/etc/forgefirm-version`
= the `.fw` meta-version; `release.sh` enforces the agreement.
- **Every source repo is pinned.** `kernel-module-glowforge`,
`python3-gfhardware`, `Glowforge-Utilities`, `grblHAL-glowforge` and
`forgectrl` are fetched from GitHub at an exact `SRCREV`; there is no
`AUTOREV` anywhere. When a source repo changes: push it, then bump the pin
deliberately (BSP recipes in `meta-openglow`, ForgeFIRM components in
`meta-forgefirm`) and re-verify with `bitbake -c fetch <recipe>`. A
component's `SRCREV` (and the `PV` that moves with it) lives in
`<recipe>-pin.inc` next to the recipe, and nothing else goes in that file:
the image manifest leaves `*-pin.inc` out 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` lives on its `scarthgap` branch** (Yocto layer convention;
the Dunfell-era `master` is untouched). Development happens on the local
sibling checkout; `scarthgap` is pushed as work lands.
- **The upstream layers are locked** by `kas lock` (poky, meta-openembedded,
meta-freescale, meta-freescale-distro); the lockfile is committed and
refreshed deliberately.
- **At release time**: flip `meta-openglow` in `forgefirm-glowforge.yml` from
the local-sibling block to the pinned-remote block (the commented block in
the file), refresh `kas lock`, tag all repos, and prove self-containment by
building from a fresh clone. Then `scripts/release.sh <version>` gates
(version single-source, rootfs-vs-slot size, installer-embedded pubkey vs the
signing key, factory-era fwup verification, and the acceptance gate: the
committed `releases/v<version>/acceptance.json` from the bench campaign must
authorize the built rootfs, `docs/ACCEPTANCE.md`), builds, packs and signs
`forgefirm.fw`, stages the assets with `sha256sums.txt`, and prints the
`gh release create` command. Assets and their exact names (the installer and
the update manager download them verbatim): `forgefirm.fw`,
`sha256sums.txt`, `forgefirm-image-glowforge.rootfs.wic.gz`, plus
`acceptance.json` and `acceptance.md`. The release tag `v<version>` =
`FORGEFIRM_RELEASE` = the rootfs `/etc/forgefirm-version` = the `.fw`
meta-version; `release.sh` enforces 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.
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
## Build-time facts
The kas scaffold + `LAYERSERIES_COMPAT` bumps let the layers be *selected* under
Scarthgap, but the legacy (Dunfell/Gatesgarth) layers won't build clean until:
- `ACCEPT_FSL_EULA = "1"` is set in the kas config: the image carries NXP's
VPU firmware blob, and `firmware-imx-lic` ships the EULA text beside it.
- Every `LICENSE` string in the layers (`meta-forgefirm`, `meta-glowforge-bsp`,
`meta-openglow-core`) is SPDX; recipes for third-party components with more
than one license (`wlconf`, `python3-gfhardware`) declare each with a
checksum on its license text.
- `forgefirm-image-dev` is a strict superset of `forgefirm-image` (the bench
image, `docs/ACCEPTANCE.md`); every build produces both.
- The kernel is `linux-fslc` with the board's device tree, config fragment and
layer patches in `meta-openglow/meta-glowforge-bsp/recipes-kernel/linux/`;
the bbappend header lists the patches and `glowforge.cfg` documents the
config. The bootloader recipe is `u-boot_2020.01.bb` in `recipes-bsp`.
1. ~~**Override-syntax migration**~~ — **DONE.** All `_append`/`_prepend`/
`_remove`/`_${PN}` override syntax converted to the colon form across
`meta-forgefirm`, `meta-openglow-core`, and `meta-glowforge-bsp` (22
occurrences).
2. **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-fslc` 6.12.20 builds for `glowforge` with a
ported device tree (`glowforge.dts` + `openglow_common.dtsi` overlaid into
`arch/arm/boot/dts/nxp/imx/`, registered via a Makefile patch) and deploys
`zImage` + `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-consumer` enabled via `glowforge.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.c` in `arch/arm/mach-imx` (`CONFIG_MXC_EPIT_API`),
in vmlinux, symbols exported; `&epit1/&epit2` in 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 in `Module.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 pinned `SRCREV`.
- **DT**: `glowforge,cnc/thermal/pic/head` re-added with `pwms`/`pwm-names`
phandles; `glowforge.dtb` compiles with all motion nodes.
Motion polish: PWM prescaler (factory 1001) is **obsolete** — 6.12
`pwm-imx27` auto-computes the prescaler from the requested period. The PIC
inter-word SPI delay (factory 1005) is a **hardware-bring-up TODO**: 6.12
`spi-imx.c` has no `PERIODREG`/`word_delay` programming, so re-derive it in
`spi_imx_setupxfer` (write `MX51_ECSPI_PERIODREG` from `t->word_delay`,
guarded to ECSPI) and verify the wait-states on a scope. pic.c keeps the
inter-transfer delay meanwhile. The factory `glowforge,imx-pwm-audio`
(buzzer) driver is not part of ForgeFIRM.
- **Camera — DONE and hardware-validated.** The factory
`ov5648_mipi.c` (NXP's removed `v4l2_int_device`/`mxc_v4l2_capture`) is
replaced by the mainline `ovti,ov5648` subdev + imx6 `imx-media` (IPU CSI)
+ `imx6-mipi-csi2` receiver. The factory CAM_SEL MIPI switch is modeled
with the mainline `video-mux` (gpio-mux on `gpio7 10`): both sensors →
video-mux → `mipi_csi` → IPU CSI. Sensor `xvclk` is 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 units (8 MP OV8856) — code complete, UNTESTED.** The DT lists both
`ovti,ov5648` (5 MP) and `ovti,ov8856` (8 MP) at 0x36 so one image covers
both, and the driver matching the chip ID wins. Everything the OV8856
needs is in the build: patch 0011 gives it the `get_mbus_config` the
IPU-CSI hard-fails without (the same gap 0006 closes for ov5648), patch
0012 retunes both PLL multipliers for the board's 24 MHz xvclk (mainline's
tables are written for 19.2 MHz, which would run the link 25 % above the
frequency the driver publishes), patch 0013 adds the 2-lane RAW8 modes
(below), the endpoint's `link-frequencies` list carries the driver's whole
2-lane menu (it rejects the endpoint outright if any entry is missing —
the old list omitted 720 MHz, so probe would have failed), and forgectrl
and gfhardware pick geometry and the sensor's control set from whichever
driver bound.
The capture mode is the **full 3264×2448**, reached in RAW8. The
sensor's stock RAW10 full-resolution 2-lane mode asks for 1.44 Gbps/lane
and the i.MX6 CSI-2 D-PHY stops at 1 Gbps (`hsfreq_map` in
`imx6-mipi-csi2.c` ends at 1000 Mbps and `max_mbps_to_hsfreqrange_sel()`
returns `-EINVAL` above it), so `imx6-mipi-csi2` refuses to program it —
but 8-bit samples carry the same frame at half the rate, which puts it on
the 360 MHz link the binned modes already use, at 180 Mpx/s and 15 fps.
Patch 0013 builds those modes from mainline's own 4-lane 3264×2448 and
1632×1224 register lists plus a per-mode delta list: `0x3018` for two
lanes, `0x3031` for 8-bit readout, and double the HTS because half the
lanes carry half a line in the same time. It also makes the sample depth a
mode property, so pixel rate, blanking and exposure ranges follow the mode
instead of a fixed 10. Side effect worth having: the OV8856 path becomes
byte-identical in shape to the OV5648's (8-bit BGGR, one byte per sample),
and 3264 is a multiple of 32 so the NEON superpixel converter applies,
which 1640 did not allow.
The values are the factory firmware's: its own OV8856 driver is RAW8-only
and ships exactly these two resolutions over two lanes with the same
`0x3018`/`0x3031` and the same HTS/VTS pairs. It reaches them through a
different PLL divider chain (`0x0302=0x1e`, `0x0303=0x03`, `0x030f=0x07`,
`0x0312=0x05`, `0x4837=0x58`) that halves the link again to 180 MHz and
the internal SCLK with it — a self-consistent alternative, recorded in the
patch header as the configuration to fall back to if the D-PHY will not
lock at 720 Mbps/lane on real hardware.
Open, and only answerable on an 8 MP machine: whether it streams at all at
720 Mbps/lane, and exposure/gain/white-balance commissioning — the OV8856
driver publishes no red/blue balance controls, so white balance is
uncorrected.
3. **u-boot** — **DONE.** The `glowforge` u-boot is
a standalone `u-boot_2020.01.bb` (Scarthgap's poky has no u-boot 2020.01
base recipe to extend). It reuses poky's
`u-boot-common.inc`/`u-boot.inc`, pins `SRCREV` to the upstream **v2020.01**
tag with the matching `Licenses/README` md5, and overlays the glowforge board
support + arch-Kconfig patch. **Builds clean under Scarthgap (GCC 13, no
source fixes) and deploys `u-boot-glowforge.imx`.** Remaining: move
`fw_printenv`/`fw_setenv` from `u-boot-fw-utils` to `libubootenv`
(`PREFERRED_PROVIDER_u-boot-fw-utils` in `glowforge.inc`) when the rootfs needs
them.
4. **Device tree — DONE.** The `glowforge` `.dts` is validated against the
linux-fslc 6.12 bindings and against the running board (motion, safety
readbacks, cameras, sensors all bind and work).
5. **Real-time strategy — decided.** The kernel runs
`CONFIG_PREEMPT=y` (factory behavior; `imx_v6_v7_defconfig` alone gives only
`PREEMPT_VOLUNTARY`). **PREEMPT_RT is not selectable on arm32 6.12** (no
`ARCH_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 32 MiB (the `ring_mb` module parameter, backed by the 32 MiB
reserved pool, matching the factory ring): ~168 s of stream at 200 kHz,
~56 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_FIFO` for the
feeder is the design; revisit RT only if the underrun bench ever
contradicts this arithmetic.
6. **gfui-client → forgectrl — DONE.** The stock `gfui-client` is excluded
from `forgefirm-image` (`IMAGE_INSTALL:remove = "gfui-client"` in
`meta-forgefirm/recipes-forgefirm/images/forgefirm-image.bb`). Its slot is
filled by `forgectrl` (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) and
`gfcloud` (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.
Design facts (the pulse ring, real-time choices, hardware measurements) are in
`docs/BRINGUP.md` ("Hardware facts bank") and
`kernel-module-glowforge/UAPI.md`; bench status and open work are
`docs/BRINGUP.md`; the dated record is `docs/CAMPAIGN-LOG.md`.
@@ -10,7 +10,7 @@ DESCRIPTION = "OpenGlow/ForgeFIRM image for Glowforge"
# (gfui-client connects to Glowforge's servers). Removing it here (override
# only; the shared glowforge-image base is untouched) also sidesteps its
# do_package failure. Its role is filled locally by forgectrl and the
# controllers below (kas/README.md backlog #6).
# controllers below.
IMAGE_INSTALL:remove = "gfui-client"
# grblhal-glowforge: the grblHAL motion controller (Grbl over TCP:23).
@@ -46,7 +46,7 @@ IMAGE_INSTALL:append = " libegl-mesa libgles2-mesa libgbm mesa-megadriver"
IMAGE_INSTALL:append = " firmware-imx-lic"
# The release rootfs must fit a 200 MiB factory eMMC slot (409600 blocks).
# Sizing: content + 40 MiB working space, hard-capped at the slot size —
# Sizing: content + 40 MiB working space, hard-capped at the slot size:
# the build fails rather than emit an unflashable image. The raw ext4 is
# deployed alongside the wic; scripts/mkfw.sh packs it into the signed
# .fw release artifact.