Skip to content

feat(asic): Intel BZM2 (Bonanza Mine 2) ASIC family support - #69

Open
recklessnode wants to merge 9 commits into
256foundation:mainfrom
recklessnode:bzm2/pr2-asic-core
Open

feat(asic): Intel BZM2 (Bonanza Mine 2) ASIC family support#69
recklessnode wants to merge 9 commits into
256foundation:mainfrom
recklessnode:bzm2/pr2-asic-core

Conversation

@recklessnode

@recklessnode recklessnode commented Jun 11, 2026

Copy link
Copy Markdown

Part 2 of the BZM2 series (stacked on part 1 — review the feat(asic) commit). Adds the ASIC-family layer for the Intel BZM2 (Bonanza Mine 2), the chip the 256 Foundation received as a 54,000-unit grant and that the Satoshi Starter / bitaxeBIRDS board family is built around.

The layout mirrors the in-tree bm13xx structure (pure codec → controller → HashThread):

  • protocol.rs — opcodes, frame encoders (write_job, write_register, noop, loopback, read_result), TDM frame/result parsers, the 20×12 logical engine grid (236 active engines by default), target→leading-zeros conversion. Depends on bitcoin + std only.
  • uart.rsBzm2UartController for the chip's 9-bit multidrop UART: register R/W (unicast/multicast/local), NOOP "BZ2" verification, chain enumeration (ID-assignment walk from the 0xFA power-on default), TDM enable and synchronized reads, engine-map discovery, loopback, and DTS/VS (on-die temperature/voltage sensor) configure + query.
  • clock.rsBzm2ClockController: PLL/DLL program/enable/lock-wait, per-ASIC and broadcast, plus clock debug reports.
  • thread.rsBzm2Thread implementing HashThread: direct UART work dispatch, TDM result handling with per-ASIC/per-PLL hashrate estimation, DTS/VS telemetry emitted via HashThreadEvent::TelemetryUpdate (from part 1), thermal-trip frame handling, and a diagnostics handle used by the board layer in part 3.

Unlike the BM13xx family, the BZM2 is publicly documented — the protocol reference and integration guide land with part 4 (CC-BY-SA), so this driver can be reviewed against the chip's actual documentation rather than reverse engineering.

Tests are PTY-based chain emulations (multi-ASIC enumeration, TDM result paths, DTS/VS frames), gated #[cfg(all(test, unix))] so non-Unix hosts build the crate cleanly.

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

🤖 Generated with Claude Code


Serial-robustness hardening (added 2026-07-22)

An internal adversarial audit of this PR's serial parser/actor against hostile and malformed
multidrop-UART input surfaced five defects in the code this PR introduces. They are fixed here
as discrete, individually-tested fix(bzm2): commits on top of the feat(asic) commit, so the ASIC
layer is robust on a noisy real-world bus from the first commit rather than being hardened in a later
PR. Each is independently reviewable and CI-green per commit.

  • Parser hang + unbounded buffer on line noise (protocol.rs): a READREG-looking prefix with
    no pending read wedged framing at cursor 0 and grew the buffer on every read. Now resyncs one byte
    forward (matching the unknown-opcode arm), keeping strictly-forward progress and a bounded buffer.
  • Silent drop of fault frames for out-of-range ASIC ids (protocol.rs): the asic >= 100 resync
    heuristic dropped DTS/VS trip/fault frames too — reachable via MUJINA_BZM2_ENUM_START_ID, which
    silently disabled over-temp protection for the bus. DTS/VS frames are now exempt from the heuristic.
  • Unbounded diagnostic + enumeration reads (thread.rs, uart.rs): bare read_exact in the
    diagnostic handlers and in enumerate_chain's post-assign verify let a silent chip freeze the
    single-task actor (including Shutdown) or wedge board init. Both are now time-bounded.
  • DTS/VS trip acted on without validation (thread.rs): a single frame with a fault bit set
    killed the thread permanently — DoS-by-noise. Fault decisions are now gated on the matching
    sensor-enable bit; a genuine over-temp still stops immediately (no debounce). Residual risk noted
    in a code comment (the protocol carries no checksum).

Two lower-severity findings (generation-mismatch mis-framing; 1-bit nonce-replay double-count) are
deferred with rationale — both would need invasive changes that risk dropping valid frames/shares,
and the safety-relevant consequence of the first is already mitigated by the DTS/VS enable-gate.
A scheduler thread-respawn observation is upstream's own code and out of scope for this series.

@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

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 and others added 9 commits August 1, 2026 09:26
…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>
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