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+
3236Signal control (while --pty-bridge is running):
3337 kill -USR1 <pid> # Toggle EC reset
3438 kill -USR2 <pid> # Enter EC flash mode
3539"""
3640
3741import argparse
3842import collections
43+ import contextlib
3944import errno
4045import fcntl
46+ import io
4147import os
4248import select
4349import 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+
402449def 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 ("\n Interrupted." )
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
632707if __name__ == "__main__" :
0 commit comments