mirror of
https://github.com/openglow-org/forgefirm.git
synced 2026-09-27 16:51:12 -07:00
Follow the pulse ring to 32 MiB
The ring and the device-tree pool that backs it are now the size the factory firmware runs. Docs follow, and the hardware facts bank gains the reserved-memory map: 511 MiB usable, 96 MiB reserved for DMA, ~454 MiB left to Linux. image.health reads the pool size and ring_mb back and fails if they disagree, so a pool the parameter does not use cannot ship unnoticed, and checks that free never exceeds the ring less its gap.
This commit is contained in:
+22
-8
@@ -26,7 +26,7 @@ Read together with:
|
|||||||
hardware-validated.**
|
hardware-validated.**
|
||||||
|
|
||||||
- **Platform bring-up: complete and hardware-verified.** SDMA + EPIT pulse
|
- **Platform bring-up: complete and hardware-verified.** SDMA + EPIT pulse
|
||||||
playback out of a 16 MiB reserved pool, live-fed during a run; laser PWM at
|
playback out of a reserved DMA pool, live-fed during a run; laser PWM at
|
||||||
39.98 kHz; `CONFIG_PREEMPT=y`; both OV5648 cameras on the mainline
|
39.98 kHz; `CONFIG_PREEMPT=y`; both OV5648 cameras on the mainline
|
||||||
imx-media pipeline with VPU JPEG encode; A/B slot install, signed `.fw`
|
imx-media pipeline with VPU JPEG encode; A/B slot install, signed `.fw`
|
||||||
releases and factory restore.
|
releases and factory restore.
|
||||||
@@ -657,12 +657,22 @@ not a release.
|
|||||||
`sdio write failed (-84)` and costs ~1 s of Wi-Fi (wlcore firmware recovery)
|
`sdio write failed (-84)` and costs ~1 s of Wi-Fi (wlcore firmware recovery)
|
||||||
— see "Wi-Fi SDIO CRC watch" under Next work.
|
— see "Wi-Fi SDIO CRC watch" under Next work.
|
||||||
- **SDMA pulse engine**: ring size = the `ring_mb` module parameter (default
|
- **SDMA pulse engine**: ring size = the `ring_mb` module parameter (default
|
||||||
16 MiB; power of two, must fit the 16 MiB `cnc-pulsebuf` no-map DT pool).
|
32 MiB, the factory ring size; power of two, must fit the 32 MiB
|
||||||
Free = size − 32 KiB gap. Bench-verified at 16 MiB: 20 MB streamed at 100 kHz
|
`cnc-pulsebuf` no-map DT pool). Free = size − 32 KiB gap, so 33,521,664 bytes.
|
||||||
through the wrapping ring, 0 ENOMEM, 0.4 ms max write latency, starve →
|
Bench-verified on the 16 MiB ring the earlier images shipped, and the
|
||||||
`underrun` per protocol. The ring caps legacy cloud-mode job length
|
mechanism is size-independent: 20 MB streamed at 100 kHz through the wrapping
|
||||||
(whole-file preload: ~1 MiB per 100 s of 10 kHz stream, so ~28 min); the
|
ring, 0 ENOMEM, 0.4 ms max write latency, starve → `underrun` per protocol.
|
||||||
grblHAL live feed keeps only a few KB in flight. The playback script is
|
The ring caps legacy cloud-mode job length (whole-file preload: ~1 MiB per
|
||||||
|
100 s of 10 kHz stream, so ~56 min); the grblHAL live feed keeps only a few
|
||||||
|
KB in flight.
|
||||||
|
- **Reserved memory**: 511 MiB usable DRAM (`0x10000000`–`0x2fefffff`), of which
|
||||||
|
96 MiB is reserved for DMA: the 32 MiB `cnc-pulsebuf` no-map pool (dynamically
|
||||||
|
placed, `alignment = size`, so it lands at `0x2c000000`) plus 64 MiB of
|
||||||
|
reusable CMA for camera/IPU/VPU buffers. no-map means the pulse pool is gone
|
||||||
|
from the kernel's map whether a job uses it or not, which is what makes
|
||||||
|
`dma_alloc_coherent()` deterministic for a late-probing out-of-tree module.
|
||||||
|
MemTotal ~454 MiB; measured idle use in GRBL mode with the daemon and
|
||||||
|
controller up is ~100 MiB. The playback script is
|
||||||
relocated to SDMA channel 26 at `<26 0xF00>` (halfword 7680) with a pre-run
|
relocated to SDMA channel 26 at `<26 0xF00>` (halfword 7680) with a pre-run
|
||||||
integrity guard; the probe lines to look for are `EPIT clock 66000000 Hz` and
|
integrity guard; the probe lines to look for are `EPIT clock 66000000 Hz` and
|
||||||
`SDMA channel 26 reserved for pulse playback (script at halfword 7680)`.
|
`SDMA channel 26 reserved for pulse playback (script at halfword 7680)`.
|
||||||
@@ -1010,7 +1020,11 @@ Open items only. Anything closed is in `CAMPAIGN-LOG.md`.
|
|||||||
image built with the `<recipe>-pin.inc` layout: that image is a platform
|
image built with the `<recipe>-pin.inc` layout: that image is a platform
|
||||||
change against everything recorded so far, unavoidably, and only from then
|
change against everything recorded so far, unavoidably, and only from then
|
||||||
on does a component pin bump re-require just the tests covering that
|
on does a component pin bump re-require just the tests covering that
|
||||||
component. `20260817124714` was built after the pin files landed and the
|
component. It now also carries the 32 MiB pulse ring (DT pool plus the
|
||||||
|
module default, batched into that flash), which is a platform change in its
|
||||||
|
own right and the first bench sighting of the larger ring: `image.health`
|
||||||
|
reads the pool and `ring_mb` back, and a preloaded cloud job past the old
|
||||||
|
28-minute cap is the capability proof. `20260817124714` was built after the pin files landed and the
|
||||||
parity tests were driven on it, but no full-campaign export is recorded for
|
parity tests were driven on it, but no full-campaign export is recorded for
|
||||||
it — confirm on the bench and write the record here. Also still owed:
|
it — confirm on the bench and write the record here. Also still owed:
|
||||||
exercising the ported bench tools from the page (they are registered and
|
exercising the ported bench tools from the page (they are registered and
|
||||||
|
|||||||
+4
-4
@@ -60,7 +60,7 @@ Two consequences worth knowing:
|
|||||||
|
|
||||||
### The ring, and two ways to fill it
|
### The ring, and two ways to fill it
|
||||||
|
|
||||||
Pulse bytes go into a 16 MiB ring buffer in reserved memory. There are two ways
|
Pulse bytes go into a 32 MiB ring buffer in reserved memory, the same size the factory firmware uses. There are two ways
|
||||||
to use it, and the mode you run decides which:
|
to use it, and the mode you run decides which:
|
||||||
|
|
||||||
- **Live streaming (GRBL mode).** The controller keeps only a small window of
|
- **Live streaming (GRBL mode).** The controller keeps only a small window of
|
||||||
@@ -70,8 +70,8 @@ to use it, and the mode you run decides which:
|
|||||||
falls behind far enough to empty the ring, the machine enters an **underrun**
|
falls behind far enough to empty the ring, the machine enters an **underrun**
|
||||||
state: motion stops instantly, and position is no longer trusted.
|
state: motion stops instantly, and position is no longer trusted.
|
||||||
- **Preloading (cloud mode).** The whole job is written into the ring before it
|
- **Preloading (cloud mode).** The whole job is written into the ring before it
|
||||||
starts. Nothing can starve, but the ring size caps job length — roughly
|
starts. Nothing can starve, but the ring size caps job length: roughly
|
||||||
1 MiB per 100 seconds at the cloud's 10 kHz tick, so about 28 minutes. A job
|
1 MiB per 100 seconds at the cloud's 10 kHz tick, so about 56 minutes. A job
|
||||||
larger than the ring is rejected cleanly before it runs.
|
larger than the ring is rejected cleanly before it runs.
|
||||||
|
|
||||||
### Stopping and resuming at the hardware level
|
### Stopping and resuming at the hardware level
|
||||||
@@ -398,7 +398,7 @@ switch to cloud mode and back — re-home after switching.
|
|||||||
| Input | G-code over TCP:23 | a downloaded pulse file |
|
| Input | G-code over TCP:23 | a downloaded pulse file |
|
||||||
| Ring use | live-streamed, small window | whole job preloaded |
|
| Ring use | live-streamed, small window | whole job preloaded |
|
||||||
| Machine tick | 28160 Hz default | 10 kHz (from the job header) |
|
| Machine tick | 28160 Hz default | 10 kHz (from the job header) |
|
||||||
| Job length limit | none | ~28 minutes (ring size) |
|
| Job length limit | none | ~56 minutes (ring size) |
|
||||||
| Needs internet | no | yes |
|
| Needs internet | no | yes |
|
||||||
| Laser arming | button press per job | button press per job |
|
| Laser arming | button press per job | button press per job |
|
||||||
| Button mid-job | feed hold / cycle start | pause with backtrack / resume with lead |
|
| Button mid-job | feed hold / cycle start | pause with backtrack / resume with lead |
|
||||||
|
|||||||
@@ -41,6 +41,16 @@ def kernel_config():
|
|||||||
return cfg
|
return cfg
|
||||||
|
|
||||||
|
|
||||||
|
def _dt_u32(path):
|
||||||
|
"""A device-tree cell as an int (big-endian), or None."""
|
||||||
|
try:
|
||||||
|
with open(path, "rb") as f:
|
||||||
|
raw = f.read(4)
|
||||||
|
except OSError:
|
||||||
|
return None
|
||||||
|
return int.from_bytes(raw, "big") if len(raw) == 4 else None
|
||||||
|
|
||||||
|
|
||||||
def fds_of(pid):
|
def fds_of(pid):
|
||||||
out = []
|
out = []
|
||||||
try:
|
try:
|
||||||
@@ -61,8 +71,8 @@ def fds_of(pid):
|
|||||||
("grblhal-glowforge", "CMakeLists.txt"), ("kernel-module-glowforge", "**"),
|
("grblhal-glowforge", "CMakeLists.txt"), ("kernel-module-glowforge", "**"),
|
||||||
("linux-fslc", "**")],
|
("linux-fslc", "**")],
|
||||||
description="The image that is running is the image the manifest describes, with the "
|
description="The image that is running is the image the manifest describes, with the "
|
||||||
"kernel options, the module, the daemon ownership, the init ordering, and the "
|
"kernel options, the module and the pulse ring it maps, the daemon ownership, "
|
||||||
"file modes the release depends on.")
|
"the init ordering, and the file modes the release depends on.")
|
||||||
def image_health(ctx):
|
def image_health(ctx):
|
||||||
ev = ctx.evidence
|
ev = ctx.evidence
|
||||||
manifest = ctx.runner.manifest
|
manifest = ctx.runner.manifest
|
||||||
@@ -98,6 +108,25 @@ def image_health(ctx):
|
|||||||
ev["cnc_free"] = free
|
ev["cnc_free"] = free
|
||||||
ctx.check(free is not None and free > 0, "cnc/free unreadable or zero (ring not mapped?)")
|
ctx.check(free is not None and free > 0, "cnc/free unreadable or zero (ring not mapped?)")
|
||||||
ctx.log("cnc/free: %s bytes", free)
|
ctx.log("cnc/free: %s bytes", free)
|
||||||
|
|
||||||
|
# The ring the platform ships: the DT pool is the promise, ring_mb is what
|
||||||
|
# the module took. They must agree - a pool the parameter does not use is
|
||||||
|
# no-map RAM burned for nothing, and a parameter the pool cannot back would
|
||||||
|
# have failed the probe. free can only ever be size less the 32 KiB gap.
|
||||||
|
pool = _dt_u32("/proc/device-tree/reserved-memory/cnc-pulsebuf/size")
|
||||||
|
try:
|
||||||
|
ring = int((_read("/sys/module/glowforge/parameters/ring_mb", "") or "").strip()) << 20
|
||||||
|
except ValueError:
|
||||||
|
ring = None
|
||||||
|
ev["cnc_pool_bytes"] = pool
|
||||||
|
ev["cnc_ring_bytes"] = ring
|
||||||
|
ctx.log("cnc-pulsebuf pool: %s bytes, ring_mb: %s bytes", pool, ring)
|
||||||
|
ctx.check(pool is not None, "no cnc-pulsebuf reserved-memory node (the ring fell back to CMA?)")
|
||||||
|
ctx.check(ring is not None, "/sys/module/glowforge/parameters/ring_mb unreadable")
|
||||||
|
ctx.check(pool is None or ring is None or pool == ring,
|
||||||
|
"DT pool %s and ring_mb %s disagree", pool, ring)
|
||||||
|
ctx.check(ring is None or free is None or free <= ring - 32 * 1024,
|
||||||
|
"cnc/free %s exceeds the ring less its 32 KiB gap (%s)", free, ring)
|
||||||
ctx.check(hw.sysfs_read("cnc/interlock_circuit") is not None, "cnc/interlock_circuit unreadable")
|
ctx.check(hw.sysfs_read("cnc/interlock_circuit") is not None, "cnc/interlock_circuit unreadable")
|
||||||
|
|
||||||
# 4. forgectrl holds /dev/glowforge and supervises the controller
|
# 4. forgectrl holds /dev/glowforge and supervises the controller
|
||||||
|
|||||||
+3
-3
@@ -255,9 +255,9 @@ Scarthgap, but the legacy (Dunfell/Gatesgarth) layers won't build clean until:
|
|||||||
rides out worst-case scheduling latency with orders of magnitude to spare
|
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
|
(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
|
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
|
itself is 32 MiB (the `ring_mb` module parameter, backed by the 32 MiB
|
||||||
reserved pool): ~84 s of stream at 200 kHz, ~28 min at the 10 kHz
|
reserved pool, matching the factory ring): ~168 s of stream at 200 kHz,
|
||||||
cloud-mode tick — a capacity that matters for the whole-job preload of
|
~56 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
|
cloud mode, not for latency. Bounded queue depth + `SCHED_FIFO` for the
|
||||||
feeder is the design; revisit RT only if the underrun bench ever
|
feeder is the design; revisit RT only if the underrun bench ever
|
||||||
contradicts this arithmetic.
|
contradicts this arithmetic.
|
||||||
|
|||||||
Reference in New Issue
Block a user