feat(api): BZM2 diagnostic endpoints and hardware documentation - #71
feat(api): BZM2 diagnostic endpoints and hardware documentation#71recklessnode wants to merge 17 commits into
Conversation
|
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
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. |
66367ef to
3ff5d28
Compare
|
Series merge order (updated 2026-07-22) - four stacked PRs, all MERGEABLE, each green under
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. |
3ff5d28 to
908830a
Compare
…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>
908830a to
ffba625
Compare
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 aBoardCommandthrough the board's command channel (part 1's plumbing) with a 5 s reply timeout; boards without a command channel answer 400:POST dts-vs-queryPOST noop"BZ2"payloadPOST loopbackPOST register-read/register-writePOST clock-reportGET chain-summaryPOST discover-enginesRequest/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 fmtclean.🤖 Generated with Claude Code