Skip to content

Commit 3aad0f3

Browse files
committed
buspirate_ctrl: Allow reset and enter bootloader
./buspirate_ctrl.py --buspirate-reset ./buspirate_ctrl.py --buspirate-bootloader Signed-off-by: Daniel Schaefer <dhs@frame.work>
1 parent 81b7ccf commit 3aad0f3

1 file changed

Lines changed: 82 additions & 7 deletions

File tree

scripts/buspirate_ctrl.py

Lines changed: 82 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,21 @@
2929
./buspirate_ctrl.py --flash ./result/ --no-reset # Flash without reboot
3030
./buspirate_ctrl.py --flash ./result/ --log # Flash, reset, print boot log
3131
32+
Bus Pirate itself (not the EC):
33+
./buspirate_ctrl.py --buspirate-reset # Reboot the BP5
34+
./buspirate_ctrl.py --buspirate-bootloader # Reboot BP5 into RP2 bootloader
35+
3236
Signal control (while --pty-bridge is running):
3337
kill -USR1 <pid> # Toggle EC reset
3438
kill -USR2 <pid> # Enter EC flash mode
3539
"""
3640

3741
import argparse
3842
import collections
43+
import contextlib
3944
import errno
4045
import fcntl
46+
import io
4147
import os
4248
import select
4349
import signal
@@ -325,8 +331,12 @@ def gpio_release_all(bp):
325331
# BP5 lifecycle
326332
# ---------------------------------------------------------------------------
327333

328-
def setup_bp5(port, debug=False):
329-
"""Open BPIO client, verify connection, enable PSU and UART."""
334+
def setup_bp5(port, debug=False, configure=True):
335+
"""Open BPIO client, verify connection, and (optionally) enable PSU and UART.
336+
337+
configure=False stops after the version handshake — used by the
338+
--buspirate-* commands, which only reboot the BP5.
339+
"""
330340
bp = BPIOClient(port, debug=debug)
331341

332342
# Set write timeout so we don't block forever if BP5 isn't responding
@@ -361,6 +371,9 @@ def setup_bp5(port, debug=False):
361371
fw_min = st.get('version_firmware_minor', 0)
362372
print(f"Connected: FW v{fw_maj}.{fw_min}")
363373

374+
if not configure:
375+
return bp
376+
364377
print("Enabling PSU (3.3V for IO buffers)...")
365378
bp.configuration_request(psu_enable=True, psu_set_mv=3300)
366379
time.sleep(0.2)
@@ -399,6 +412,40 @@ def cmd_reset_hold(bp):
399412
print("EC held in reset. Run --reset to release.")
400413

401414

415+
def bpio_fire_and_forget(bp, **kwargs):
416+
"""Send a configuration request the BP5 will never answer.
417+
418+
hardware_reset / hardware_bootloader take the MCU down inside the
419+
request handler, before the ConfigurationResponse is built, so the
420+
USB device just disappears instead of acking. Shorten the timeout
421+
and swallow pybpio's "Timeout waiting for response" complaint.
422+
"""
423+
prev_timeout = bp.timeout
424+
bp.timeout = 0.5
425+
try:
426+
with contextlib.redirect_stdout(io.StringIO()):
427+
bp.configuration_request(**kwargs)
428+
except Exception:
429+
pass # Device dropped off the bus mid-write — expected
430+
finally:
431+
bp.timeout = prev_timeout
432+
433+
434+
def cmd_buspirate_reset(bp):
435+
"""Hardware reset the Bus Pirate itself (not the EC)."""
436+
print("Resetting Bus Pirate...")
437+
bpio_fire_and_forget(bp, hardware_reset=True)
438+
print("Reset sent. The BP5 will re-enumerate over USB in a few seconds.")
439+
440+
441+
def cmd_buspirate_bootloader(bp):
442+
"""Reboot the Bus Pirate into the RP2 UF2 bootloader."""
443+
print("Rebooting Bus Pirate into bootloader...")
444+
bpio_fire_and_forget(bp, hardware_bootloader=True)
445+
print("Bootloader entered. The BP5 should appear as an RPI-RP2 mass\n"
446+
"storage device; copy the .uf2 firmware onto it to update.")
447+
448+
402449
def cmd_pty_bridge(bp, debug=False, reset_after_start=False):
403450
"""Run PTY bridge until Ctrl+C. Supports signal-based control.
404451
@@ -568,6 +615,10 @@ def main():
568615
help="Full flash workflow with uartupdatetool. "
569616
"PATH is a build dir with ec.bin + npcx_monitor.bin, "
570617
"or a single ec.bin file (monitor taken from script dir)")
618+
group.add_argument("--buspirate-reset", action="store_true",
619+
help="Hardware reset the Bus Pirate itself (not the EC)")
620+
group.add_argument("--buspirate-bootloader", action="store_true",
621+
help="Reboot the Bus Pirate into its UF2 bootloader")
571622

572623
# Combinable flags
573624
parser.add_argument("--reset", action="store_true",
@@ -581,8 +632,17 @@ def main():
581632

582633
args = parser.parse_args()
583634

584-
if not (args.reset or args.reset_hold or args.pty_bridge or args.flash or args.log):
585-
parser.error("One of --reset, --reset-hold, --pty-bridge, --flash, or --log is required")
635+
# These reboot the BP5 itself, so nothing else can run afterwards
636+
bp_reboot = args.buspirate_reset or args.buspirate_bootloader
637+
638+
if not (args.reset or args.reset_hold or args.pty_bridge or args.flash
639+
or args.log or bp_reboot):
640+
parser.error("One of --reset, --reset-hold, --pty-bridge, --flash, --log, "
641+
"--buspirate-reset, or --buspirate-bootloader is required")
642+
643+
if bp_reboot and (args.reset or args.log or args.enter_flash_mode):
644+
parser.error("--buspirate-reset/--buspirate-bootloader cannot be combined "
645+
"with --reset, --log, or --enter-flash-mode")
586646

587647
# Find port
588648
binmode_port = args.port or find_bp5_binport()
@@ -593,7 +653,7 @@ def main():
593653
print(f"BP5 binmode: {binmode_port}")
594654

595655
# Setup
596-
bp = setup_bp5(binmode_port, debug=args.debug)
656+
bp = setup_bp5(binmode_port, debug=args.debug, configure=not bp_reboot)
597657

598658
# Install signal handler for clean shutdown
599659
original_sigint = signal.getsignal(signal.SIGINT)
@@ -605,6 +665,14 @@ def sigint_handler(signum, frame):
605665
signal.signal(signal.SIGINT, sigint_handler)
606666

607667
try:
668+
# BP5 self-reboot: nothing else applies, the device goes away
669+
if args.buspirate_reset:
670+
cmd_buspirate_reset(bp)
671+
return
672+
if args.buspirate_bootloader:
673+
cmd_buspirate_bootloader(bp)
674+
return
675+
608676
# Optional: enter flash mode before primary action
609677
if args.enter_flash_mode:
610678
print("Entering EC flash mode...")
@@ -625,8 +693,15 @@ def sigint_handler(signum, frame):
625693
except KeyboardInterrupt:
626694
print("\nInterrupted.")
627695
finally:
628-
gpio_release_all(bp)
629-
cleanup_bp5(bp)
696+
if bp_reboot:
697+
# Device is already gone — just drop the port
698+
try:
699+
bp.close()
700+
except Exception:
701+
pass
702+
else:
703+
gpio_release_all(bp)
704+
cleanup_bp5(bp)
630705

631706

632707
if __name__ == "__main__":

0 commit comments

Comments
 (0)