docs: present-state build and update docs; fix the ring-size arithmetic

- kas/README.md: the real-time rationale rests on the feeder's bounded
  queue depth, not ring size; the ring is 16 MiB (~84 s at 200 kHz,
  ~28 min at the 10 kHz cloud tick), a capacity for cloud-mode preload.
- BUILD.md, kas config, release checklist, cold-build workflow: only
  forgefirm and meta-openglow (branch scarthgap) are cloned as
  siblings; every ForgeFIRM source repo is fetched by pinned SRCREV.
- UPDATE-SYSTEM.md reads as the present-state design: the cloud-mode
  compatibility baseline is the cloud client's configured firmware
  version, not release metadata; decisions and open items listed
  plainly.
- README.md states what GRBL mode still needs the Glowforge service
  for (camera-referenced homing) and what runs without it.
- BRINGUP.md: generic build-host and fwup-lab references, the retained
  reproductions of the no-fire drill, the System tab.
- LIGHTBURN.md: the arm-window timeouts are machine settings.
- forgefirm-image.bb describes forgectrl as the machine-services
  daemon and points at the right backlog entry.
- American spelling throughout.
This commit is contained in:
ScottW514
2026-08-15 06:12:51 -04:00
parent ff117c4f6b
commit 0a05b6b114
10 changed files with 141 additions and 142 deletions
+30 -22
View File
@@ -728,16 +728,16 @@ item 8.
at startup regardless), and `vs-supply = <&reg_3p3v>` on the lm75
node (was the last queued cosmetic "dummy regulator" probe line
besides the two SoC USB PHYs). Nothing else queued.
- **Build host**: WSL2 distro `forge-yocto`, tree at
`~/dev/openglow-forgefirm`. `~/src-sync.sh` rsyncs the Windows repos in
(includes `python3-gfhardware` and `grblHAL-glowforge`). Build:
`cd ~/dev/openglow-forgefirm/forgefirm && kas shell
kas/forgefirm-glowforge.yml -c 'bitbake forgefirm-image
forgefirm-image-dev'`. Artifacts:
- **Build host**: a Linux build environment (a WSL2 distro works)
holding the `forgefirm` + `meta-openglow` sibling checkout (`BUILD.md`);
the ForgeFIRM source repos are fetched by pinned `SRCREV`. Build:
`cd forgefirm && kas shell kas/forgefirm-glowforge.yml -c 'bitbake
forgefirm-image forgefirm-image-dev'`. Artifacts:
`forgefirm/build/tmp/deploy/images/glowforge/`.
- **fwup lab (host)**: `~/fwup-lab/bin/` holds host-built `fwup-0.14.2`
(factory-era) and `fwup-v1.16.0`; `~/fwup-lab/devkeys/fwup-key.{priv,pub}`
is the DEV signing keypair (`fwup-key-raw.pub` = raw 32-byte form —
- **fwup lab (host)**: a host directory (`<fwup-lab>` below) holds
host-built `fwup-0.14.2` (factory-era) and `fwup-v1.16.0` under `bin/`
and the DEV signing keypair `devkeys/fwup-key.{priv,pub}`
(`fwup-key-raw.pub` = raw 32-byte form —
what fwup 0.14.2 expects; 1.x reads both). Cross-version compat is
proven both ways (modern-packed signed archives apply with 0.14.2;
modern fwup verifies+applies the factory .fw — signer key
@@ -746,12 +746,12 @@ item 8.
release key is held offline by the operator** — the installer embeds
its public key, so releases sign with that key only.
Pack releases with `scripts/mkfw.sh`; the full pipeline is
`scripts/release.sh`, invoked on this host as:
`FWUP=~/fwup-lab/bin/fwup-v1.16.0 FWUP_COMPAT=~/fwup-lab/bin/fwup-0.14.2
FORGEFIRM_DEV_KEY=~/fwup-lab/devkeys/fwup-key.priv
`scripts/release.sh`, invoked as:
`FWUP=<fwup-lab>/bin/fwup-v1.16.0 FWUP_COMPAT=<fwup-lab>/bin/fwup-0.14.2
FORGEFIRM_DEV_KEY=<fwup-lab>/devkeys/fwup-key.priv
FORGEFIRM_SIGNING_KEY=<release key> RELEASE_STAGING_DIR=<dir>
./scripts/release.sh <version>` (gh for the publish step lives on the
Windows side; release.sh prints the exact command).
./scripts/release.sh <version>` (the publish step needs an
authenticated `gh`; release.sh prints the exact command).
- **Shell gotchas** (cost real time): PowerShell mangles embedded double
quotes in git-commit here-strings (avoid `"` in messages); `wsl -- bash
-c '...'` eats `$VAR` expansions (use script files run via PowerShell,
@@ -759,7 +759,7 @@ item 8.
## Running the controller (grblHAL-glowforge on the board)
Source: `C:\dev\openglow-forgefirm\grblHAL-glowforge` — the **canonical
Source: the `grblHAL-glowforge` sibling repo — the **canonical
grblHAL driver repo** (github.com/ScottW514/grblHAL-glowforge, branch
`main`): core as a submodule at `src/grbl` (→ ScottW514/core fork, branch
`forgefirm` = **upstream master + the step_us_min buffer fix pending
@@ -783,9 +783,11 @@ and maps step events to pulse bytes; a SCHED_FIFO shipper feeds
for interrupt masking. `GFSINK` unset = null-sink mode (full engine, no
hardware I/O — host testing).
1. Build: `wsl -d forge-yocto -- bash <repo>/forgefirm/scripts/bench/build-glowforge.sh`
(from PowerShell). Produces `build-arm/grblHAL_glowforge` in the WSL
tree (`-O1 -g`; machine constants live in `src/boards/glowforge.h`,
1. Build: `bash <repo>/forgefirm/scripts/bench/build-glowforge.sh` in
the build environment (from Windows, launch it through the WSL
distro from PowerShell — Git Bash mangles `/mnt/c` paths). Produces
`build-arm/grblHAL_glowforge` in the checkout (`-O1 -g`; machine
constants live in `src/boards/glowforge.h`,
force-included into the core: 53.333 µsteps/mm XY @ ×8, 2.832
half-steps/mm Z, 0.417" Z travel, 12000 mm/min max, 700/590 mm/s²
accel — factory-derived, see `puls_profile.py`).
@@ -843,7 +845,7 @@ jogs, $0 min 35.5 intact, $H rejected ($22=0); forgectrl streams
## The machine-services daemon (forgectrl, port 8080)
Source: `C:\dev\openglow-forgefirm\forgectrl` — the **canonical repo**
Source: the `forgectrl` sibling repo — the **canonical repo**
(github.com/ScottW514/forgectrl, branch `main`, MIT). forgectrl is the
ForgeFIRM machine-services daemon: **controller-mode supervision** (it
spawns exactly one of grblHAL / gfcloud as a direct child, respawns on
@@ -872,7 +874,9 @@ serves it all, including both OV5648 cameras as MJPEG over the
mainline imx-media pipeline:
- `GET /` — the tabbed machine control panel (Status / Machine /
GF Cloud / GRBL / Diagnostics; ui.c): status page with the
GF Cloud / GRBL / Diagnostics / System; ui.c — System carries the
A/B slot selection, ForgeFIRM updates, image install/restore, the
wireless regulatory region, and reboot): status page with the
controller-mode selector (live switch through the supervisor; the
setting persists for boot), the operational dashboard, a scaled lid snapshot +
on-demand live stream, and the settings forms for display units,
@@ -1389,7 +1393,11 @@ accordingly ("Automatic — AP country, else World").
insertions), and 534 dark steps after the last fire bit = the
entire G0 return.
- **On-board no-fire verification 15/15 PASS** (chain unarmed,
nobody at the button; `laser_arm_test.py` drill): latch locked
nobody at the button; the drill script was a bench one-off and
is not retained — the arm-window state machine is reproduced
host-side by `scripts/bench/laser_lifecycle_test.py` and grblHAL's
`tests/laser_arm_test.c`, and the latch readbacks on hardware by
`gate_a_kernel_drills.py` and `live_fire_drills.py`): latch locked
at idle and through jogs (interlock_circuit 13), M4 → prompt +
latch unlocked (5) + button LED white + run fans forced +
status served during the wait, soft-reset abort relocks + LED
@@ -1590,7 +1598,7 @@ accordingly ("Automatic — AP country, else World").
equilibrates near ambient and the heater cannot reach a
cutting-session loop temperature — 100 % duty drives the
downstream sensor past 50 °C in 30 s while the bulk barely
moves). Behaviour at 27–32 °C baselines, and under real laser
moves). Behavior at 27–32 °C baselines, and under real laser
heating, must be characterized at first light. Physics argues
the dependence is weak — with forced flow ΔT = P/(ṁ·c), which
carries no absolute-temperature term — but that is reasoning,
+3 -1
View File
@@ -38,7 +38,9 @@ The laser fires only inside an operator-armed window:
`M2`/`M30`), when the sender connection changes, or after
`laser_disarm_s` (default 60 s) with the spindle off — counting even
while a job sits paused in Hold or with the lid open; the next job
prompts again.
prompts again. Both timeouts are machine settings (keys in
`/data/forgefirm.conf`, set through the control panel's settings API);
the defaults suit normal use.
- S-value scale: `$30` defaults to 1000, so set LightBurn's S-max to
1000. 100 % power = S1000. Use M4 (variable/dynamic) mode for cuts
and engraves.
+52 -62
View File
@@ -1,11 +1,12 @@
# ForgeFIRM install, update & recovery system — implementation plan
# ForgeFIRM install, update & recovery system
Phased plan for moving ForgeFIRM from the legacy carve-out-a-partition
install to the factory's own A/B slot scheme, with signed `.fw`
packaging, a GUI update manager, factory restore, and a refreshed
recovery image. The measured ground truth this builds on (eMMC layout,
boot0/boot1 maps, saved-env location, factory `.fw`/updater internals)
is in `BRINGUP.md` → "eMMC boot & recovery architecture".
Design and contracts of the ForgeFIRM install/update/recovery system:
the factory's own A/B slot scheme, signed `.fw` packaging, the GUI
update manager, factory restore, and the recovery image. The system is
described in the implementation units ("phases") it is built from; the
bench status of each lives in `BRINGUP.md`, as does the measured ground
truth this rests on (eMMC layout, boot0/boot1 maps, saved-env location,
factory `.fw`/updater internals — "eMMC boot & recovery architecture").
## Settled decisions
@@ -152,15 +153,14 @@ demonstrably untouched.*
- One version source: `FORGEFIRM_RELEASE` = git tag =
`/etc/forgefirm-version` = `.fw` meta-version; the script enforces
agreement.
- `tested_against_gf`: pinned release metadata naming the Glowforge
service/firmware version this release validated optional cloud mode
against. `release.sh` sets it beside `FORGEFIRM_RELEASE` and writes
it into the `/etc/forgefirm-version` companion and the `.fw` fwup
meta. It is deliberately distinct from the version cloud mode
advertises to the service; forgectrl reads it to warn when the live
Glowforge service has advanced past the tested version (cloud mode
may break), and falls back to the advertised version on images that
predate the field.
- Cloud-mode compatibility baseline: the cloud client's connect-time
probe records `{latest_gf_version, tested_against_gf}` to
`/data/forgefirm/gf-latest.json`, and forgectrl's panel warns when
the live Glowforge service has moved past the tested version (cloud
mode may break). `tested_against_gf` is the cloud client's configured
firmware version (`FACTORY_FIRMWARE.FW_VERSION`, the same value it
advertises to the service); it is **not** release metadata — neither
`release.sh` nor the `.fw` meta carries such a field.
- `release.sh --dev` packs a **dev-key-signed** `forgefirm-dev.fw`
from the release rootfs for the GUI upload path (decides open
question 4: dev archives are signed with the dev key, never
@@ -170,7 +170,7 @@ demonstrably untouched.*
forgectrl (minutes, no Yocto); optional `workflow_dispatch`
cold-Yocto reproducibility build whose only product is a checksum.
## Phase 4 — forgectrl update manager (GUI) — IMPLEMENTED
## Phase 4 — forgectrl update manager (GUI)
Endpoints in `forgectrl/src/update.c`, driven from the panel's System
tab; trust anchors in `/etc/forgefirm/keys` (`forgefirm-keys` recipe:
@@ -183,17 +183,11 @@ refuse the booted root slot, verify signature before writing, and
re-verify the written filesystem. `GET /slots` inventory, `POST /boot`
(probe-gated), `POST /update/{check,download,apply,upload}`,
`POST /restore/factory` (archive md5 checked), `POST /system/reboot`.
**Bench-verified end-to-end 2026-08-08** (slot b as scratch, no
reboots): production-signed upload classified `forgefirm` and applied
(`signed:true`); dev-signed classified `unsigned`, apply refused until
`confirm_unsigned=1`; factory-2022 restore md5-verified and written;
boot-select flipped and reverted without reboot; path-traversal /
bogus-target / booted-root-slot writes all refused; `/slots` inventory
matched the physical layout. Original design notes below.
Every state-changing call is behind forgectrl's auth layer (bearer
token + origin checks; unsigned installs additionally require the
physical button held).
Backend endpoints + a panel page (OpenGlow visual identity):
Functions of the panel page:
- **Inventory**: slot contents (Phase 1 probe), current/next boot
selection, archive presence/version.
@@ -218,7 +212,7 @@ Backend endpoints + a panel page (OpenGlow visual identity):
*Exit: full loop on the bench — GUI upgrade, rollback via boot
selector, factory restore and return — without touching a shell.*
## Phase 5 — recovery refresh (reserved; build after 0–4 land)
## Phase 5 — recovery refresh (not yet built)
- **v1 scope**: replace only the boot0 recovery squashfs (boot1 `/usr`
only if needed). Never write below offset 0xC0000 in boot0 — U-Boot
@@ -252,42 +246,38 @@ selector, factory restore and return — without touching a shell.*
`factory-rootfs-<ver>.img.gz`, `boot0.img`, `boot1.img`,
`manifest` (slot versions, dates, checksums).
## Open questions / decision gates
## Decisions
1. **RESOLVED** (Phase 0): uEnv.txt keeps its `mmcargs` override with
`root=${mmcroot}` — slot-agnostic, hardware-verified.
2. **RESOLVED** (Phase 0): modern-fwup-packed signed archives apply
with the factory 0.14.2 binary (raw 32-byte pubkey form); no
shipped fwup needed on the factory side.
3. **RESOLVED** (Phase 3): size gates live in two layers — bitbake
fails past the 200 MiB slot; release.sh warns ≥ 170 MiB and fails
≥ 195 MiB.
4. **RESOLVED** (Phase 3): dev archives are always signed with the
dedicated dev key (`release.sh --dev`), never unsigned.
8. **RESOLVED** — production signing-key ceremony executed: key
generated on the build host (never in the repo, CI, or
cloud-synced plaintext; the build-host copy is the only online
copy, offline backups held by the operator), public key embedded
in the installer, and the chain verified: production-signed
archives verify with fwup 1.16 and the factory's 0.14.2 (raw
pubkey form); dev-signed archives are rejected. Custody optimizes
against compromise over loss: loss means users re-run a fresh
installer; compromise means attacker-signed firmware on fielded
machines.
5. Periodic GUI update check default-on vs opt-in (it pings the GitHub
API; proposal: on by default, apply always manual, config switch to
disable).
6. Recovery kernel modules: carried from the factory image vs rebuilt
from GPL source (Phase 5 gate).
7. U-Boot bootcount/auto-revert: **out of scope** — the recovery
ladder covers bad flips; revisit only if field incidents say
otherwise.
- uEnv.txt keeps its `mmcargs` override with `root=${mmcroot}` — the
image is slot-agnostic, steered only by the saved env.
- Modern-fwup-packed signed archives apply with the factory 0.14.2
binary (raw 32-byte pubkey form); no shipped fwup is needed on the
factory side.
- Size gates live in two layers: bitbake fails past the 200 MiB slot;
`release.sh` warns ≥ 170 MiB and fails ≥ 195 MiB.
- Dev archives are always signed with the dedicated dev key
(`release.sh --dev`), never unsigned.
- Production signing key: held offline by the operator (never in the
repo, CI, or cloud-synced plaintext), public key embedded in the
installer. Production-signed archives verify with fwup 1.16 and the
factory's 0.14.2 (raw pubkey form); dev-signed archives are rejected.
Custody optimizes against compromise over loss: loss means users
re-run a fresh installer; compromise means attacker-signed firmware
on fielded machines.
- U-Boot bootcount/auto-revert is out of scope — the recovery ladder
covers bad flips.
## Sequencing
## Open items
0 → 1 → 2 + 2b → 3 → 4 → 5, strictly: everything after Phase 0 assumes
- Periodic GUI update check default-on vs opt-in (it pings GitHub;
proposal: on by default, apply always manual, config switch to
disable).
- Recovery kernel modules: carried from the factory image vs rebuilt
from GPL source (Phase 5 gate).
## Dependencies between the phases
0 → 1 → 2 + 2b → 3 → 4 → 5: everything after Phase 0 assumes
slot-agnostic images and working `.fw` round-trips; the GUI (4) reuses
the probe (1) and pipeline (3); recovery (5) is an independent
mini-project once the slot scheme is provenly stable. First
user-visible milestone is after Phase 2: a converted machine with
instant offline factory switchback.
mini-project on top of the stable slot scheme.