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