docs: describe the firmware that exists; ship the NXP firmware EULA

README: replace the stalled REST/GUI roadmap with what the firmware does -
GRBL mode over TCP for LightBurn/UGS/cncjs, optional cloud mode, the web
control panel, cameras, A/B install beside the factory firmware - plus the
supported hardware incl. the 8 MP camera limitation and a real roadmap.

kas/README: xvclk is the board's 24 MHz oscillator; the pin/push rule covers
every source repo; camera and motion sections are hardware-validated, with the
OV8856 caveat spelled out; the device-tree item closes with the control_12v
residue named; forgectrl is the machine-services daemon with two supervised
controllers.

LIGHTBURN.md: drop the pre-first-light status line.

BUILD.md: state the NXP firmware licensing and where the EULA lives on the
machine. The image now installs firmware-imx-lic, so the EULA text ships beside
the VPU/EPDC blobs it covers; a bbappend declares that package at parse time,
which is what makes it installable from an image recipe.
This commit is contained in:
ScottW514
2026-08-13 12:34:48 -04:00
parent e92e500d91
commit 562631d9bf
6 changed files with 132 additions and 49 deletions
+9
View File
@@ -70,6 +70,15 @@ kas lock kas/forgefirm-glowforge.yml
See [`kas/README.md`](kas/README.md) for details, the container-build option, See [`kas/README.md`](kas/README.md) for details, the container-build option,
and the Scarthgap migration backlog. and the Scarthgap migration backlog.
## Third-party firmware licensing
The i.MX6 BSP installs NXP firmware blobs (VPU, EPDC) that are distributed
under NXP's firmware EULA. The build config accepts it
(`ACCEPT_FSL_EULA = "1"`), and the image ships the license text alongside the
blobs at `/usr/share/licenses/firmware-imx/EULA` — keep it there in any
redistributed image. The SDMA firmware comes from `linux-firmware`, which
carries its own license package.
## Write to an SD card ## Write to an SD card
```console ```console
+52 -7
View File
@@ -1,15 +1,60 @@
# OpenGlow/ForgeFIRM Firmware for Glowforge # OpenGlow/ForgeFIRM Firmware for Glowforge
This repository contains opensource firmware for Glowforge brand CNC lasers.
Open-source firmware for Glowforge brand CNC lasers. ForgeFIRM replaces the
cloud-dependent factory software on the **stock control board** — no hardware
modification — and gives the machine a local controller, a local web control
panel, and a standard Grbl interface.
* [Latest Release](https://github.com/ScottW514/forgefirm/releases) * [Latest Release](https://github.com/ScottW514/forgefirm/releases)
* [Installation Instructions](https://github.com/ScottW514/forgefirm/blob/master/INSTALL.md) * [Installation Instructions](https://github.com/ScottW514/forgefirm/blob/master/INSTALL.md)
* [Build Instructions](https://github.com/ScottW514/forgefirm/blob/master/BUILD.md) * [Build Instructions](https://github.com/ScottW514/forgefirm/blob/master/BUILD.md)
* [Connecting LightBurn](https://github.com/ScottW514/forgefirm/blob/master/docs/LIGHTBURN.md)
* [Community Support](https://community.openglow.org) * [Community Support](https://community.openglow.org)
# Roadmap ## What it does
* Base image that supports all Glowforge hardware (complete)
* Support Glowforge web service - Python implementation (mostly complete)
* REST API w/ G-Code support (~~in progress~~) Stalled
* Browser-based GUI (~~pending~~) Stalled
This project is for experimental purposes only, and is not supported or endorsed by Glowforge. **Two controller modes, selected in the web panel and switchable while the
machine is idle:**
* **GRBL mode** — [grblHAL](https://github.com/grblHAL) runs on the machine and
speaks Grbl 1.1 over TCP port 23, so LightBurn, UGS, and cncjs drive the
laser directly. Motion runs on the board's own hardware step engine (SDMA +
EPIT), fed live by the planner. M3/M4 dynamic laser power, coolant-flow
verification, over-temp holds, and an operator button press to arm the laser
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.
**Around both modes:**
* A **web control panel** on port 8080: machine status and position, coolant
and fan telemetry, safety-switch states, camera view, machine settings,
hardware diagnostics, firmware updates, and boot-slot management.
* 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.
* **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.
## Hardware
The control board is common to Glowforge Basic, Plus, and Pro. The 5 MP
(OV5648) camera modules are fully supported; the 8 MP (OV8856) modules found in
"HD" units bind but do not capture yet — see the camera note in
[kas/README.md](kas/README.md).
## Roadmap
* Limit-switch homing as an alternative to camera-referenced homing.
* Camera lens calibration and bed alignment for the LightBurn overlay.
* Capture support for the 8 MP (OV8856) camera modules.
* Cloud mode: stream jobs into the motion ring during the run, lifting the
job-length cap that buffering the whole job imposes.
**A very important warning: this is experimental software. Use of this software
could seriously maim or kill you or others, and voids your warranty. It is not
affiliated with or endorsed by Glowforge. Use it at your own risk.**
+1 -3
View File
@@ -1,8 +1,6 @@
# LightBurn setup & operation (ForgeFIRM) # LightBurn setup & operation (ForgeFIRM)
Status: laser software implemented, **first light pending** (see The laser fires only inside an operator-armed window:
BRINGUP.md for the commissioning record). The laser fires only inside
an operator-armed window:
- **Starting a job that fires: press the button.** At the first - **Starting a job that fires: press the button.** At the first
laser-on command of a job the machine unlocks its laser latch, laser-on command of a job the machine unlocks its laser latch,
+44 -31
View File
@@ -89,11 +89,13 @@ kas auto-loads the lockfile on subsequent runs. Commit it; refresh deliberately.
The build is only reproducible when recipe pins, layer branches, and the kas The build is only reproducible when recipe pins, layer branches, and the kas
config move in the right order. The sequence, with current status: config move in the right order. The sequence, with current status:
1. **Source repos pushed & pinned** — **DONE.** 1. **Source repos pushed & pinned** — **DONE.** Every source repo
`kernel-module-glowforge` (`029dfb6`) and `python3-gfhardware` (`9bf31fd`) (`kernel-module-glowforge`, `python3-gfhardware`, `Glowforge-Utilities`,
pushed to GitHub master; both recipes in meta-openglow pin those exact `grblHAL-glowforge`, `forgectrl`) is on GitHub and its recipe pins an exact
SRCREVs (no `AUTOREV` anywhere). Whenever either repo changes: push it, `SRCREV` — no `AUTOREV` anywhere. Whenever a source repo changes: push it,
then bump the recipe `SRCREV` in meta-openglow deliberately. then bump the recipe `SRCREV` deliberately (BSP recipes in meta-openglow,
ForgeFIRM components in meta-forgefirm) and re-verify with
`bitbake -c fetch <recipe>`.
2. **meta-openglow pushed** — **DONE.** The Scarthgap port lives on 2. **meta-openglow pushed** — **DONE.** The Scarthgap port lives on
the **`scarthgap` branch** (Yocto layer convention; the Dunfell-era `master` the **`scarthgap` branch** (Yocto layer convention; the Dunfell-era `master`
is untouched). Development continues on the local sibling checkout; push / is untouched). Development continues on the local sibling checkout; push /
@@ -104,9 +106,6 @@ config move in the right order. The sequence, with current status:
4. **At release time**: 4. **At release time**:
- flip `meta-openglow` in `forgefirm-glowforge.yml` from the local-sibling - flip `meta-openglow` in `forgefirm-glowforge.yml` from the local-sibling
block to the pinned-remote block (commented FUTURE block in the file); block to the pinned-remote block (commented FUTURE block in the file);
- drop meta-openglow's `kernel-module-glowforge.bbappend` (externalsrc to
the local sibling — its perl-native DEPENDS is already carried in the
base recipe) so a fresh clone is fully self-contained;
- refresh `kas lock`, tag all repos, and prove self-containment by building - refresh `kas lock`, tag all repos, and prove self-containment by building
from a **fresh clone**. from a **fresh clone**.
5. **GitHub release**: run `scripts/release.sh <version>` on the build 5. **GitHub release**: run `scripts/release.sh <version>` on the build
@@ -120,8 +119,11 @@ config move in the right order. The sequence, with current status:
`v<version>` = `FORGEFIRM_RELEASE` = the rootfs `/etc/forgefirm-version` `v<version>` = `FORGEFIRM_RELEASE` = the rootfs `/etc/forgefirm-version`
= the `.fw` meta-version; `release.sh` enforces the agreement. = the `.fw` meta-version; `release.sh` enforces the agreement.
For gfhardware development, either bump the recipe pin per iteration or add a All recipes fetch their pinned revision from GitHub, so an image build is
tracked externalsrc bbappend mirroring the kernel-module pattern. 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
matching the pins.
## Scarthgap migration backlog ## Scarthgap migration backlog
@@ -145,8 +147,9 @@ Scarthgap, but the legacy (Dunfell/Gatesgarth) layers won't build clean until:
`st,lis2hh12` ×3 + `national,lm75b` + `ti,wl1805` + gpio keys/leds bind to `st,lis2hh12` ×3 + `national,lm75b` + `ti,wl1805` + gpio keys/leds bind to
mainline drivers; `reg-userspace-consumer` enabled via `glowforge.cfg`. mainline drivers; `reg-userspace-consumer` enabled via `glowforge.cfg`.
(SPI-delay / PWM-prescaler dispositions are under Motion polish below.) (SPI-delay / PWM-prescaler dispositions are under Motion polish below.)
- **Motion path — DONE (builds clean; runtime needs hardware).** The whole - **Motion path — DONE and hardware-validated** (live-fed pulse stream,
chain forward-ports and compiles on 6.12: real gantry motion, laser fire). The whole chain forward-ports and
compiles on 6.12:
- **EPIT API**: `epit_api.c` in `arch/arm/mach-imx` (`CONFIG_MXC_EPIT_API`), - **EPIT API**: `epit_api.c` in `arch/arm/mach-imx` (`CONFIG_MXC_EPIT_API`),
in vmlinux, symbols exported; `&epit1/&epit2` in the DT. in vmlinux, symbols exported; `&epit1/&epit2` in the DT.
- **SDMA-expose**: re-created `dma-imx-sdma.h` + `0003-imx-sdma-*.patch` - **SDMA-expose**: re-created `dma-imx-sdma.h` + `0003-imx-sdma-*.patch`
@@ -166,16 +169,22 @@ Scarthgap, but the legacy (Dunfell/Gatesgarth) layers won't build clean until:
`spi_imx_setupxfer` (write `MX51_ECSPI_PERIODREG` from `t->word_delay`, `spi_imx_setupxfer` (write `MX51_ECSPI_PERIODREG` from `t->word_delay`,
guarded to ECSPI) and verify the wait-states on a scope. pic.c keeps the guarded to ECSPI) and verify the wait-states on a scope. pic.c keeps the
inter-transfer delay meanwhile. `glowforge,imx-pwm-audio` (buzzer) deferred. inter-transfer delay meanwhile. `glowforge,imx-pwm-audio` (buzzer) deferred.
- **Camera — DONE (builds clean; runtime needs hardware).** 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 modelled
with the mainline `video-mux` (gpio-mux on `gpio7 10`): both `ov5648`s → with the mainline `video-mux` (gpio-mux on `gpio7 10`): both sensors →
video-mux → `mipi_csi` → IPU CSI. Sensor `xvclk` (25 MHz fixed-clock) + video-mux → `mipi_csi` → IPU CSI. Sensor `xvclk` is the board's 24 MHz
avdd/dovdd/dvdd rails in the DT. All drivers build as modules and fixed oscillator (matching the factory DTB); avdd/dovdd/dvdd rails are in
`glowforge.dtb` compiles with the full pipeline. **HW bring-up:** confirm the DT. Both cameras stream live through forgectrl (MJPEG at 15 fps with
the real supply rails, CSI-2 lane count/order and CAM_SEL polarity, then VPU JPEG encode, full-resolution snapshots, mux arbitration).
validate with `media-ctl` + a v4l2 capture. **HD-unit caveat:** the DT lists both `ovti,ov5648` (5 MP) and
`ovti,ov8856` (8 MP) at 0x36 so one image covers both, and the driver
matching the chip ID wins — but mainline `ov8856` expects a 19.2 MHz
xvclk and only warns at 24 MHz, and the capture path is written to
ov5648's SBGGR8 2592×1944 format set. 8 MP "HD" modules therefore bind
but do not capture; adding 24 MHz PLL modes plus a sensor-aware capture
path is the open work.
3. **u-boot** — **DONE.** The `glowforge` u-boot is 3. **u-boot** — **DONE.** The `glowforge` u-boot is
a standalone `u-boot_2020.01.bb` (Scarthgap's poky has no u-boot 2020.01 a standalone `u-boot_2020.01.bb` (Scarthgap's poky has no u-boot 2020.01
base recipe to extend). It reuses poky's base recipe to extend). It reuses poky's
@@ -186,8 +195,11 @@ Scarthgap, but the legacy (Dunfell/Gatesgarth) layers won't build clean until:
`fw_printenv`/`fw_setenv` from `u-boot-fw-utils` to `libubootenv` `fw_printenv`/`fw_setenv` from `u-boot-fw-utils` to `libubootenv`
(`PREFERRED_PROVIDER_u-boot-fw-utils` in `glowforge.inc`) when the rootfs needs (`PREFERRED_PROVIDER_u-boot-fw-utils` in `glowforge.inc`) when the rootfs needs
them. them.
4. **Device tree** — revalidate the `glowforge` `.dts` against the linux-fslc 4. **Device tree — DONE.** The `glowforge` `.dts` is validated against the
6.12 DT bindings (paired with the kernel forward-port in #2). linux-fslc 6.12 bindings and against the running board (motion, safety
readbacks, cameras, sensors all bind and work). Residue: `control_12v`
still uses the `reg-userspace-consumer` compatible, which matches no 6.12
driver — convert it to `regulator-output` or drop the node.
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
@@ -200,14 +212,14 @@ Scarthgap, but the legacy (Dunfell/Gatesgarth) layers won't build clean until:
the underrun bench ever contradicts this arithmetic. (Bench: 5 s of the underrun bench ever contradicts this arithmetic. (Bench: 5 s of
continuous feed at a 1 s buffer depth, zero underruns.) continuous feed at a 1 s buffer depth, zero underruns.)
6. **gfui-client → forgectrl** — the Glowforge **cloud 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
`meta-forgefirm/recipes-forgefirm/images/forgefirm-image.bb`) — it connected to `meta-forgefirm/recipes-forgefirm/images/forgefirm-image.bb`). Its slot is
Glowforge's servers, the dependency ForgeFIRM exists to cut. Its slot is filled by `forgectrl` (github.com/ScottW514/forgectrl — the machine-services
filled by `forgectrl` (github.com/ScottW514/forgectrl — the ForgeFIRM daemon: web control panel, cameras, telemetry, settings, diagnostics,
control daemon; camera MJPEG service today, hardware status/control and cooling engine, updates, and controller-mode supervision) plus the two
GRBL-vs-cloud mode selection planned) plus the `grblhal-glowforge` controllers it supervises, `grblhal-glowforge` (Grbl over TCP:23) and
motion controller (both in the images with boot autostart). `gfcloud` (the optional Glowforge web-service client, off unless selected).
--- ---
@@ -215,6 +227,7 @@ Scarthgap, but the legacy (Dunfell/Gatesgarth) layers won't build clean until:
stack and deploys `forgefirm-image-glowforge.rootfs.wic.gz` (+ `zImage`, stack and deploys `forgefirm-image-glowforge.rootfs.wic.gz` (+ `zImage`,
`glowforge.dtb`, `u-boot-glowforge.imx`) under `build/tmp/deploy/images/glowforge/`. `glowforge.dtb`, `u-boot-glowforge.imx`) under `build/tmp/deploy/images/glowforge/`.
Build-time prerequisites baked into the config: `ACCEPT_FSL_EULA = "1"` (NXP Build-time prerequisites baked into the config: `ACCEPT_FSL_EULA = "1"` (NXP
firmware-imx) and the kernel default in `glowforge.conf`. Everything compiles; firmware-imx — the image also installs `firmware-imx-lic` so the EULA text
on-hardware bring-up (motion timing, laser/safety chain, camera pipeline) is the ships beside the blobs) and the kernel default in `glowforge.conf`. The stack
remaining validation and needs a real board. is hardware-validated end to end: motion timing, the laser and safety chain,
the camera pipeline, both controller modes, and the A/B install path.
@@ -0,0 +1,13 @@
# NXP distributes the i.MX VPU/EPDC firmware blobs under its firmware EULA
# (accepted with ACCEPT_FSL_EULA), so an image carrying those blobs has to
# carry the license text with them.
#
# LICENSE_CREATE_PACKAGE builds the license package, but license.bbclass adds
# it to PACKAGES during do_package - too late for an image to depend on the
# name. Declaring it here makes it resolvable at parse time; the class then
# logs one "package already existed" note for this recipe, which is why FILES
# is set here as well.
LICENSE_CREATE_PACKAGE = "1"
PACKAGES:prepend = "${PN}-lic "
FILES:${PN}-lic = "${datadir}/licenses/${PN}"
@@ -25,6 +25,11 @@ IMAGE_INSTALL:remove = "gfui-client"
# slotmigrate: boot-time reclaim of the legacy p4 layout (grows /data). # slotmigrate: boot-time reclaim of the legacy p4 layout (grows /data).
IMAGE_INSTALL:append = " grblhal-glowforge forgectrl gfhome gfcloud v4l-utils fwup ffboot slotmigrate" IMAGE_INSTALL:append = " grblhal-glowforge forgectrl gfhome gfcloud v4l-utils fwup ffboot slotmigrate"
# NXP's firmware EULA covers the i.MX VPU/EPDC blobs the BSP installs, so the
# image ships the license text with them (/usr/share/licenses/firmware-imx).
# The SDMA firmware brings its own -license package through linux-firmware.
IMAGE_INSTALL:append = " firmware-imx-lic"
# The release rootfs must fit a 200 MiB factory eMMC slot (409600 blocks). # The release rootfs must fit a 200 MiB factory eMMC slot (409600 blocks).
# Sizing: content + 40 MiB working space, hard-capped at the slot size — # Sizing: content + 40 MiB working space, hard-capped at the slot size —
# the build fails rather than emit an unflashable image. The raw ext4 is # the build fails rather than emit an unflashable image. The raw ext4 is