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,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
|
||||||
|
|||||||
@@ -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).
|
||||||
|
|||||||
@@ -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
@@ -728,16 +728,16 @@ item 8.
|
|||||||
at startup regardless), and `vs-supply = <®_3p3v>` on the lm75
|
at startup regardless), and `vs-supply = <®_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
@@ -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
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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):
|
||||||
|
|||||||
Reference in New Issue
Block a user