Skip to content

feat(api): BZM2 diagnostic endpoints and hardware documentation - #71

Open
recklessnode wants to merge 17 commits into
256foundation:mainfrom
recklessnode:bzm2/pr4-diagnostics
Open

feat(api): BZM2 diagnostic endpoints and hardware documentation#71
recklessnode wants to merge 17 commits into
256foundation:mainfrom
recklessnode:bzm2/pr4-diagnostics

Conversation

@recklessnode

Copy link
Copy Markdown

Part 4 of the BZM2 series (stacked on parts 1–3 — review the feat(api) commit). Completes the series with the HTTP diagnostics surface and the hardware reference documentation.

Endpoints

Eight endpoints under /api/v0/boards/{name}/bzm2/, each forwarding a BoardCommand through the board's command channel (part 1's plumbing) with a 5 s reply timeout; boards without a command channel answer 400:

Endpoint Purpose
POST dts-vs-query trigger an on-die temperature/voltage sensor read; returns refreshed board telemetry
POST noop liveness probe; returns the chip's 3-byte "BZ2" payload
POST loopback echo a hex payload through the ASIC
POST register-read / register-write raw engine/local register access
POST clock-report PLL/DLL status registers, decoded
GET chain-summary bus/ASIC layout + startup path + saved-operating-point status
POST discover-engines TDM engine-map scan (idle threads only); returns refreshed telemetry with per-ASIC hole maps

Request/response DTOs carry OpenAPI schemas; integration tests drive every endpoint against a command-capable fake board, including telemetry-refresh round trips.

Documentation (docs/bzm2/)

Port architecture notes, tuning-planner (PnP) notes, opcode grounding, hardware integration guide, UART/TDM protocol reference, and the reference roadmap — the BZM2 is publicly documented, and these are the review companion for parts 2–3. README gains the BZM2 entry under Current Status plus doc links and related projects.

This PR supersedes #66 (the standalone docs PR) — the documents now land with the code they describe.

Gates: cargo build (0 warnings), cargo test (390 passed / 0 failed), cargo fmt clean.

🤖 Generated with Claude Code

@recklessnode

Copy link
Copy Markdown
Author

Series: #68 (infra) -> #69 (asic core) -> #70 (board + tuning) -> #71 (diagnostics + docs). Each part builds and tests green standalone; later parts are stacked, so their diffs include predecessors - review by commit.

@recklessnode

Copy link
Copy Markdown
Author

Following up on the 2026-06-15 dev call (discussion #73): the concern about general reference documentation living in-tree applies to part of this PR too, so we have resolved it proactively in d9ab286:

  • docs/bzm2/ now carries only the three documents that describe code in this tree: the port architecture notes, the tuning-planner (PnP) notes, and the opcode grounding.
  • The hardware reference (integration guide, UART/TDM protocol reference, reference roadmap) has moved to its canonical home: bzm2-hwref - the public, CC-BY-SA BZM2 hardware reference Reckless Systems maintains with access to the original collateral. The README now links there instead of carrying copies (-1,326 lines from this PR).

That implements the principle from the call: nothing unmaintained lives in-tree; the tree links to a maintained external home. Same proposal posted to #73 for the tabled where-should-reference-docs-live question; #66 closes in favor of this arrangement.

@recklessnode
recklessnode force-pushed the bzm2/pr4-diagnostics branch 2 times, most recently from 66367ef to 3ff5d28 Compare July 22, 2026 07:50
@recklessnode

Copy link
Copy Markdown
Author

Series merge order (updated 2026-07-22) - four stacked PRs, all MERGEABLE, each green under just checks (fmt + clippy --release -D warnings + test) on every commit. Merge strictly in order:

  1. feat(board): per-board command channel, power-rail primitives, thread telemetry types #68 bzm2/pr1-infra - generic infrastructure, zero BZM2 code (per-board command channel wiring upstream's own SetFanTarget, board/power.rs rail primitives, HashThread telemetry types, attach_configured_board). Standalone-useful; base of the stack.
  2. feat(asic): Intel BZM2 (Bonanza Mine 2) ASIC family support #69 bzm2/pr2-asic-core - BZM2 protocol/UART/clock/thread layer + serial-robustness hardening. Hard-depends on feat(board): per-board command channel, power-rail primitives, thread telemetry types #68's HashThread telemetry types (won't compile without them).
  3. feat(board): BZM2 board driver with calibration and runtime tuning #70 bzm2/pr3-board-tuning - board driver + tuning planner. Drives feat(asic): Intel BZM2 (Bonanza Mine 2) ASIC family support #69's ASIC layer; uses feat(board): per-board command channel, power-rail primitives, thread telemetry types #68's command_tx/power.
  4. feat(api): BZM2 diagnostic endpoints and hardware documentation #71 bzm2/pr4-diagnostics - HTTP diagnostic endpoints + docs. Forwards BoardCommands that feat(board): BZM2 board driver with calibration and runtime tuning #70's board answers; hardware reference docs link to the maintained bzm2-hwref.

The order is the compile dependency chain (verified at import level - no PR references a symbol from a later one), not a preference. Merging #68 first collapses #69's visible diff to its own commits, and so down the stack. Later commits added today (the runtime-retune invalidation fix on #70, the serial hardening on #69) are folded into the PR whose code they touch, keeping each PR correct-from-first-commit rather than fixed-in-a-later-PR.

@recklessnode
recklessnode force-pushed the bzm2/pr4-diagnostics branch from 3ff5d28 to 908830a Compare August 1, 2026 14:13
recklessnode and others added 17 commits August 3, 2026 13:07
…d telemetry types

Infrastructure groundwork usable by any board, no new dependencies:

- Wire the per-board command channel end to end: BackplaneConnector and
  api::registry::BoardRegistration gain an optional
  mpsc::Sender<BoardCommand>; the backplane forwards it at start_board.
  BoardCommand::SetFanTarget and SetFanTargetRequest existed but nothing
  wired them - PATCH /api/v0/boards/{name}/fans/{fan} now drives them
  (boards opt in by populating command_tx; all current boards answer
  "accepts no commands" until they grow a command loop).
- BoardRegistry::board(name) + command_tx(name) accessors with lazy
  disconnect pruning; get_board refactored onto the former.
- board/power.rs: PowerRail trait (Tps546PowerRail, FilePowerRail file
  adapters, FileGpioPin) + GpioResetLine (AsicEnable impl) +
  VoltageStackBringupPlan for ordered multi-rail bring-up with settle
  delays and reverse-order shutdown.
- hash_thread: HashThreadTemperatureReading/PowerReading/
  TelemetryUpdate + HashThreadEvent::TelemetryUpdate (typed path for
  thread-sourced sensor data; bitaxe's monitor TODO wants this) and
  HashThreadError as a shared thread error vocabulary.
- Backplane::attach_configured_board: attach an env-configured virtual
  board without synthesizing a transport event.
- BoardTelemetry.asics (serde-default) + AsicState/EngineCoordinate:
  generic per-ASIC topology/diagnostics state for multi-ASIC boards.
- transport/serial: Clone derives on the Arc-backed reader/writer/
  control halves.
- .gitattributes for LF normalization.
Applies the CODE_STYLE S.topdown/S.mod ordering rules added in e0e582f to
the new power-rail module:

- `pub trait PowerRail` now precedes `PowerRailTelemetry`, the supporting
  type that appears in its signature (S.topdown main-type-first).
- `pub async fn pulse` now precedes the private `drive` it calls
  (S.topdown pub-before-private / caller-before-callee).
- `impl AsicEnable for GpioResetLine` moves back next to `GpioResetLine`
  instead of being stranded below the unrelated `FileGpioPin` block
  (S.mod group order).
- `VoltageStackBringupPlan` is reunited with its two impls, which had been
  split off below `FilePowerRail` (S.mod group order).

Pure reordering: the file is line-for-line identical as a multiset apart
from two blank lines rustfmt collapsed. cargo fmt + cargo check clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The protocol/transport layer for the Intel BZM2 mining ASIC, mirroring
the bm13xx in-tree structure (protocol codec / controller / thread):

- protocol.rs: pure codec - opcodes, frame encoders (write_job,
  write_register, noop, loopback, read_result), TDM frame/result
  parsers, the 20x12 logical engine grid (default 236 active engines),
  target -> leading-zeros conversion. Deps: bitcoin + std only.
- uart.rs: Bzm2UartController over the 9-bit multidrop UART - register
  R/W (unicast/multicast/local), NOOP "BZ2" verification, chain
  enumeration (ID-assignment walk from the 0xFA default), TDM
  enable/sync reads, engine-map discovery, loopback, DTS/VS sensor
  configure + query.
- clock.rs: Bzm2ClockController - PLL/DLL program/enable/lock-wait
  (per-ASIC and broadcast), clock debug reports.
- thread.rs: Bzm2Thread implementing HashThread - direct UART work
  dispatch, TDM result handling with per-ASIC/per-PLL hashrate
  estimation, DTS/VS telemetry via HashThreadEvent::TelemetryUpdate,
  thermal-trip frame handling, diagnostic command handle.

Tests are PTY-based chain emulations, gated #[cfg(all(test, unix))]
so non-Unix hosts still build the crate cleanly.
The long-lived TdmFrameParser never registers an expected READREG length,
so any `<asic<100> 0x03` prefix in the stream hit the no-count `break` with
cursor at 0: nothing was drained, framing wedged permanently, and
self.buffer grew by up to a full read on every push (line-noise OOM).

Treat a READREG with no pending read as a stray byte and resync one byte
forward, matching the unknown-opcode arm. This makes strictly-forward
progress, keeps the buffer bounded to a single partial frame, and preserves
correct framing of valid frames.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The `asic >= 100` resync heuristic dropped every frame from an id at or
above 100, including DTS/VS frames carrying the thermal-trip / voltage-fault
bits. Ids that high are not reached by the supported 1/4/9/12 chains
(start_id defaults to 0), but an operator can push them via
MUJINA_BZM2_ENUM_START_ID, at which point over-temp protection was silently
disabled for the whole bus.

Exempt DTS/VS frames from the id heuristic so a trip/fault frame always
reaches handle_dts_vs_frame, where it is logged loudly before any protective
action. The heuristic still skips stray high-id bytes for the non-safety
opcodes, so resync behaviour for line noise is unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The single-task actor ran its diagnostic command handlers (read_register,
query_noop, query_loopback, and the 12 reads behind query_clock_report) on
bare read_exact calls. A chip that never answers, answers short, or goes
quiet mid-response froze the whole actor -- dispatch ticks, result handling,
DTS/VS trip detection, and even ThreadCommand::Shutdown -- permanently.

Wrap each diagnostic read in a bounded tokio timeout (DIAGNOSTIC_READ_TIMEOUT)
via a shared helper, mapping expiry to DiagnosticsFailed, mirroring the
streaming helpers.

enumerate_chain likewise verified the freshly assigned id with the unbounded
verify_noop_bz2; a device that passed the timed 0xfa probe but then failed to
echo on its new id wedged enumeration and board init. Give the post-assign
verify the same probe_timeout bound.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A single DTS/VS frame with any of thermal_trip / thermal_fault /
voltage_fault / voltage_shutdown set instantly and permanently killed the
hash thread. Those bits all live in one payload byte with no debounce and no
validity gate, so line noise on a multidrop bus (or one crafted frame) could
take a board offline until a full miner restart -- DoS-by-noise.

Gate each fault decision on the matching sensor-enable bit, which reflects
host configuration and is set on every genuine frame. A real over-temp still
stops immediately (no debounce delay), while a stray trip bit from a disabled
sensor is ignored. The residual single-frame-both-bits risk is documented in
a code comment; the protocol carries no checksum to close it fully.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Applies the CODE_STYLE S.topdown/S.mod ordering rules added in e0e582f.
thread.rs was the most inverted module in the series:

- `pub struct Bzm2Thread` - the module's subject - was the LAST of eleven
  types, below every supporting type. It and its two impls now open the
  type region (S.topdown main-type-first).
- `pub struct Bzm2ThreadHandle` sat below the three private measurement
  types; it now precedes them, so all public types come before the private
  ones (S.topdown pub-before-private).
- Four constants were scattered: three below `Bzm2ThreadConfig` and
  `DIAGNOSTIC_READ_TIMEOUT` ~1000 lines into the function group. All four
  now sit in the constants group after the imports (S.mod group order).
- `fn pll_index_for_row` was wedged between two types; moved into the
  function group (S.mod group order).
- `read_exact_diagnostic` was defined above all three of its callers
  (read_register, query_noop, query_loopback); moved below them
  (S.topdown caller-before-callee).

Pure reordering: identical line-for-line as a multiset apart from one
blank line rustfmt collapsed. cargo fmt + cargo check clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Continues the S.topdown pass (e0e582f) across the rest of the ASIC core:

- uart.rs: `Bzm2UartController`, the module's subject, was the last of five
  types, below the config, error and engine-map types that appear in its
  signatures. It and its impl now open the type region.
- clock.rs: same shape - `Bzm2ClockController` sat below all eight of its
  supporting types (Bzm2Pll/Bzm2Dll, the config and status structs); moved
  to the head of the type region.
- protocol.rs: `Bzm2EngineLayout` and its two impls were stranded in the
  middle of the free-function group, between `default_engine_coordinates`
  and `leading_zero_threshold`. Moved up with the other types so the
  function group is contiguous (S.mod group order).

Pure reordering in all three files - identical as a multiset of lines apart
from blank lines rustfmt normalised. cargo fmt + cargo check clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Add the Intel BZM2 board driver: bringup and shutdown sequencing over
sysfs-configured power rails, live pre-thread calibration with saved
operating point replay, a runtime monitor publishing board and tuning
telemetry, per-board diagnostic command handling, and the blockscale
tuning planner it drives.

This supersedes the earlier carve-series history on the fork's
pre-rebase refs; upstream's per-commit CI makes the split-commit
presentation expensive, and the reviewable-split property lives in the
module tree itself.
A doc audit of the saved-operating-point lifecycle exposed a gap: when
runtime retune triggers persisted past the trigger tracker's threshold,
the monitor demoted the saved operating point back to pending. Startup
replay only refuses invalidated profiles, so a known-bad operating
point was replayed on the next restart and cost one bad startup cycle
before runtime retune corrected it.

Persistently triggered retunes now mark the saved operating point
invalidated and persist that status through the existing store path, so
restart refuses the known-bad replay and falls back to live
calibration. A successful retune still stores a fresh point, which
validates on the first clean poll.
Completes the S.topdown pass (e0e582f) across the board driver and tuning
planner:

- tuning/blockscale.rs: `Bzm2CalibrationPlanner`, the module's subject, was
  declared last among twenty types; it and its impl now open the type
  region. The private `OperatingTarget` struct, previously defined in the
  middle of the function group, moves up into the types group after the
  public types (S.topdown main-type-first, S.mod group order).
- board/bzm2/mod.rs: the private `create_bzm2_board` constructor sat above
  every type in the file; moved into the function group (S.mod group order).
- board/bzm2/telemetry.rs: the private `upsert_asic_state` and
  `parse_scaled_sensor_value` were interleaved among the pub(super)
  functions; all ten public functions now precede both private ones.
- board/bzm2/bringup.rs: the private `build_rails` and `build_reset_line`
  sat above three pub(super) methods inside `impl Bzm2BringupConfig`; moved
  below them (S.topdown pub-before-private).
- board/bzm2/calibration.rs: the function group interleaved public and
  private four times over; regrouped to six pub(super) functions followed
  by nine private ones, which also fixes `store_calibration_profile` being
  defined above its caller.

Pure reordering: every file is identical as a multiset of lines apart from
one blank line rustfmt normalised in bringup.rs. cargo fmt + cargo check clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Completes the BZM2 series with the HTTP diagnostics surface and
reference documentation:

- Eight endpoints under /api/v0/boards/{name}/bzm2/: dts-vs-query,
  noop, loopback, register-read, register-write, clock-report,
  chain-summary (GET), discover-engines. Each forwards a BoardCommand
  through the board's command channel with a 5 s timeout; boards
  without a command channel answer 400.
- Request/response DTOs with OpenAPI schemas (hex-encoded payloads
  for raw register/loopback paths).
- Integration tests drive every endpoint against a command-capable
  fake board, including telemetry-refresh round trips (DTS/VS readings
  and discovered engine maps appearing in the returned board state).
- docs/bzm2/: port architecture notes, tuning planner (PnP) notes,
  opcode grounding, hardware integration guide, UART/TDM protocol
  reference, and reference roadmap. README gains the BZM2 entry under
  Current Status plus doc links and related projects.
Per the 2026-06-15 dev call (discussion 256foundation#73): general reference
material that does not describe in-tree code should not live in the
main tree. docs/bzm2/ now carries only the three documents that
describe this driver (port architecture, tuning planner, opcode
grounding); the hardware reference (integration guide, UART/TDM
protocol, roadmap) lives in the maintained, CC-BY-SA bzm2-hwref
repository, which the README now links.
Apply the doc fixes from the H/I/J audit reports against the rebuilt
driver tree:

bzm2-port.md (audit H):
- drop the false "calibration and autotuning state machines not
  implemented" limit; state the real limit (no in-place mid-run
  recalibration) and add DTS/VS query + engine discovery to the live
  API surface list
- rename BoardState to BoardTelemetry throughout
- rewrite the bring-up closed-loop/retune note to match monitor.rs
  (closed-loop rail/thermal checks flag a pending retune; applied at
  next startup)
- fix reset attribution: GpioResetLine/FileGpioPin, not AsicEnable
- fix MUJINA_BZM2_ENUM_MAX_ASICS_PER_BUS ceiling wording
- point readers at board/bzm2/config.rs as the authoritative env-var
  reference instead of an exhaustive inline list
- reframe legacy cgminer/bzmd process-split and file-level claims as
  clearly-labeled unverified background; note generic fan/ambient
  telemetry now exists

bzm2-pnp.md (audit I):
- describe actual restart behavior: persistent retune triggers demote
  the saved operating point to pending (never invalidated), so a
  suspect profile is still replayed on restart; gap flagged for PR
  reviewers
- correct the efficiency model to the real single domain-ordered pass
  (O(domains x ASICs)) instead of three linear passes
- validation promotion happens on the first clean monitor poll, not
  after a sampling window
- label legacy pnp.c internals as recalled, unverified background

bzm2-opcode-grounding.md (audit J):
- reground the Scope on the in-tree protocol.rs/uart.rs and the public
  bzm2-hwref UART protocol reference; the legacy bzm2_cgminer tree is
  not vendored here and all links to it were broken
- fix the broken relative protocol.rs link; also cite uart.rs
- remove PLL debug readout from the exclusions (implemented in
  clock.rs, surfaced via thread.rs diagnostics)
- mark the one-outstanding-TDM-noop and broadcast-TDM-read-layering
  claims as unverified against the legacy source

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… fix

The pnp and port docs described the pre-fix behavior: persistent retune
triggers demoted the saved operating point to pending, which restart
replay still accepted. Persistent triggers now invalidate the saved
point and persist that status, so describe the fixed lifecycle: pending
validates on the first clean poll, persistent triggers invalidate, and
startup refuses an invalidated profile and recalibrates live.
The private `decode_hex_payload` helper added for the BZM2 diagnostic
endpoints was defined above both of its callers (the register-write and
loopback handlers). S.topdown asks that among private items callers come
before callees, so it now sits directly below the last handler that uses it.

Pure reordering - identical as a multiset of lines. cargo fmt + cargo check clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@recklessnode
recklessnode force-pushed the bzm2/pr4-diagnostics branch from 908830a to ffba625 Compare August 3, 2026 20:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant