From 4ab504c0d80335cd46231b14abd9fccc96fb1698 Mon Sep 17 00:00:00 2001 From: Cryptskii <47649969+cryptskii@users.noreply.github.com> Date: Sun, 27 Sep 2026 15:03:53 -0400 Subject: [PATCH 1/2] fix(anchor): the chip-id halt names its cause and repeats it The firmware halted at boot with "[T1] chip id: FAIL" and nothing else: the libtropic error was discarded and the raw link probe's MISO bytes were thrown away. It printed once, before any host attached, so a phone saw only a board that took its USB connection and never answered. Both rig anchors were in this state after the phones were swapped, and nothing on the phone could say why. The halt now prints the library's error and the probe's MISO bytes, and repeats them every two seconds so a late host reads them. On the rig it read `ChipBusy; raw probe MISO [0, 0, 0, 0]`: the chip never drove MISO, which a powered, connected TROPIC01 does in every mode (a chip in alarm answers AlarmMode). The halt itself is unchanged. Built --release with bench-adopt-existing-chip (the profile the board ran), flashed with picotool, and read on a Mac: the line above, every two seconds. --- crates/dsm-anchor-pico/src/main.rs | 34 ++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/crates/dsm-anchor-pico/src/main.rs b/crates/dsm-anchor-pico/src/main.rs index 8c9283772..7a15c477f 100644 --- a/crates/dsm-anchor-pico/src/main.rs +++ b/crates/dsm-anchor-pico/src/main.rs @@ -221,7 +221,9 @@ struct ChipTropic<'a, SPI: SpiDevice, CS: OutputPin> { } impl Tropic for ChipTropic<'_, SPI, CS> { fn counter_get(&mut self) -> Result { - self.sess.mcounter_get(COUNTER).map_err(|_| TropicError::Comm) + self.sess + .mcounter_get(COUNTER) + .map_err(|_| TropicError::Comm) } fn counter_update(&mut self) -> Result<(), TropicError> { self.sess @@ -266,7 +268,9 @@ fn ensure_chip_key( } sess.ecc_key_generate(CHIP_KEY_SLOT.into(), EccCurve::Ed25519) .map_err(|_| "ecc_key_generate")?; - let res = sess.ecc_key_read(CHIP_KEY_SLOT.into()).map_err(|_| "ecc_key_read")?; + let res = sess + .ecc_key_read(CHIP_KEY_SLOT.into()) + .map_err(|_| "ecc_key_read")?; Ok(res.pub_key().to_vec()) } @@ -474,6 +478,10 @@ fn main() -> ! { // ---- Phase 1: raw link probe ~3s while USB enumerates ---- let probe_until = timer.get_counter().ticks() + 3_000_000; let mut last = timer.get_counter(); + // What the chip last put on MISO for the raw probe: the halt below reports + // it, so a silent bus (all 0x00 or all 0xFF) reads apart from a chip that + // answers but is refused. + let mut probe_rx = [0u8; 4]; while timer.get_counter().ticks() < probe_until { usb_dev.poll(&mut [&mut serial]); if (timer.get_counter() - last).to_millis() >= 1000 { @@ -481,6 +489,7 @@ fn main() -> ! { let mut rx = [0u8; 4]; let tx = [0xAAu8, 0, 0, 0]; let _ = spi_dev.transfer(&mut rx, &tx); + probe_rx = rx; let _ = serial.flush(); } } @@ -493,14 +502,22 @@ fn main() -> ! { let mut tropic = Tropic01::new(spi_dev); let chip_id_hash = match tropic.get_info_chip_id() { Ok(id) => anchor_core::hash::h("DSM/anchor/chip-id/v1", &[id]), - Err(_) => { - put( - &mut serial, - b"[T1] chip id: FAIL (no real identity; halting)\r\n", + Err(e) => { + // The halt names its cause, and repeats it: the banner is printed + // once at boot, before a phone or a Mac has attached to read it. + let why = alloc::format!( + "[T1] chip id: FAIL (no real identity; halting): {e:?}; raw probe MISO {probe_rx:?}\r\n" ); + put(&mut serial, why.as_bytes()); let _ = serial.flush(); + let mut last = timer.get_counter(); loop { usb_dev.poll(&mut [&mut serial]); + if (timer.get_counter() - last).to_millis() >= 2000 { + last = timer.get_counter(); + put(&mut serial, why.as_bytes()); + let _ = serial.flush(); + } } } }; @@ -610,7 +627,10 @@ fn main() -> ! { let (h0, b) = match enroll(&mut sess, &ident, &policy_hash) { Ok(v) => v, Err(_) => { - put(&mut serial, b"[T4] enroll FAIL (halting; no fallback identity)\r\n"); + put( + &mut serial, + b"[T4] enroll FAIL (halting; no fallback identity)\r\n", + ); let _ = serial.flush(); loop { usb_dev.poll(&mut [&mut serial]); From 9a617b13605a1347456199fdd1d23421671cdf34 Mon Sep 17 00:00:00 2001 From: Cryptskii <47649969+cryptskii@users.noreply.github.com> Date: Sun, 27 Sep 2026 15:04:25 -0400 Subject: [PATCH 2/2] chore(anchor): leave the three lines rustfmt reflowed as they were The previous commit ran rustfmt over the whole file, which reflowed three lines the change does not touch. They go back as they were, so the change is only the halt's diagnostic. --- crates/dsm-anchor-pico/src/main.rs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/crates/dsm-anchor-pico/src/main.rs b/crates/dsm-anchor-pico/src/main.rs index 7a15c477f..d9dcc5cb9 100644 --- a/crates/dsm-anchor-pico/src/main.rs +++ b/crates/dsm-anchor-pico/src/main.rs @@ -221,9 +221,7 @@ struct ChipTropic<'a, SPI: SpiDevice, CS: OutputPin> { } impl Tropic for ChipTropic<'_, SPI, CS> { fn counter_get(&mut self) -> Result { - self.sess - .mcounter_get(COUNTER) - .map_err(|_| TropicError::Comm) + self.sess.mcounter_get(COUNTER).map_err(|_| TropicError::Comm) } fn counter_update(&mut self) -> Result<(), TropicError> { self.sess @@ -268,9 +266,7 @@ fn ensure_chip_key( } sess.ecc_key_generate(CHIP_KEY_SLOT.into(), EccCurve::Ed25519) .map_err(|_| "ecc_key_generate")?; - let res = sess - .ecc_key_read(CHIP_KEY_SLOT.into()) - .map_err(|_| "ecc_key_read")?; + let res = sess.ecc_key_read(CHIP_KEY_SLOT.into()).map_err(|_| "ecc_key_read")?; Ok(res.pub_key().to_vec()) } @@ -627,10 +623,7 @@ fn main() -> ! { let (h0, b) = match enroll(&mut sess, &ident, &policy_hash) { Ok(v) => v, Err(_) => { - put( - &mut serial, - b"[T4] enroll FAIL (halting; no fallback identity)\r\n", - ); + put(&mut serial, b"[T4] enroll FAIL (halting; no fallback identity)\r\n"); let _ = serial.flush(); loop { usb_dev.poll(&mut [&mut serial]);