mirror of
https://github.com/openglow-org/forgefirm.git
synced 2026-09-27 16:51:12 -07:00
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:
+30
-22
@@ -728,16 +728,16 @@ item 8.
|
||||
at startup regardless), and `vs-supply = <®_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
@@ -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
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user