hv_enable: EV_SW bit 4 is the HV_ENABLE readback; pins for the rename

SAFETY.md and the safing figure name GPIO4_06 for what it is - the
readback of the chain's HV_ENABLE output through U24 (the factory net
label E-STOP is kept as a note); BRINGUP records the rename, the
device-tree polarity flip that makes bit 4 read as HV_ENABLE itself,
the removal of the estop_halts_motion opt-in, and the bench check for
the flash that ships it. Recipe pins move to forgectrl 801f1f3,
grblHAL-glowforge b629c18 and python3-gfhardware c3d1790 (PV 0.1.5).
This commit is contained in:
ScottW514
2026-08-15 12:28:58 -04:00
parent 8a1fa91db5
commit f75629a4df
6 changed files with 62 additions and 37 deletions
+36 -13
View File
@@ -1436,11 +1436,25 @@ accordingly ("Automatic — AP country, else World").
the bit reads 0 = satisfied/good-to-go; Pro brings it out for an the bit reads 0 = satisfied/good-to-go; Pro brings it out for an
external lockout chain. Must NOT gate motion (beam is external lockout chain. Must NOT gate motion (beam is
hardware-gated). hardware-gated).
**SW_ESTOP reads LOW during ANY motion** (measured 2026-08-07: **`hv_enable` (EV_SW bit 4, GPIO4_06) is the readback of the safety
polled at 20 ms through X and Z jogs — low for the whole run, ~70/75 chain's HV_ENABLE output** through the U24 inverter — not an input.
samples, recovers instantly at idle; True at idle) — must NOT gate Active for the whole duration of any run (the window in which the
motion on the factory board either (it false-tripped every legacy charge pump is fed and HV_ENABLE is alive), inactive at idle, and it
cloud motion ~0.1 s in). Doors/door1/door2 stay stable during motion. drops ≈0.45 s after the last charge-pump pulse (measured 2026-08-07 at
20 ms through X and Z jogs, ~70/75 samples; watchdog period measured
2026-08-15). It gates nothing anywhere — it is telemetry (`/status`
`switches.hv_enable`, control-panel "HV enable"). Naming note: the
factory design labels this net **E-STOP**, and dated entries below
written before 2026-08-15 call it `estop`/`SW_ESTOP` with the
pre-rename polarity (the device tree then declared the pin active-high,
so the bit read HIGH at idle and LOW through a run — the same physical
behavior, inverted); the DTS now declares it active-low so the bit
reads as HV_ENABLE itself. The former `estop_halts_motion` /
`MOTION.ESTOP_HALTS_MOTION` opt-in (gate motion on this line, for a
hypothetical retrofit) is removed: it only ever made sense while the
line was misread as an e-stop input, and a real e-stop belongs in the
lid-switch chain (`docs/SAFETY.md`). Doors/door1/door2 stay stable
during motion.
- Machine identity from OCOTP nvmem: HW_OCOTP_MAC0 is the serial, - Machine identity from OCOTP nvmem: HW_OCOTP_MAC0 is the serial,
base-23-encoded to the factory hostname — fuse-verified on the bench base-23-encoded to the factory hostname — fuse-verified on the bench
against the factory label. The bench machine's actual values are against the factory label. The bench machine's actual values are
@@ -2083,14 +2097,11 @@ accordingly ("Automatic — AP country, else World").
hardware chain already does to the beam. Bit 3 is the series hardware chain already does to the beam. Bit 3 is the series
combination the safety chain itself uses, not the individual door combination the safety chain itself uses, not the individual door
switches. switches.
- **e-stop (bit 4): opt-in only.** The line rests ACTIVE on a - **hv_enable (bit 4): never gated on.** It is the readback of the
healthy machine and drops for the duration of any stepper motion chain's HV_ENABLE output (facts bank above), telemetry only; the
on this board, so gating on it would abort every job. Set core's `e_stop` capability is not advertised. (The `estop_halts_motion`
`estop_halts_motion` in `/data/forgefirm.conf` (hand-edited; it is opt-in that existed until 2026-08-15 is gone, together with the
not in forgectrl's settings whitelist, and unknown keys survive name — see the facts bank.)
forgectrl's writes) for a machine retrofitted with a real e-stop
circuit. Same escape hatch as the cloud client's
`MOTION.ESTOP_HALTS_MOTION`.
- **interlock latch (bit 6): deliberately not gated on.** Its - **interlock latch (bit 6): deliberately not gated on.** Its
resting state on a healthy machine is not characterized and a resting state on a healthy machine is not characterized and a
false assertion would wedge every job; the hardware chain enforces false assertion would wedge every job; the hardware chain enforces
@@ -2315,6 +2326,18 @@ accordingly ("Automatic — AP country, else World").
- The uniprocessor locking assumption and the panic/dead-man safe - The uniprocessor locking assumption and the panic/dead-man safe
states are documented in `kernel-module-glowforge/UAPI.md`; no states are documented in `kernel-module-glowforge/UAPI.md`; no
bench item. bench item.
- **`hv_enable` rename + polarity flip (2026-08-15) rides the same
flash.** The gpio-keys node for GPIO4_06 is now `hv_enable`,
declared active-low, so EV_SW bit 4 reads as the HV_ENABLE output
itself (inactive at idle, active through a run). forgectrl
(`/status` key `switches.hv_enable`, panel "HV enable"), the grblHAL
driver (`SW_BIT_HV_ENABLE`, no gating) and gfhardware
(`InputSwitch.SW_HV_ENABLE`, no gating) all ship in the same image
and read the new polarity; the DTS and that userspace must not be
mixed across the flash (a mismatch only inverts the telemetry — nothing
gates on the bit — but the dashboard would lie). **Bench:** `/status`
shows `hv_enable:false` at idle, `true` during a jog, back to `false`
≈0.45 s after the run ends, in lockstep with `charge_pump_alive`.
- **GATE A kernel fixes added to the same flash (2026-08-14):** - **GATE A kernel fixes added to the same flash (2026-08-14):**
the controlled-deceleration ramp now floors at the minimum step the controlled-deceleration ramp now floors at the minimum step
frequency with a saturating decrement, and `epit_hz_to_divisor()` frequency with a saturating decrement, and `epit_hz_to_divisor()`
+19 -17
View File
@@ -52,7 +52,7 @@ away kills emission in hardware, not in software.
| U17 | SN74AHC08 quad 2-input AND | The four gates: DOORS, HV_ENABLE, and the two-stage LASER_ON gate | | U17 | SN74AHC08 quad 2-input AND | The four gates: DOORS, HV_ENABLE, and the two-stage LASER_ON gate |
| U23 | CD4043B quad R/S latch (NOR type, active-high S/R, output enable tied high) | Latch 1 = button latch, latch 2 = interlock latch | | U23 | CD4043B quad R/S latch (NOR type, active-high S/R, output enable tied high) | Latch 1 = button latch, latch 2 = interlock latch |
| U32 | 74AHC1G32 single 2-input OR | Lid-open OR SoC lock → button latch SET | | U32 | 74AHC1G32 single 2-input OR | Lid-open OR SoC lock → button latch SET |
| U24 | 74AHC1G04 single inverter | E-STOP sense line = ¬HV_ENABLE | | U24 | 74AHC1G04 single inverter | HV_ENABLE readback to the SoC (the pin carries ¬HV_ENABLE; the factory design labels this net **E-STOP**) |
| U18 | i.MX6 Solo | The SoC: drives CHG_PUMP, LATCH_RESET, INTERLOCK_RESET, FIRE; reads everything else | | U18 | i.MX6 Solo | The SoC: drives CHG_PUMP, LATCH_RESET, INTERLOCK_RESET, FIRE; reads everything else |
### 2.2 Inputs ### 2.2 Inputs
@@ -68,7 +68,7 @@ away kills emission in hardware, not in software.
| Button latch state | U23-1 Q → U5-6 → U6-1 | double inversion | GPIO1_03 (R7) | `cnc/button_latch`, `interlock_circuit` bit 2 | 1 = latch SET (fire blocked / not armed), 0 = armed | | Button latch state | U23-1 Q → U5-6 → U6-1 | double inversion | GPIO1_03 (R7) | `cnc/button_latch`, `interlock_circuit` bit 2 | 1 = latch SET (fire blocked / not armed), 0 = armed |
| Interlock latch state | U23-2 Q → U6-3 → U6-4 | double inversion | GPIO1_02 (T1) | code 6 `interlock_latch`, active high → **active = latch SET** | 1 = interlock latch blocking | | Interlock latch state | U23-2 Q → U6-3 → U6-4 | double inversion | GPIO1_02 (T1) | code 6 `interlock_latch`, active high → **active = latch SET** | 1 = interlock latch blocking |
| LASER_ON readback | J1_12 net (U17-3 output) | U6-5 inverts | GPIO1_05 (R4) | `cnc/laser_on`, `laser_on_sampled`, `interlock_circuit` bit 0 (raw, active low) | The gated output — the only software-visible proof of emission permission | | LASER_ON readback | J1_12 net (U17-3 output) | U6-5 inverts | GPIO1_05 (R4) | `cnc/laser_on`, `laser_on_sampled`, `interlock_circuit` bit 0 (raw, active low) | The gated output — the only software-visible proof of emission permission |
| E-STOP | U24 = ¬HV_ENABLE | — | GPIO4_06 (W5) | code 4 `estop`, active high | Sense line, **not** an input: high whenever HV_ENABLE is low (idle), low while HV_ENABLE is alive | | HV_ENABLE readback (factory net name E-STOP) | U24 = ¬HV_ENABLE | — | GPIO4_06 (W5) | code 4 `hv_enable`, active low → **active = HV_ENABLE asserted** | Readback of the chain's own output, **not** an input: inactive at idle, active only while a run feeds the watchdog with the lid closed |
| LASER_PGOOD | J1_14 (the supply's HV_OK line) | — | GPIO4_21 (P24) | `cnc/laser_pgood`, `laser_pgood_sampled` (active low) | Read as "power good" from the laser supply; what the supply actually signals on it is not fully characterized | | LASER_PGOOD | J1_14 (the supply's HV_OK line) | — | GPIO4_21 (P24) | `cnc/laser_pgood`, `laser_pgood_sampled` (active low) | Read as "power good" from the laser supply; what the supply actually signals on it is not fully characterized |
### 2.3 SoC outputs into the chain ### 2.3 SoC outputs into the chain
@@ -91,7 +91,7 @@ on end-of-data or underrun.
DOORS_OK = DOOR_SW1 · DOOR_SW2 (U17-1) DOORS_OK = DOOR_SW1 · DOOR_SW2 (U17-1)
WDOG_ALIVE = U1-1 Q, retriggered by every CHG_PUMP rising edge WDOG_ALIVE = U1-1 Q, retriggered by every CHG_PUMP rising edge
HV_ENABLE = DOORS_OK · WDOG_ALIVE (U17-4) → J1_16 HV_ENABLE = DOORS_OK · WDOG_ALIVE (U17-4) → J1_16
E_STOP = ¬HV_ENABLE (U24 inverter) → GPIO4_06 ¬HV_ENABLE (U24 inverter) → GPIO4_06, read back as `hv_enable`
Button latch (U23-1): Button latch (U23-1):
SET = ¬DOORS_OK + LATCH_RESET (U32 OR) SET = ¬DOORS_OK + LATCH_RESET (U32 OR)
@@ -124,11 +124,13 @@ machine when the lid is closed *and* the SoC has already released its lock.
| Remote-interlock loop opens (Pro) | unchanged | blocked: the kernel drives INTERLOCK_RESET high on the switch edge, setting the interlock latch. Opening the loop by itself only releases the latch's RESET — the board has no direct trip path — so this SoC drive is what makes the interlock a hardware cut (see §3.1); software additionally parks the job on `interlock` | close the loop: the kernel releases INTERLOCK_RESET and the closed loop resets the latch | | Remote-interlock loop opens (Pro) | unchanged | blocked: the kernel drives INTERLOCK_RESET high on the switch edge, setting the interlock latch. Opening the loop by itself only releases the latch's RESET — the board has no direct trip path — so this SoC drive is what makes the interlock a hardware cut (see §3.1); software additionally parks the job on `interlock` | close the loop: the kernel releases INTERLOCK_RESET and the closed loop resets the latch |
| Interlock latch already SET | unchanged | blocked | closing the loop clears it | | Interlock latch already SET | unchanged | blocked | closing the loop clears it |
Note the *E-STOP* line: despite the factory name it is an output of this `hv_enable` (GPIO4_06) is a readback of this chain's own output, not an
chain — the inverse of HV_ENABLE. It reads active on a healthy idle machine and input: it is inactive on an idle machine and active for the duration of any
drops for the whole duration of any kernel run, the window in which the charge kernel run — the window in which the charge pump is fed and HV_ENABLE is
pump is fed and HV_ENABLE is alive. Nothing in ForgeFIRM gates on it unless a alive. Nothing in ForgeFIRM gates on it; it is telemetry. (The factory design
machine settings key opts in for a retrofitted circuit. labels the net E-STOP; no Glowforge model has an e-stop input, and a
retrofitted one belongs in the lid-switch chain, where the hardware enforces
it.)
--- ---
@@ -247,10 +249,10 @@ kernel readbacks (the runbook `BRINGUP.md` holds the drill records):
for the in-flight run; a locked latch survives a stop + resume replay. for the in-flight run; a locked latch survives a stop + resume replay.
- Armed kill mid-FIRE: emission tail equals the ring in-flight only - Armed kill mid-FIRE: emission tail equals the ring in-flight only
(15–171 ms), the latch relocks, the burn line ends abruptly. (15–171 ms), the latch relocks, the burn line ends abruptly.
- Switch bits 0–3, 5, 6 verified against physical state; bit 4 (`estop`) - Switch bits 0–3, 5, 6 verified against physical state; bit 4
characterized live: high at idle, low through any run, and it flips (`hv_enable`) characterized live: inactive at idle, active through any run,
together with `charge_pump_alive` on both edges (HV_ENABLE = DOORS_OK · and it flips together with `charge_pump_alive` on both edges (HV_ENABLE =
WDOG_ALIVE observed). DOORS_OK · WDOG_ALIVE observed).
- Interlock latch drive: with the connector unjumpered, `interlock`, - Interlock latch drive: with the connector unjumpered, `interlock`,
`interlock_latch_reset` and `interlock_latch` all assert within one 50 ms `interlock_latch_reset` and `interlock_latch` all assert within one 50 ms
sample and all clear when the loop is closed again. sample and all clear when the loop is closed again.
@@ -265,9 +267,9 @@ Present gaps in the hardware picture. None of them changes the safety
argument (every gap is on the readback/sense side or is a "which part" question), argument (every gap is on the readback/sense side or is a "which part" question),
but each is worth closing: but each is worth closing:
- **`estop` toggles seen inside motion windows** are run boundaries: `estop` - **`hv_enable` toggles seen inside motion windows** are run boundaries:
follows the watchdog exactly (low from the first pulse of a run, high `hv_enable` follows the watchdog exactly (active from the first pulse of a
≈0.45 s after its last), and homing and jogs are several short runs. A run, inactive ≈0.45 s after its last), and homing and jogs are several
feed late by more than ~250 ms would look the same and has not been short runs. A feed late by more than ~250 ms would look the same and has
observed. not been observed.
- **`laser_pgood` (HV_OK, J1_14) semantics** are not fully characterized. - **`laser_pgood` (HV_OK, J1_14) semantics** are not fully characterized.
+3 -3
View File
@@ -75,13 +75,13 @@
<path class="wire-out" d="M824 146 H1300"/> <path class="wire-out" d="M824 146 H1300"/>
<circle cx="880" cy="146" r="3.5" class="dot"/> <circle cx="880" cy="146" r="3.5" class="dot"/>
<text x="900" y="166" class="net">HV_ENABLE</text> <text x="900" y="166" class="net">HV_ENABLE</text>
<!-- U24 inverter -> estop --> <!-- U24 inverter -> hv_enable readback (factory net name E-STOP) -->
<path class="wire" d="M880 146 V88 H930"/> <path class="wire" d="M880 146 V88 H930"/>
<rect x="930" y="70" width="64" height="36" rx="6" class="gate"/> <rect x="930" y="70" width="64" height="36" rx="6" class="gate"/>
<text x="962" y="86" class="gate-l">NOT</text><text x="962" y="99" class="gate-s">U24</text> <text x="962" y="86" class="gate-l">NOT</text><text x="962" y="99" class="gate-s">U24</text>
<path class="wire" d="M994 88 H1010"/> <path class="wire" d="M994 88 H1010"/>
<rect x="1010" y="76" width="236" height="24" rx="5" class="rb-tag"/> <rect x="1010" y="76" width="262" height="24" rx="5" class="rb-tag"/>
<text x="1018" y="92" class="tag-t">estop</text><text x="1060" y="92" class="tag-s">EV_SW 4 · GPIO4_06 · = ¬HV_ENABLE</text> <text x="1018" y="92" class="tag-t">hv_enable</text><text x="1092" y="92" class="tag-s">EV_SW 4 · GPIO4_06 · readback</text>
<!-- PSU tag HV_ENABLE --> <!-- PSU tag HV_ENABLE -->
<rect x="1300" y="132" width="172" height="28" rx="5" class="psu-tag"/> <rect x="1300" y="132" width="172" height="28" rx="5" class="psu-tag"/>
<text x="1308" y="151" class="tag-t">J1_16</text><text x="1354" y="151" class="tag-s">HV_ENABLE</text> <text x="1308" y="151" class="tag-t">J1_16</text><text x="1354" y="151" class="tag-s">HV_ENABLE</text>

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

@@ -9,7 +9,7 @@ PV = "0.1.0"
SRC_URI = "git://github.com/ScottW514/forgectrl.git;protocol=https;branch=main" SRC_URI = "git://github.com/ScottW514/forgectrl.git;protocol=https;branch=main"
# Pinned; bump deliberately after pushing forgectrl changes. # Pinned; bump deliberately after pushing forgectrl changes.
SRCREV = "f7276c5788b823f669baec9997bc1b0737c0b027" SRCREV = "801f1f3a1f90c38157429ee1e4ff79b0c2fe68e3"
S = "${WORKDIR}/git" S = "${WORKDIR}/git"
@@ -11,11 +11,11 @@ LIC_FILES_CHKSUM = "file://LICENSE;md5=62f8bb455fcc4bf177ecab380f71cd5d"
SRC_URI = "git://github.com/ScottW514/python3-gfhardware.git;protocol=https;branch=master" SRC_URI = "git://github.com/ScottW514/python3-gfhardware.git;protocol=https;branch=master"
# Pinned; bump deliberately (AUTOREV is not reproducible). # Pinned; bump deliberately (AUTOREV is not reproducible).
SRCREV = "6c7534a050f61e1f205743f34a9d2743a1a7b96f" SRCREV = "c3d1790e6cd8f11eee7b386b5314d53454a1c2db"
# Bump PV with every SRCREV move: the hash-derived package version is not # Bump PV with every SRCREV move: the hash-derived package version is not
# monotonic on its own and buildhistory QA fails the build when it sorts # monotonic on its own and buildhistory QA fails the build when it sorts
# backwards. # backwards.
PV = "0.1.4+git" PV = "0.1.5+git"
S = "${WORKDIR}/git" S = "${WORKDIR}/git"
@@ -11,7 +11,7 @@ PV = "0.1.0"
# upstream). # upstream).
SRC_URI = "gitsm://github.com/ScottW514/grblHAL-glowforge.git;protocol=https;branch=main" SRC_URI = "gitsm://github.com/ScottW514/grblHAL-glowforge.git;protocol=https;branch=main"
# Pinned; bump deliberately after pushing grblHAL-glowforge changes. # Pinned; bump deliberately after pushing grblHAL-glowforge changes.
SRCREV = "a9446fe73059c040cb1e59497ae41f3c2badbadf" SRCREV = "b629c188227a0ad4eb47095cf9ebe7783a2b9a74"
SRC_URI += "file://grblhal.init" SRC_URI += "file://grblhal.init"