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:
ScottW514
2026-08-19 21:21:30 -04:00
parent 6e4a26ac61
commit 4300f7f4a8
4 changed files with 60 additions and 17 deletions
+31 -2
View File
@@ -41,6 +41,16 @@ def kernel_config():
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):
out = []
try:
@@ -61,8 +71,8 @@ def fds_of(pid):
("grblhal-glowforge", "CMakeLists.txt"), ("kernel-module-glowforge", "**"),
("linux-fslc", "**")],
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 "
"file modes the release depends on.")
"kernel options, the module and the pulse ring it maps, the daemon ownership, "
"the init ordering, and the file modes the release depends on.")
def image_health(ctx):
ev = ctx.evidence
manifest = ctx.runner.manifest
@@ -98,6 +108,25 @@ def image_health(ctx):
ev["cnc_free"] = free
ctx.check(free is not None and free > 0, "cnc/free unreadable or zero (ring not mapped?)")
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")
# 4. forgectrl holds /dev/glowforge and supervises the controller