Skip to content

Commit 5326952

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 cffaadc commit 5326952

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
@@ -28,15 +28,21 @@
2828
./buspirate_ctrl.py --flash ./result/ --no-reset # Flash without reboot
2929
./buspirate_ctrl.py --flash ./result/ --log # Flash, reset, print boot log
3030
31+
Bus Pirate itself (not the EC):
32+
./buspirate_ctrl.py --buspirate-reset # Reboot the BP5
33+
./buspirate_ctrl.py --buspirate-bootloader # Reboot BP5 into RP2 bootloader
34+
3135
Signal control (while --pty-bridge is running):
3236
kill -USR1 <pid> # Toggle EC reset
3337
kill -USR2 <pid> # Enter EC flash mode
3438
"""
3539

3640
import argparse
3741
import collections
42+
import contextlib
3843
import errno
3944
import fcntl
45+
import io
4046
import os
4147
import select
4248
import signal
@@ -324,8 +330,12 @@ def gpio_release_all(bp):
324330
# BP5 lifecycle
325331
# ---------------------------------------------------------------------------
326332

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

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

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

400413

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

557608
# Combinable flags
558609
parser.add_argument("--reset", action="store_true",
@@ -566,8 +617,17 @@ def main():
566617

567618
args = parser.parse_args()
568619

569-
if not (args.reset or args.reset_hold or args.pty_bridge or args.flash or args.log):
570-
parser.error("One of --reset, --reset-hold, --pty-bridge, --flash, or --log is required")
620+
# These reboot the BP5 itself, so nothing else can run afterwards
621+
bp_reboot = args.buspirate_reset or args.buspirate_bootloader
622+
623+
if not (args.reset or args.reset_hold or args.pty_bridge or args.flash
624+
or args.log or bp_reboot):
625+
parser.error("One of --reset, --reset-hold, --pty-bridge, --flash, --log, "
626+
"--buspirate-reset, or --buspirate-bootloader is required")
627+
628+
if bp_reboot and (args.reset or args.log or args.enter_flash_mode):
629+
parser.error("--buspirate-reset/--buspirate-bootloader cannot be combined "
630+
"with --reset, --log, or --enter-flash-mode")
571631

572632
# Find port
573633
binmode_port = args.port or find_bp5_binport()
@@ -578,7 +638,7 @@ def main():
578638
print(f"BP5 binmode: {binmode_port}")
579639

580640
# Setup
581-
bp = setup_bp5(binmode_port, debug=args.debug)
641+
bp = setup_bp5(binmode_port, debug=args.debug, configure=not bp_reboot)
582642

583643
# Install signal handler for clean shutdown
584644
original_sigint = signal.getsignal(signal.SIGINT)
@@ -590,6 +650,14 @@ def sigint_handler(signum, frame):
590650
signal.signal(signal.SIGINT, sigint_handler)
591651

592652
try:
653+
# BP5 self-reboot: nothing else applies, the device goes away
654+
if args.buspirate_reset:
655+
cmd_buspirate_reset(bp)
656+
return
657+
if args.buspirate_bootloader:
658+
cmd_buspirate_bootloader(bp)
659+
return
660+
593661
# Optional: enter flash mode before primary action
594662
if args.enter_flash_mode:
595663
print("Entering EC flash mode...")
@@ -610,8 +678,15 @@ def sigint_handler(signum, frame):
610678
except KeyboardInterrupt:
611679
print("\nInterrupted.")
612680
finally:
613-
gpio_release_all(bp)
614-
cleanup_bp5(bp)
681+
if bp_reboot:
682+
# Device is already gone — just drop the port
683+
try:
684+
bp.close()
685+
except Exception:
686+
pass
687+
else:
688+
gpio_release_all(bp)
689+
cleanup_bp5(bp)
615690

616691

617692
if __name__ == "__main__":

0 commit comments

Comments
 (0)