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
+4 -10
View File
@@ -30,10 +30,10 @@ jobs:
with: with:
path: forgefirm path: forgefirm
# The kas config references meta-openglow as a local sibling, and # The kas config references meta-openglow as a local sibling
# meta-openglow's kernel-module bbappend uses an externalsrc sibling # (kas/README.md "Push & release order" step 4 flips it to the
# (both by design during active BSP development - kas/README.md # pinned-remote block at release time). Every source repo the recipes
# "Push & release order" step 4 flips these at release time). # build is fetched by pinned SRCREV; no other sibling is needed.
- name: Checkout meta-openglow (sibling) - name: Checkout meta-openglow (sibling)
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
@@ -41,12 +41,6 @@ jobs:
ref: scarthgap ref: scarthgap
path: meta-openglow path: meta-openglow
- name: Checkout kernel-module-glowforge (sibling)
uses: actions/checkout@v4
with:
repository: ScottW514/kernel-module-glowforge
path: kernel-module-glowforge
- name: Host dependencies - name: Host dependencies
run: | run: |
sudo apt-get update -qq sudo apt-get update -qq
+9 -7
View File
@@ -26,20 +26,21 @@ Do not build as root (the Yocto sanity checks refuse it).
## Get the sources ## Get the sources
Clone the repos as siblings (the kas config references `meta-openglow` at Clone the two repos as siblings (the kas config references `meta-openglow`,
`../meta-openglow` while it is under active Scarthgap migration): branch `scarthgap`, at `../meta-openglow`; kas fetches the upstream Yocto
layers itself, and every ForgeFIRM source repo — the kernel module, the
controller, the daemon, the cloud apps — is fetched by its recipe at a pinned
revision):
```console ```console
git clone https://github.com/ScottW514/forgefirm.git git clone https://github.com/ScottW514/forgefirm.git
git clone https://github.com/ScottW514/meta-openglow.git git clone -b scarthgap https://github.com/ScottW514/meta-openglow.git
git clone https://github.com/ScottW514/kernel-module-glowforge.git
``` ```
``` ```
openglow-forgefirm/ openglow-forgefirm/
├── forgefirm/ ← base repo, build runs here ├── forgefirm/ ← base repo, build runs here
├── meta-openglow/ └── meta-openglow/
└── kernel-module-glowforge/
``` ```
## Build the image ## Build the image
@@ -86,5 +87,6 @@ cd build/tmp/deploy/images/glowforge
sudo zcat forgefirm-image-glowforge.rootfs.wic.gz | dd of=/dev/sdX bs=1M sudo zcat forgefirm-image-glowforge.rootfs.wic.gz | dd of=/dev/sdX bs=1M
``` ```
To install onto the factory eMMC (dual-boot with the Glowforge firmware), see To install onto the factory eMMC (into the unused A/B slot, with the factory
firmware archived first — one OS runs at a time), see
[`INSTALL.md`](INSTALL.md). [`INSTALL.md`](INSTALL.md).
+6 -3
View File
@@ -24,7 +24,9 @@ machine is idle:**
for each job. for each job.
* **Cloud mode** — the machine presents itself as a stock Glowforge to the * **Cloud mode** — the machine presents itself as a stock Glowforge to the
Glowforge web service, so the phone and web apps work as they always did. Glowforge web service, so the phone and web apps work as they always did.
Optional, and off by default; nothing about GRBL mode needs it. Optional, and off by default. GRBL mode jogs and cuts without it; the one
GRBL-mode function that still reaches the Glowforge service is
camera-referenced homing (below), until limit-switch homing lands.
**Around both modes:** **Around both modes:**
@@ -34,8 +36,9 @@ machine is idle:**
* Both **cameras** as MJPEG streams and full-resolution snapshots — the lid * Both **cameras** as MJPEG streams and full-resolution snapshots — the lid
camera feeds LightBurn's camera overlay directly. camera feeds LightBurn's camera overlay directly.
* **Camera-referenced homing**: `$H` from any sender runs the factory-style * **Camera-referenced homing**: `$H` from any sender runs the factory-style
camera homing cycle through the Glowforge service, and the machine records camera homing cycle through the Glowforge service (a Glowforge account and a
where it is. live service session are required for `$H`; everything else in GRBL mode
runs without them), and the machine records where it is.
* **Installs alongside the factory firmware** in the unused A/B rootfs slot, * **Installs alongside the factory firmware** in the unused A/B rootfs slot,
archiving every factory version first, so the machine can be switched back archiving every factory version first, so the machine can be switched back
to stock at any time without the Glowforge cloud. to stock at any time without the Glowforge cloud.
+30 -22
View File
@@ -728,16 +728,16 @@ item 8.
at startup regardless), and `vs-supply = <&reg_3p3v>` on the lm75 at startup regardless), and `vs-supply = <&reg_3p3v>` on the lm75
node (was the last queued cosmetic "dummy regulator" probe line node (was the last queued cosmetic "dummy regulator" probe line
besides the two SoC USB PHYs). Nothing else queued. besides the two SoC USB PHYs). Nothing else queued.
- **Build host**: WSL2 distro `forge-yocto`, tree at - **Build host**: a Linux build environment (a WSL2 distro works)
`~/dev/openglow-forgefirm`. `~/src-sync.sh` rsyncs the Windows repos in holding the `forgefirm` + `meta-openglow` sibling checkout (`BUILD.md`);
(includes `python3-gfhardware` and `grblHAL-glowforge`). Build: the ForgeFIRM source repos are fetched by pinned `SRCREV`. Build:
`cd ~/dev/openglow-forgefirm/forgefirm && kas shell `cd forgefirm && kas shell kas/forgefirm-glowforge.yml -c 'bitbake
kas/forgefirm-glowforge.yml -c 'bitbake forgefirm-image forgefirm-image forgefirm-image-dev'`. Artifacts:
forgefirm-image-dev'`. Artifacts:
`forgefirm/build/tmp/deploy/images/glowforge/`. `forgefirm/build/tmp/deploy/images/glowforge/`.
- **fwup lab (host)**: `~/fwup-lab/bin/` holds host-built `fwup-0.14.2` - **fwup lab (host)**: a host directory (`<fwup-lab>` below) holds
(factory-era) and `fwup-v1.16.0`; `~/fwup-lab/devkeys/fwup-key.{priv,pub}` host-built `fwup-0.14.2` (factory-era) and `fwup-v1.16.0` under `bin/`
is the DEV signing keypair (`fwup-key-raw.pub` = raw 32-byte form — 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 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; proven both ways (modern-packed signed archives apply with 0.14.2;
modern fwup verifies+applies the factory .fw — signer key 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 release key is held offline by the operator** — the installer embeds
its public key, so releases sign with that key only. its public key, so releases sign with that key only.
Pack releases with `scripts/mkfw.sh`; the full pipeline is Pack releases with `scripts/mkfw.sh`; the full pipeline is
`scripts/release.sh`, invoked on this host as: `scripts/release.sh`, invoked as:
`FWUP=~/fwup-lab/bin/fwup-v1.16.0 FWUP_COMPAT=~/fwup-lab/bin/fwup-0.14.2 `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_DEV_KEY=<fwup-lab>/devkeys/fwup-key.priv
FORGEFIRM_SIGNING_KEY=<release key> RELEASE_STAGING_DIR=<dir> FORGEFIRM_SIGNING_KEY=<release key> RELEASE_STAGING_DIR=<dir>
./scripts/release.sh <version>` (gh for the publish step lives on the ./scripts/release.sh <version>` (the publish step needs an
Windows side; release.sh prints the exact command). authenticated `gh`; release.sh prints the exact command).
- **Shell gotchas** (cost real time): PowerShell mangles embedded double - **Shell gotchas** (cost real time): PowerShell mangles embedded double
quotes in git-commit here-strings (avoid `"` in messages); `wsl -- bash quotes in git-commit here-strings (avoid `"` in messages); `wsl -- bash
-c '...'` eats `$VAR` expansions (use script files run via PowerShell, -c '...'` eats `$VAR` expansions (use script files run via PowerShell,
@@ -759,7 +759,7 @@ item 8.
## Running the controller (grblHAL-glowforge on the board) ## 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 grblHAL driver repo** (github.com/ScottW514/grblHAL-glowforge, branch
`main`): core as a submodule at `src/grbl` (→ ScottW514/core fork, branch `main`): core as a submodule at `src/grbl` (→ ScottW514/core fork, branch
`forgefirm` = **upstream master + the step_us_min buffer fix pending `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 for interrupt masking. `GFSINK` unset = null-sink mode (full engine, no
hardware I/O — host testing). hardware I/O — host testing).
1. Build: `wsl -d forge-yocto -- bash <repo>/forgefirm/scripts/bench/build-glowforge.sh` 1. Build: `bash <repo>/forgefirm/scripts/bench/build-glowforge.sh` in
(from PowerShell). Produces `build-arm/grblHAL_glowforge` in the WSL the build environment (from Windows, launch it through the WSL
tree (`-O1 -g`; machine constants live in `src/boards/glowforge.h`, 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 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² half-steps/mm Z, 0.417" Z travel, 12000 mm/min max, 700/590 mm/s²
accel — factory-derived, see `puls_profile.py`). 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) ## 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 (github.com/ScottW514/forgectrl, branch `main`, MIT). forgectrl is the
ForgeFIRM machine-services daemon: **controller-mode supervision** (it ForgeFIRM machine-services daemon: **controller-mode supervision** (it
spawns exactly one of grblHAL / gfcloud as a direct child, respawns on 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: mainline imx-media pipeline:
- `GET /` — the tabbed machine control panel (Status / Machine / - `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 controller-mode selector (live switch through the supervisor; the
setting persists for boot), the operational dashboard, a scaled lid snapshot + setting persists for boot), the operational dashboard, a scaled lid snapshot +
on-demand live stream, and the settings forms for display units, 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 insertions), and 534 dark steps after the last fire bit = the
entire G0 return. entire G0 return.
- **On-board no-fire verification 15/15 PASS** (chain unarmed, - **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 + at idle and through jogs (interlock_circuit 13), M4 → prompt +
latch unlocked (5) + button LED white + run fans forced + latch unlocked (5) + button LED white + run fans forced +
status served during the wait, soft-reset abort relocks + LED 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 equilibrates near ambient and the heater cannot reach a
cutting-session loop temperature — 100 % duty drives the cutting-session loop temperature — 100 % duty drives the
downstream sensor past 50 °C in 30 s while the bulk barely 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 heating, must be characterized at first light. Physics argues
the dependence is weak — with forced flow ΔT = P/(ṁ·c), which the dependence is weak — with forced flow ΔT = P/(ṁ·c), which
carries no absolute-temperature term — but that is reasoning, 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 `M2`/`M30`), when the sender connection changes, or after
`laser_disarm_s` (default 60 s) with the spindle off — counting even `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 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 - 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 1000. 100 % power = S1000. Use M4 (variable/dynamic) mode for cuts
and engraves. 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 Design and contracts of the ForgeFIRM install/update/recovery system:
install to the factory's own A/B slot scheme, with signed `.fw` the factory's own A/B slot scheme, signed `.fw` packaging, the GUI
packaging, a GUI update manager, factory restore, and a refreshed update manager, factory restore, and the recovery image. The system is
recovery image. The measured ground truth this builds on (eMMC layout, described in the implementation units ("phases") it is built from; the
boot0/boot1 maps, saved-env location, factory `.fw`/updater internals) bench status of each lives in `BRINGUP.md`, as does the measured ground
is in `BRINGUP.md` → "eMMC boot & recovery architecture". truth this rests on (eMMC layout, boot0/boot1 maps, saved-env location,
factory `.fw`/updater internals — "eMMC boot & recovery architecture").
## Settled decisions ## Settled decisions
@@ -152,15 +153,14 @@ demonstrably untouched.*
- One version source: `FORGEFIRM_RELEASE` = git tag = - One version source: `FORGEFIRM_RELEASE` = git tag =
`/etc/forgefirm-version` = `.fw` meta-version; the script enforces `/etc/forgefirm-version` = `.fw` meta-version; the script enforces
agreement. agreement.
- `tested_against_gf`: pinned release metadata naming the Glowforge - Cloud-mode compatibility baseline: the cloud client's connect-time
service/firmware version this release validated optional cloud mode probe records `{latest_gf_version, tested_against_gf}` to
against. `release.sh` sets it beside `FORGEFIRM_RELEASE` and writes `/data/forgefirm/gf-latest.json`, and forgectrl's panel warns when
it into the `/etc/forgefirm-version` companion and the `.fw` fwup the live Glowforge service has moved past the tested version (cloud
meta. It is deliberately distinct from the version cloud mode mode may break). `tested_against_gf` is the cloud client's configured
advertises to the service; forgectrl reads it to warn when the live firmware version (`FACTORY_FIRMWARE.FW_VERSION`, the same value it
Glowforge service has advanced past the tested version (cloud mode advertises to the service); it is **not** release metadata — neither
may break), and falls back to the advertised version on images that `release.sh` nor the `.fw` meta carries such a field.
predate the field.
- `release.sh --dev` packs a **dev-key-signed** `forgefirm-dev.fw` - `release.sh --dev` packs a **dev-key-signed** `forgefirm-dev.fw`
from the release rootfs for the GUI upload path (decides open from the release rootfs for the GUI upload path (decides open
question 4: dev archives are signed with the dev key, never question 4: dev archives are signed with the dev key, never
@@ -170,7 +170,7 @@ demonstrably untouched.*
forgectrl (minutes, no Yocto); optional `workflow_dispatch` forgectrl (minutes, no Yocto); optional `workflow_dispatch`
cold-Yocto reproducibility build whose only product is a checksum. 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 Endpoints in `forgectrl/src/update.c`, driven from the panel's System
tab; trust anchors in `/etc/forgefirm/keys` (`forgefirm-keys` recipe: 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` re-verify the written filesystem. `GET /slots` inventory, `POST /boot`
(probe-gated), `POST /update/{check,download,apply,upload}`, (probe-gated), `POST /update/{check,download,apply,upload}`,
`POST /restore/factory` (archive md5 checked), `POST /system/reboot`. `POST /restore/factory` (archive md5 checked), `POST /system/reboot`.
**Bench-verified end-to-end 2026-08-08** (slot b as scratch, no Every state-changing call is behind forgectrl's auth layer (bearer
reboots): production-signed upload classified `forgefirm` and applied token + origin checks; unsigned installs additionally require the
(`signed:true`); dev-signed classified `unsigned`, apply refused until physical button held).
`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.
Functions of the panel page:
Backend endpoints + a panel page (OpenGlow visual identity):
- **Inventory**: slot contents (Phase 1 probe), current/next boot - **Inventory**: slot contents (Phase 1 probe), current/next boot
selection, archive presence/version. 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 *Exit: full loop on the bench — GUI upgrade, rollback via boot
selector, factory restore and return — without touching a shell.* 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` - **v1 scope**: replace only the boot0 recovery squashfs (boot1 `/usr`
only if needed). Never write below offset 0xC0000 in boot0 — U-Boot 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`, `factory-rootfs-<ver>.img.gz`, `boot0.img`, `boot1.img`,
`manifest` (slot versions, dates, checksums). `manifest` (slot versions, dates, checksums).
## Open questions / decision gates ## Decisions
1. **RESOLVED** (Phase 0): uEnv.txt keeps its `mmcargs` override with - uEnv.txt keeps its `mmcargs` override with `root=${mmcroot}` — the
`root=${mmcroot}` — slot-agnostic, hardware-verified. image is slot-agnostic, steered only by the saved env.
2. **RESOLVED** (Phase 0): modern-fwup-packed signed archives apply - Modern-fwup-packed signed archives apply with the factory 0.14.2
with the factory 0.14.2 binary (raw 32-byte pubkey form); no binary (raw 32-byte pubkey form); no shipped fwup is needed on the
shipped fwup needed on the factory side. factory side.
3. **RESOLVED** (Phase 3): size gates live in two layers — bitbake - Size gates live in two layers: bitbake fails past the 200 MiB slot;
fails past the 200 MiB slot; release.sh warns ≥ 170 MiB and fails `release.sh` warns ≥ 170 MiB and fails ≥ 195 MiB.
≥ 195 MiB. - Dev archives are always signed with the dedicated dev key
4. **RESOLVED** (Phase 3): dev archives are always signed with the (`release.sh --dev`), never unsigned.
dedicated dev key (`release.sh --dev`), never unsigned. - Production signing key: held offline by the operator (never in the
8. **RESOLVED** — production signing-key ceremony executed: key repo, CI, or cloud-synced plaintext), public key embedded in the
generated on the build host (never in the repo, CI, or installer. Production-signed archives verify with fwup 1.16 and the
cloud-synced plaintext; the build-host copy is the only online factory's 0.14.2 (raw pubkey form); dev-signed archives are rejected.
copy, offline backups held by the operator), public key embedded Custody optimizes against compromise over loss: loss means users
in the installer, and the chain verified: production-signed re-run a fresh installer; compromise means attacker-signed firmware
archives verify with fwup 1.16 and the factory's 0.14.2 (raw on fielded machines.
pubkey form); dev-signed archives are rejected. Custody optimizes - U-Boot bootcount/auto-revert is out of scope — the recovery ladder
against compromise over loss: loss means users re-run a fresh covers bad flips.
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.
## 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 slot-agnostic images and working `.fw` round-trips; the GUI (4) reuses
the probe (1) and pipeline (3); recovery (5) is an independent the probe (1) and pipeline (3); recovery (5) is an independent
mini-project once the slot scheme is provenly stable. First mini-project on top of the stable slot scheme.
user-visible milestone is after Phase 2: a converted machine with
instant offline factory switchback.
+24 -19
View File
@@ -29,15 +29,15 @@ openglow-forgefirm/
│ ├── build/ ← bitbake output incl. images (gitignored) │ ├── build/ ← bitbake output incl. images (gitignored)
│ ├── downloads/ sstate-cache/ ← caches (gitignored) │ ├── downloads/ sstate-cache/ ← caches (gitignored)
│ └── .gitignore │ └── .gitignore
├── meta-openglow/ ← Glowforge BSP layers (local sibling, under migration) └── meta-openglow/ ← Glowforge BSP layers (local sibling checkout)
└── kernel-module-glowforge/ ← glowforge.ko sources (pulled by recipe SRC_URI)
``` ```
`meta-openglow` is referenced as a **local sibling** (`../meta-openglow`) while `meta-openglow` is referenced as a **local sibling** (`../meta-openglow`), so
we migrate it to Scarthgap, so its in-place edits are what gets built. Once that its in-place edits are what gets built. The commented pinned-remote block in
migration is committed/pushed, flip it to a kas-cloned + pinned repo (commented `forgefirm-glowforge.yml` makes the forgefirm repo fully self-contained when
block in `forgefirm-glowforge.yml`) and the forgefirm repo becomes fully flipped on. The source repos the recipes build (`kernel-module-glowforge`,
self-contained. `grblHAL-glowforge`, `forgectrl`, `python3-gfhardware`, `Glowforge-Utilities`)
are fetched by pinned `SRCREV` and are not needed as local checkouts.
## Prerequisites ## Prerequisites
@@ -122,7 +122,7 @@ config move in the right order. The sequence, with current status:
All recipes fetch their pinned revision from GitHub, so an image build is 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 reproducible from the repos alone. For fast iteration on a source repo, bump
its pin per iteration, or add a **local, untracked** `externalsrc` bbappend its pin per iteration, or add a **local, untracked** `externalsrc` bbappend
pointing at the sibling checkout — never commit one, or released images stop pointing at a working checkout — never commit one, or released images stop
matching the pins. matching the pins.
## Scarthgap migration backlog ## Scarthgap migration backlog
@@ -158,8 +158,8 @@ Scarthgap, but the legacy (Dunfell/Gatesgarth) layers won't build clean until:
- **`glowforge.ko`**: ported across many 6.12 API changes - **`glowforge.ko`**: ported across many 6.12 API changes
(`tasklet_hrtimer`→soft hrtimer, `timer_setup`, LED-trigger API, (`tasklet_hrtimer`→soft hrtimer, `timer_setup`, LED-trigger API,
`pwm_get`, `spi_delay`/`controller`, `filelock.h`, void `.remove`, `pwm_get`, `spi_delay`/`controller`, `filelock.h`, void `.remove`,
1-arg i2c probe). Compiles + links, 0 undefined symbols. Built from the 1-arg i2c probe). Compiles + links, 0 undefined symbols; the recipe
local sibling via an `externalsrc` bbappend during migration. fetches the module by pinned `SRCREV`.
- **DT**: `glowforge,cnc/thermal/pic/head` re-added with `pwms`/`pwm-names` - **DT**: `glowforge,cnc/thermal/pic/head` re-added with `pwms`/`pwm-names`
phandles; `glowforge.dtb` compiles with all motion nodes. phandles; `glowforge.dtb` compiles with all motion nodes.
Motion polish: PWM prescaler (factory 1001) is **obsolete** — 6.12 Motion polish: PWM prescaler (factory 1001) is **obsolete** — 6.12
@@ -173,7 +173,7 @@ Scarthgap, but the legacy (Dunfell/Gatesgarth) layers won't build clean until:
- **Camera — DONE and hardware-validated.** The factory - **Camera — DONE and hardware-validated.** The factory
`ov5648_mipi.c` (NXP's removed `v4l2_int_device`/`mxc_v4l2_capture`) is `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) replaced by the mainline `ovti,ov5648` subdev + imx6 `imx-media` (IPU CSI)
+ `imx6-mipi-csi2` receiver. The factory CAM_SEL MIPI switch is modelled + `imx6-mipi-csi2` receiver. The factory CAM_SEL MIPI switch is modeled
with the mainline `video-mux` (gpio-mux on `gpio7 10`): both sensors → 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 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 fixed oscillator (matching the factory DTB); avdd/dovdd/dvdd rails are in
@@ -202,14 +202,19 @@ Scarthgap, but the legacy (Dunfell/Gatesgarth) layers won't build clean until:
5. **Real-time strategy — decided.** The kernel runs 5. **Real-time strategy — decided.** The kernel runs
`CONFIG_PREEMPT=y` (factory behavior; `imx_v6_v7_defconfig` alone gives only `CONFIG_PREEMPT=y` (factory behavior; `imx_v6_v7_defconfig` alone gives only
`PREEMPT_VOLUNTARY`). **PREEMPT_RT is not selectable on arm32 6.12** (no `PREEMPT_VOLUNTARY`). **PREEMPT_RT is not selectable on arm32 6.12** (no
`ARCH_SUPPORTS_RT`) and is **not needed for the pulse feeder**: the SDMA `ARCH_SUPPORTS_RT`) and is **not needed for the pulse feeder**. The
ring is 128 MiB draining at 1 byte per EPIT tick — ≤200–400 KB/s even at argument is about queue depth, not ring size: the ring drains at 1 byte
the 200 kHz ceiling — so a full ring holds **~5–11 minutes** of stream and per EPIT tick (≤200 KB/s even at the 200 kHz ceiling), so the live
a modest 1 MiB of queued data already rides out ~3–5 s of scheduling feeder's bounded queue depth of ~150 ms — a few KB in flight — already
latency, orders of magnitude beyond anything PREEMPT exhibits. Deep rides out worst-case scheduling latency with orders of magnitude to spare
buffering + `SCHED_FIFO` for the feeder is the design; revisit RT only if (measured: 0.2 ms worst write latency under full CPU + I/O load; the
the underrun bench ever contradicts this arithmetic. (Bench: 5 s of underrun bench ran 100 kHz for 120 s with zero underruns). The ring
continuous feed at a 1 s buffer depth, zero underruns.) itself is 16 MiB (the `ring_mb` module 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_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 6. **gfui-client → forgectrl — DONE.** The stock `gfui-client` is excluded
from `forgefirm-image` (`IMAGE_INSTALL:remove = "gfui-client"` in from `forgefirm-image` (`IMAGE_INSTALL:remove = "gfui-client"` in
+7 -13
View File
@@ -73,11 +73,10 @@ repos:
.: .:
# --- meta-openglow — Glowforge BSP layers --------------------------------- # --- meta-openglow — Glowforge BSP layers ---------------------------------
# ACTIVE DEVELOPMENT: referenced as a local sibling checkout (no url => kas # Referenced as a local sibling checkout (no url => kas performs no git ops,
# performs no git ops, so the scarthgap-migration edits we're making are what # so in-place BSP edits are what gets built). The kernel-module-glowforge
# gets built). The kernel-module-glowforge sources are NOT a layer; they are # sources are NOT a layer; the kernel-module-glowforge.bb recipe fetches them
# pulled by the kernel-module-glowforge.bb recipe's SRC_URI, so kas does not # by pinned SRCREV, so kas does not manage them here.
# manage them here.
meta-openglow: meta-openglow:
path: ../meta-openglow path: ../meta-openglow
layers: layers:
@@ -85,11 +84,8 @@ repos:
meta-glowforge-bsp: meta-glowforge-bsp:
# meta-openglow-bsp (separate OpenGlow_std board) intentionally excluded. # meta-openglow-bsp (separate OpenGlow_std board) intentionally excluded.
# #
# FUTURE — flip to this pinned-remote block at release time (gated on # Pinned-remote alternative for a fully self-contained clone (see
# active BSP development settling — see kas/README.md "Push & release order". # kas/README.md "Push & release order"):
# When flipping, also drop meta-openglow's kernel-module-glowforge.bbappend
# (externalsrc to the local sibling) so a fresh clone is self-contained; its
# perl-native DEPENDS is already carried in the base recipe):
# meta-openglow: # meta-openglow:
# url: https://github.com/ScottW514/meta-openglow.git # url: https://github.com/ScottW514/meta-openglow.git
# branch: scarthgap # pin via kas lock / a tag at release # branch: scarthgap # pin via kas lock / a tag at release
@@ -122,9 +118,7 @@ local_conf_header:
# release.sh gates the release rootfs against a passwordless root entry. # release.sh gates the release rootfs against a passwordless root entry.
build-tweaks: | build-tweaks: |
# Parallelism for the 12-core / 16 GB WSL2 VM. (The earlier mid-build deaths # Parallelism sized for a 12-core / 16 GB build VM; raise on larger hosts.
# were the mirrored-networking vsock relay, not memory — see .wslconfig — so
# this can comfortably use more of the VM.)
BB_NUMBER_THREADS = "8" BB_NUMBER_THREADS = "8"
PARALLEL_MAKE = "-j 8" PARALLEL_MAKE = "-j 8"
# Caches kept inside the forgefirm repo (gitignored): # Caches kept inside the forgefirm repo (gitignored):
@@ -5,13 +5,14 @@ DESCRIPTION = "OpenGlow/ForgeFIRM image for Glowforge"
# ForgeFIRM cuts the cloud dependency: drop the Glowforge cloud client # ForgeFIRM cuts the cloud dependency: drop the Glowforge cloud client
# (gfui-client connects to Glowforge's servers). Removing it here (override # (gfui-client connects to Glowforge's servers). Removing it here (override
# only; the shared glowforge-image base is untouched) also sidesteps its # only; the shared glowforge-image base is untouched) also sidesteps its
# do_package failure. Its slot is filled locally by forgectrl (camera MJPEG # do_package failure. Its role is filled locally by forgectrl and the
# service; the grblHAL controller recipe is tracked in kas/README.md # controllers below (kas/README.md backlog #6).
# backlog #5).
IMAGE_INSTALL:remove = "gfui-client" IMAGE_INSTALL:remove = "gfui-client"
# grblhal-glowforge: the grblHAL motion controller (Grbl over TCP:23). # grblhal-glowforge: the grblHAL motion controller (Grbl over TCP:23).
# forgectrl: the ForgeFIRM control daemon (camera MJPEG service on :8080). # forgectrl: the ForgeFIRM machine-services daemon (HTTP :8080): controller
# supervisor, pulse-device broker, cooling engine, cameras, telemetry,
# settings, diagnostics, web control panel, and A/B updates.
# gfhome: one-shot Glowforge web-service homing, invoked by the controller # gfhome: one-shot Glowforge web-service homing, invoked by the controller
# for $H when homing_mode = gfcloud (/data/forgefirm.conf). # for $H when homing_mode = gfcloud (/data/forgefirm.conf).
# gfcloud: full Glowforge web-service controller daemon (the factory cloud # gfcloud: full Glowforge web-service controller daemon (the factory cloud
+1 -1
View File
@@ -186,7 +186,7 @@ cat <<EOF
Pre-publish checklist (kas/README.md "Push & release order" step 4): Pre-publish checklist (kas/README.md "Push & release order" step 4):
- meta-openglow pushed; kas config flipped to the pinned-remote block - meta-openglow pushed; kas config flipped to the pinned-remote block
- externalsrc bbappend dropped; kas lock refreshed - kas lock refreshed
- self-containment proven from a fresh clone - self-containment proven from a fresh clone
Publish (from a directory with an authenticated gh): Publish (from a directory with an authenticated gh):