diff --git a/.github/workflows/yocto-cold-build.yml b/.github/workflows/yocto-cold-build.yml index fe593d0..1aafcc7 100644 --- a/.github/workflows/yocto-cold-build.yml +++ b/.github/workflows/yocto-cold-build.yml @@ -30,10 +30,10 @@ jobs: with: path: forgefirm - # The kas config references meta-openglow as a local sibling, and - # meta-openglow's kernel-module bbappend uses an externalsrc sibling - # (both by design during active BSP development - kas/README.md - # "Push & release order" step 4 flips these at release time). + # The kas config references meta-openglow as a local sibling + # (kas/README.md "Push & release order" step 4 flips it to the + # pinned-remote block at release time). Every source repo the recipes + # build is fetched by pinned SRCREV; no other sibling is needed. - name: Checkout meta-openglow (sibling) uses: actions/checkout@v4 with: @@ -41,12 +41,6 @@ jobs: ref: scarthgap 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 run: | sudo apt-get update -qq diff --git a/BUILD.md b/BUILD.md index bdeb4e0..aaa8ad3 100644 --- a/BUILD.md +++ b/BUILD.md @@ -26,20 +26,21 @@ Do not build as root (the Yocto sanity checks refuse it). ## Get the sources -Clone the repos as siblings (the kas config references `meta-openglow` at -`../meta-openglow` while it is under active Scarthgap migration): +Clone the two repos as siblings (the kas config references `meta-openglow`, +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 git clone https://github.com/ScottW514/forgefirm.git -git clone https://github.com/ScottW514/meta-openglow.git -git clone https://github.com/ScottW514/kernel-module-glowforge.git +git clone -b scarthgap https://github.com/ScottW514/meta-openglow.git ``` ``` openglow-forgefirm/ ├── forgefirm/ ← base repo, build runs here -├── meta-openglow/ -└── kernel-module-glowforge/ +└── meta-openglow/ ``` ## 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 ``` -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). diff --git a/README.md b/README.md index a862148..585ab45 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,9 @@ machine is idle:** for each job. * **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. - 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:** @@ -34,8 +36,9 @@ machine is idle:** * Both **cameras** as MJPEG streams and full-resolution snapshots — the lid camera feeds LightBurn's camera overlay directly. * **Camera-referenced homing**: `$H` from any sender runs the factory-style - camera homing cycle through the Glowforge service, and the machine records - where it is. + camera homing cycle through the Glowforge service (a Glowforge account and a + 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, archiving every factory version first, so the machine can be switched back to stock at any time without the Glowforge cloud. diff --git a/docs/BRINGUP.md b/docs/BRINGUP.md index 6e6b43a..647f27d 100644 --- a/docs/BRINGUP.md +++ b/docs/BRINGUP.md @@ -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 (`` 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=/bin/fwup-v1.16.0 FWUP_COMPAT=/bin/fwup-0.14.2 + FORGEFIRM_DEV_KEY=/devkeys/fwup-key.priv FORGEFIRM_SIGNING_KEY= RELEASE_STAGING_DIR= - ./scripts/release.sh ` (gh for the publish step lives on the - Windows side; release.sh prints the exact command). + ./scripts/release.sh ` (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 /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 /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, diff --git a/docs/LIGHTBURN.md b/docs/LIGHTBURN.md index b53ae22..00de95d 100644 --- a/docs/LIGHTBURN.md +++ b/docs/LIGHTBURN.md @@ -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. diff --git a/docs/UPDATE-SYSTEM.md b/docs/UPDATE-SYSTEM.md index 8294499..ee0d0ab 100644 --- a/docs/UPDATE-SYSTEM.md +++ b/docs/UPDATE-SYSTEM.md @@ -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-.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. diff --git a/kas/README.md b/kas/README.md index 6681487..1b9c13b 100644 --- a/kas/README.md +++ b/kas/README.md @@ -29,15 +29,15 @@ openglow-forgefirm/ │ ├── build/ ← bitbake output incl. images (gitignored) │ ├── downloads/ sstate-cache/ ← caches (gitignored) │ └── .gitignore -├── meta-openglow/ ← Glowforge BSP layers (local sibling, under migration) -└── kernel-module-glowforge/ ← glowforge.ko sources (pulled by recipe SRC_URI) +└── meta-openglow/ ← Glowforge BSP layers (local sibling checkout) ``` -`meta-openglow` is referenced as a **local sibling** (`../meta-openglow`) while -we migrate it to Scarthgap, so its in-place edits are what gets built. Once that -migration is committed/pushed, flip it to a kas-cloned + pinned repo (commented -block in `forgefirm-glowforge.yml`) and the forgefirm repo becomes fully -self-contained. +`meta-openglow` is referenced as a **local sibling** (`../meta-openglow`), so +its in-place edits are what gets built. The commented pinned-remote block in +`forgefirm-glowforge.yml` makes the forgefirm repo fully self-contained when +flipped on. The source repos the recipes build (`kernel-module-glowforge`, +`grblHAL-glowforge`, `forgectrl`, `python3-gfhardware`, `Glowforge-Utilities`) +are fetched by pinned `SRCREV` and are not needed as local checkouts. ## 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 reproducible from the repos alone. For fast iteration on a source repo, bump its pin per iteration, or add a **local, untracked** `externalsrc` bbappend -pointing at 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. ## 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 (`tasklet_hrtimer`→soft hrtimer, `timer_setup`, LED-trigger API, `pwm_get`, `spi_delay`/`controller`, `filelock.h`, void `.remove`, - 1-arg i2c probe). Compiles + links, 0 undefined symbols. Built from the - local sibling via an `externalsrc` bbappend during migration. + 1-arg i2c probe). Compiles + links, 0 undefined symbols; the recipe + fetches the module by pinned `SRCREV`. - **DT**: `glowforge,cnc/thermal/pic/head` re-added with `pwms`/`pwm-names` phandles; `glowforge.dtb` compiles with all motion nodes. Motion polish: PWM prescaler (factory 1001) is **obsolete** — 6.12 @@ -173,7 +173,7 @@ Scarthgap, but the legacy (Dunfell/Gatesgarth) layers won't build clean until: - **Camera — DONE and hardware-validated.** The factory `ov5648_mipi.c` (NXP's removed `v4l2_int_device`/`mxc_v4l2_capture`) is replaced by the mainline `ovti,ov5648` subdev + imx6 `imx-media` (IPU CSI) - + `imx6-mipi-csi2` receiver. The factory CAM_SEL MIPI switch is 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 → 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 @@ -202,14 +202,19 @@ Scarthgap, but the legacy (Dunfell/Gatesgarth) layers won't build clean until: 5. **Real-time strategy — decided.** The kernel runs `CONFIG_PREEMPT=y` (factory behavior; `imx_v6_v7_defconfig` alone gives only `PREEMPT_VOLUNTARY`). **PREEMPT_RT is not selectable on arm32 6.12** (no - `ARCH_SUPPORTS_RT`) and is **not needed for the pulse feeder**: the SDMA - ring is 128 MiB draining at 1 byte per EPIT tick — ≤200–400 KB/s even at - the 200 kHz ceiling — so a full ring holds **~5–11 minutes** of stream and - a modest 1 MiB of queued data already rides out ~3–5 s of scheduling - latency, orders of magnitude beyond anything PREEMPT exhibits. Deep - buffering + `SCHED_FIFO` for the feeder is the design; revisit RT only if - the underrun bench ever contradicts this arithmetic. (Bench: 5 s of - continuous feed at a 1 s buffer depth, zero underruns.) + `ARCH_SUPPORTS_RT`) and is **not needed for the pulse feeder**. The + argument is about queue depth, not ring size: the ring drains at 1 byte + per EPIT tick (≤200 KB/s even at the 200 kHz ceiling), so the live + feeder's bounded queue depth of ~150 ms — a few KB in flight — already + rides out worst-case scheduling latency with orders of magnitude to spare + (measured: 0.2 ms worst write latency under full CPU + I/O load; the + underrun bench ran 100 kHz for 120 s with zero underruns). The ring + itself is 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 from `forgefirm-image` (`IMAGE_INSTALL:remove = "gfui-client"` in diff --git a/kas/forgefirm-glowforge.yml b/kas/forgefirm-glowforge.yml index cfdf119..cd618b0 100644 --- a/kas/forgefirm-glowforge.yml +++ b/kas/forgefirm-glowforge.yml @@ -73,11 +73,10 @@ repos: .: # --- meta-openglow — Glowforge BSP layers --------------------------------- - # ACTIVE DEVELOPMENT: referenced as a local sibling checkout (no url => kas - # performs no git ops, so the scarthgap-migration edits we're making are what - # gets built). The kernel-module-glowforge sources are NOT a layer; they are - # pulled by the kernel-module-glowforge.bb recipe's SRC_URI, so kas does not - # manage them here. + # Referenced as a local sibling checkout (no url => kas performs no git ops, + # so in-place BSP edits are what gets built). The kernel-module-glowforge + # sources are NOT a layer; the kernel-module-glowforge.bb recipe fetches them + # by pinned SRCREV, so kas does not manage them here. meta-openglow: path: ../meta-openglow layers: @@ -85,11 +84,8 @@ repos: meta-glowforge-bsp: # meta-openglow-bsp (separate OpenGlow_std board) intentionally excluded. # - # FUTURE — flip to this pinned-remote block at release time (gated on - # active BSP development settling — see 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): + # Pinned-remote alternative for a fully self-contained clone (see + # kas/README.md "Push & release order"): # meta-openglow: # url: https://github.com/ScottW514/meta-openglow.git # 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. build-tweaks: | - # Parallelism for the 12-core / 16 GB WSL2 VM. (The earlier mid-build deaths - # were the mirrored-networking vsock relay, not memory — see .wslconfig — so - # this can comfortably use more of the VM.) + # Parallelism sized for a 12-core / 16 GB build VM; raise on larger hosts. BB_NUMBER_THREADS = "8" PARALLEL_MAKE = "-j 8" # Caches kept inside the forgefirm repo (gitignored): diff --git a/meta-forgefirm/recipes-forgefirm/images/forgefirm-image.bb b/meta-forgefirm/recipes-forgefirm/images/forgefirm-image.bb index 51dc2fc..19df197 100644 --- a/meta-forgefirm/recipes-forgefirm/images/forgefirm-image.bb +++ b/meta-forgefirm/recipes-forgefirm/images/forgefirm-image.bb @@ -5,13 +5,14 @@ DESCRIPTION = "OpenGlow/ForgeFIRM image for Glowforge" # ForgeFIRM cuts the cloud dependency: drop the Glowforge cloud client # (gfui-client connects to Glowforge's servers). Removing it here (override # only; the shared glowforge-image base is untouched) also sidesteps its -# do_package failure. Its slot is filled locally by forgectrl (camera MJPEG -# service; the grblHAL controller recipe is tracked in kas/README.md -# backlog #5). +# do_package failure. Its role is filled locally by forgectrl and the +# controllers below (kas/README.md backlog #6). IMAGE_INSTALL:remove = "gfui-client" # 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 # for $H when homing_mode = gfcloud (/data/forgefirm.conf). # gfcloud: full Glowforge web-service controller daemon (the factory cloud diff --git a/scripts/release.sh b/scripts/release.sh index be37898..76f749e 100644 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -186,7 +186,7 @@ cat <