2121import usb
2222import time
2323import tempfile
24+ from typing import Optional , Union
25+
2426from snagrecover import utils
2527from snagflash .android_sparse_file .utils import split
2628
3537for more information on fastboot support in U-Boot.
3638"""
3739
40+ FASTBOOT_UNSUPPORTED_CMD_RESPONSE = b"Unsupported command"
41+ FASTBOOT_UNRECOGNIZED_CMD_RESPONSE = b"unrecognized command"
42+
43+ CHECK_OEM_RUN_CMD_SUPPORT = "oem run:version\x00 "
44+
3845
3946class FastbootError (Exception ):
40- def __init__ (self , message ):
47+ def __init__ (self , message , data = None ):
4148 self .message = message
49+ self .data = data
4250 super ().__init__ (self .message )
4351
4452 def __str__ (self ):
@@ -86,40 +94,43 @@ def __init__(self, dev: usb.core.Device, timeout: int = 10000):
8694
8795 self .max_size = MAX_LIBUSB_TRANSFER_SIZE
8896
89- def cmd (self , packet : bytes ):
90- self .dev .write (self .ep_out , packet , timeout = self .timeout )
91- status = ""
92- t0 = time .time ()
93- while time .time () - t0 < 10 * self .timeout :
94- ret = self .dev .read (self .ep_in , 256 , timeout = self .timeout )
95- status = bytes (ret [:4 ])
96- if status == b"INFO" :
97- logger .debug (f"(bootloader) { bytes (ret [4 :256 ])} " )
98- elif status == b"TEXT" :
99- logger .debug (f"(bootloader) { bytes (ret [4 :256 ])} " , end = "" )
100- elif status == b"FAIL" :
101- raise FastbootError (f"Fastboot fail with message: { bytes (ret [4 :256 ])} " )
102- elif status == b"OKAY" :
103- logger .debug ("fastboot OKAY" )
104- return bytes (ret [4 :])
105- elif status == b"DATA" :
106- length = int ("0x" + (bytes (ret [4 :12 ]).decode ("ascii" )), base = 16 )
107- logger .debug (f"fastboot DATA length: { length } " )
108- return length
109- raise FastbootError ("Timeout while completing fastboot transaction" )
97+ # The support of OEM commands is depending on the configuration
98+ # u-boot was built with, so they need to be probed at runtime.
99+ # The command handler for oem run is actually ucmd in the sources,
100+ # therefore it's safe to use this as fallback.
101+ self .oem_run_basecmd = (
102+ "oem run" if self ._is_cmd_supported (CHECK_OEM_RUN_CMD_SUPPORT ) else "UCmd"
103+ )
110104
111- def response (self ):
105+ def _is_cmd_supported (self , cmd : str ) -> bool :
106+ try :
107+ self .cmd (cmd )
108+ except FastbootError as e :
109+ if e .data and FASTBOOT_UNSUPPORTED_CMD_RESPONSE in e .data :
110+ return False
111+ return True
112+
113+ def cmd (
114+ self , packet : Optional [bytes ] = None , loglevel = logging .DEBUG
115+ ) -> Union [bytes , int ]:
116+ if packet is not None :
117+ self .dev .write (self .ep_out , packet , timeout = self .timeout )
112118 t0 = time .time ()
113119 while time .time () - t0 < 10 * self .timeout :
114120 ret = self .dev .read (self .ep_in , 256 , timeout = self .timeout )
115121 status = bytes (ret [:4 ])
122+ data = bytes (ret [4 :256 ])
116123 if status in [b"INFO" , b"TEXT" ]:
117- logger .info ( f"(bootloader) { bytes ( ret [ 4 : 256 ]) } " , end = "" )
124+ logger .log ( loglevel , f"(bootloader) { data } " , end = "" )
118125 elif status == b"FAIL" :
119- raise FastbootError (f"Fastboot fail with message: { bytes ( ret [ 4 : 256 ]) } " )
126+ raise FastbootError (f"Fastboot fail with message: { data } " , data )
120127 elif status == b"OKAY" :
121- logger .info ("fastboot OKAY" )
122- return bytes (ret [4 :])
128+ logger .log (loglevel , "fastboot OKAY" )
129+ return data
130+ elif packet is not None and status == b"DATA" :
131+ length = int ("0x" + (data .decode ("ascii" )), base = 16 )
132+ logger .log (loglevel , f"fastboot DATA length: { length } " )
133+ return length
123134 raise FastbootError ("Timeout while completing fastboot transaction" )
124135
125136 def getvar (self , var : str ):
@@ -133,7 +144,7 @@ def send(self, blob: bytes, padding: int = 0):
133144 self .cmd (packet )
134145 for chunk in utils .dnload_iter (blob + b"\x00 " * padding , self .max_size ):
135146 self .dev .write (self .ep_out , chunk , timeout = self .timeout )
136- self .response ( )
147+ self .cmd ( loglevel = logging . INFO )
137148
138149 def download (self , path : str , padding : int = 0 ):
139150 with open (path , "rb" ) as file :
@@ -191,7 +202,7 @@ def oem_run(self, cmd: str):
191202 """
192203 Execute an arbitrary U-Boot command
193204 """
194- packet = f"oem run :{ cmd } \x00 "
205+ packet = f"{ self . oem_run_basecmd } :{ cmd } \x00 "
195206 self .cmd (packet )
196207
197208 def oem_format (self ):
0 commit comments