fastboot.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  1. # Copyright 2014 Google Inc. All rights reserved.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. """A libusb1-based fastboot implementation."""
  15. import binascii
  16. import collections
  17. import io
  18. import logging
  19. import os
  20. import struct
  21. from adb import common
  22. from adb import usb_exceptions
  23. _LOG = logging.getLogger('fastboot')
  24. DEFAULT_MESSAGE_CALLBACK = lambda m: logging.info('Got %s from device', m)
  25. FastbootMessage = collections.namedtuple( # pylint: disable=invalid-name
  26. 'FastbootMessage', ['message', 'header'])
  27. # From fastboot.c
  28. VENDORS = {0x18D1, 0x0451, 0x0502, 0x0FCE, 0x05C6, 0x22B8, 0x0955,
  29. 0x413C, 0x2314, 0x0BB4, 0x8087}
  30. CLASS = 0xFF
  31. SUBCLASS = 0x42
  32. PROTOCOL = 0x03
  33. # pylint: disable=invalid-name
  34. DeviceIsAvailable = common.InterfaceMatcher(CLASS, SUBCLASS, PROTOCOL)
  35. # pylint doesn't understand cross-module exception baseclasses.
  36. # pylint: disable=nonstandard-exception
  37. class FastbootTransferError(usb_exceptions.FormatMessageWithArgumentsException):
  38. """Transfer error."""
  39. class FastbootRemoteFailure(usb_exceptions.FormatMessageWithArgumentsException):
  40. """Remote error."""
  41. class FastbootStateMismatch(usb_exceptions.FormatMessageWithArgumentsException):
  42. """Fastboot and uboot's state machines are arguing. You Lose."""
  43. class FastbootInvalidResponse(
  44. usb_exceptions.FormatMessageWithArgumentsException):
  45. """Fastboot responded with a header we didn't expect."""
  46. class FastbootProtocol(object):
  47. """Encapsulates the fastboot protocol."""
  48. FINAL_HEADERS = {b'OKAY', b'DATA'}
  49. def __init__(self, usb, chunk_kb=1024):
  50. """Constructs a FastbootProtocol instance.
  51. Args:
  52. usb: UsbHandle instance.
  53. chunk_kb: Packet size. For older devices, 4 may be required.
  54. """
  55. self.usb = usb
  56. self.chunk_kb = chunk_kb
  57. @property
  58. def usb_handle(self):
  59. return self.usb
  60. def SendCommand(self, command, arg=None):
  61. """Sends a command to the device.
  62. Args:
  63. command: The command to send.
  64. arg: Optional argument to the command.
  65. """
  66. if arg is not None:
  67. if not isinstance(arg, bytes):
  68. arg = arg.encode('utf8')
  69. command = b'%s:%s' % (command, arg)
  70. self._Write(io.BytesIO(command), len(command))
  71. def HandleSimpleResponses(
  72. self, timeout_ms=None, info_cb=DEFAULT_MESSAGE_CALLBACK):
  73. """Accepts normal responses from the device.
  74. Args:
  75. timeout_ms: Timeout in milliseconds to wait for each response.
  76. info_cb: Optional callback for text sent from the bootloader.
  77. Returns:
  78. OKAY packet's message.
  79. """
  80. return self._AcceptResponses(b'OKAY', info_cb, timeout_ms=timeout_ms)
  81. def HandleDataSending(self, source_file, source_len,
  82. info_cb=DEFAULT_MESSAGE_CALLBACK,
  83. progress_callback=None, timeout_ms=None):
  84. """Handles the protocol for sending data to the device.
  85. Args:
  86. source_file: File-object to read from for the device.
  87. source_len: Amount of data, in bytes, to send to the device.
  88. info_cb: Optional callback for text sent from the bootloader.
  89. progress_callback: Callback that takes the current and the total progress
  90. of the current file.
  91. timeout_ms: Timeout in milliseconds to wait for each response.
  92. Raises:
  93. FastbootTransferError: When fastboot can't handle this amount of data.
  94. FastbootStateMismatch: Fastboot responded with the wrong packet type.
  95. FastbootRemoteFailure: Fastboot reported failure.
  96. FastbootInvalidResponse: Fastboot responded with an unknown packet type.
  97. Returns:
  98. OKAY packet's message.
  99. """
  100. accepted_size = self._AcceptResponses(
  101. b'DATA', info_cb, timeout_ms=timeout_ms)
  102. accepted_size = binascii.unhexlify(accepted_size[:8])
  103. accepted_size, = struct.unpack(b'>I', accepted_size)
  104. if accepted_size != source_len:
  105. raise FastbootTransferError(
  106. 'Device refused to download %s bytes of data (accepts %s bytes)',
  107. source_len, accepted_size)
  108. self._Write(source_file, accepted_size, progress_callback)
  109. return self._AcceptResponses(b'OKAY', info_cb, timeout_ms=timeout_ms)
  110. def _AcceptResponses(self, expected_header, info_cb, timeout_ms=None):
  111. """Accepts responses until the expected header or a FAIL.
  112. Args:
  113. expected_header: OKAY or DATA
  114. info_cb: Optional callback for text sent from the bootloader.
  115. timeout_ms: Timeout in milliseconds to wait for each response.
  116. Raises:
  117. FastbootStateMismatch: Fastboot responded with the wrong packet type.
  118. FastbootRemoteFailure: Fastboot reported failure.
  119. FastbootInvalidResponse: Fastboot responded with an unknown packet type.
  120. Returns:
  121. OKAY packet's message.
  122. """
  123. while True:
  124. response = self.usb.BulkRead(64, timeout_ms=timeout_ms)
  125. header = bytes(response[:4])
  126. remaining = bytes(response[4:])
  127. if header == b'INFO':
  128. info_cb(FastbootMessage(remaining, header))
  129. elif header in self.FINAL_HEADERS:
  130. if header != expected_header:
  131. raise FastbootStateMismatch(
  132. 'Expected %s, got %s', expected_header, header)
  133. if header == b'OKAY':
  134. info_cb(FastbootMessage(remaining, header))
  135. return remaining
  136. elif header == b'FAIL':
  137. info_cb(FastbootMessage(remaining, header))
  138. raise FastbootRemoteFailure('FAIL: %s', remaining)
  139. else:
  140. raise FastbootInvalidResponse(
  141. 'Got unknown header %s and response %s', header, remaining)
  142. def _HandleProgress(self, total, progress_callback):
  143. """Calls the callback with the current progress and total ."""
  144. current = 0
  145. while True:
  146. current += yield
  147. try:
  148. progress_callback(current, total)
  149. except Exception: # pylint: disable=broad-except
  150. _LOG.exception('Progress callback raised an exception. %s',
  151. progress_callback)
  152. continue
  153. def _Write(self, data, length, progress_callback=None):
  154. """Sends the data to the device, tracking progress with the callback."""
  155. if progress_callback:
  156. progress = self._HandleProgress(length, progress_callback)
  157. next(progress)
  158. while length:
  159. tmp = data.read(self.chunk_kb * 1024)
  160. length -= len(tmp)
  161. self.usb.BulkWrite(tmp)
  162. if progress_callback and progress:
  163. progress.send(len(tmp))
  164. class FastbootCommands(object):
  165. """Encapsulates the fastboot commands."""
  166. def __init__(self):
  167. """Constructs a FastbootCommands instance.
  168. Args:
  169. usb: UsbHandle instance.
  170. """
  171. self.__reset()
  172. def __reset(self):
  173. self._handle = None
  174. self._protocol = None
  175. @property
  176. def usb_handle(self):
  177. return self._handle
  178. def Close(self):
  179. self._handle.Close()
  180. def ConnectDevice(self, port_path=None, serial=None, default_timeout_ms=None, chunk_kb=1024, **kwargs):
  181. """Convenience function to get an adb device from usb path or serial.
  182. Args:
  183. port_path: The filename of usb port to use.
  184. serial: The serial number of the device to use.
  185. default_timeout_ms: The default timeout in milliseconds to use.
  186. chunk_kb: Amount of data, in kilobytes, to break fastboot packets up into
  187. kwargs: handle: Device handle to use (instance of common.TcpHandle or common.UsbHandle)
  188. banner: Connection banner to pass to the remote device
  189. rsa_keys: List of AuthSigner subclass instances to be used for
  190. authentication. The device can either accept one of these via the Sign
  191. method, or we will send the result of GetPublicKey from the first one
  192. if the device doesn't accept any of them.
  193. auth_timeout_ms: Timeout to wait for when sending a new public key. This
  194. is only relevant when we send a new public key. The device shows a
  195. dialog and this timeout is how long to wait for that dialog. If used
  196. in automation, this should be low to catch such a case as a failure
  197. quickly; while in interactive settings it should be high to allow
  198. users to accept the dialog. We default to automation here, so it's low
  199. by default.
  200. If serial specifies a TCP address:port, then a TCP connection is
  201. used instead of a USB connection.
  202. """
  203. if 'handle' in kwargs:
  204. self._handle = kwargs['handle']
  205. else:
  206. self._handle = common.UsbHandle.FindAndOpen(
  207. DeviceIsAvailable, port_path=port_path, serial=serial,
  208. timeout_ms=default_timeout_ms)
  209. self._protocol = FastbootProtocol(self._handle, chunk_kb)
  210. return self
  211. @classmethod
  212. def Devices(cls):
  213. """Get a generator of UsbHandle for devices available."""
  214. return common.UsbHandle.FindDevices(DeviceIsAvailable)
  215. def _SimpleCommand(self, command, arg=None, **kwargs):
  216. self._protocol.SendCommand(command, arg)
  217. return self._protocol.HandleSimpleResponses(**kwargs)
  218. def FlashFromFile(self, partition, source_file, source_len=0,
  219. info_cb=DEFAULT_MESSAGE_CALLBACK, progress_callback=None):
  220. """Flashes a partition from the file on disk.
  221. Args:
  222. partition: Partition name to flash to.
  223. source_file: Filename to download to the device.
  224. source_len: Optional length of source_file, uses os.stat if not provided.
  225. info_cb: See Download.
  226. progress_callback: See Download.
  227. Returns:
  228. Download and flash responses, normally nothing.
  229. """
  230. if source_len == 0:
  231. # Fall back to stat.
  232. source_len = os.stat(source_file).st_size
  233. download_response = self.Download(
  234. source_file, source_len=source_len, info_cb=info_cb,
  235. progress_callback=progress_callback)
  236. flash_response = self.Flash(partition, info_cb=info_cb)
  237. return download_response + flash_response
  238. def Download(self, source_file, source_len=0,
  239. info_cb=DEFAULT_MESSAGE_CALLBACK, progress_callback=None):
  240. """Downloads a file to the device.
  241. Args:
  242. source_file: A filename or file-like object to download to the device.
  243. source_len: Optional length of source_file. If source_file is a file-like
  244. object and source_len is not provided, source_file is read into
  245. memory.
  246. info_cb: Optional callback accepting FastbootMessage for text sent from
  247. the bootloader.
  248. progress_callback: Optional callback called with the percent of the
  249. source_file downloaded. Note, this doesn't include progress of the
  250. actual flashing.
  251. Returns:
  252. Response to a download request, normally nothing.
  253. """
  254. if isinstance(source_file, str):
  255. source_len = os.stat(source_file).st_size
  256. source_file = open(source_file)
  257. with source_file:
  258. if source_len == 0:
  259. # Fall back to storing it all in memory :(
  260. data = source_file.read()
  261. source_file = io.BytesIO(data.encode('utf8'))
  262. source_len = len(data)
  263. self._protocol.SendCommand(b'download', b'%08x' % source_len)
  264. return self._protocol.HandleDataSending(
  265. source_file, source_len, info_cb, progress_callback=progress_callback)
  266. def Flash(self, partition, timeout_ms=0, info_cb=DEFAULT_MESSAGE_CALLBACK):
  267. """Flashes the last downloaded file to the given partition.
  268. Args:
  269. partition: Partition to overwrite with the new image.
  270. timeout_ms: Optional timeout in milliseconds to wait for it to finish.
  271. info_cb: See Download. Usually no messages.
  272. Returns:
  273. Response to a download request, normally nothing.
  274. """
  275. return self._SimpleCommand(b'flash', arg=partition, info_cb=info_cb,
  276. timeout_ms=timeout_ms)
  277. def Erase(self, partition, timeout_ms=None):
  278. """Erases the given partition.
  279. Args:
  280. partition: Partition to clear.
  281. """
  282. self._SimpleCommand(b'erase', arg=partition, timeout_ms=timeout_ms)
  283. def Getvar(self, var, info_cb=DEFAULT_MESSAGE_CALLBACK):
  284. """Returns the given variable's definition.
  285. Args:
  286. var: A variable the bootloader tracks. Use 'all' to get them all.
  287. info_cb: See Download. Usually no messages.
  288. Returns:
  289. Value of var according to the current bootloader.
  290. """
  291. return self._SimpleCommand(b'getvar', arg=var, info_cb=info_cb)
  292. def Oem(self, command, timeout_ms=None, info_cb=DEFAULT_MESSAGE_CALLBACK):
  293. """Executes an OEM command on the device.
  294. Args:
  295. command: Command to execute, such as 'poweroff' or 'bootconfig read'.
  296. timeout_ms: Optional timeout in milliseconds to wait for a response.
  297. info_cb: See Download. Messages vary based on command.
  298. Returns:
  299. The final response from the device.
  300. """
  301. if not isinstance(command, bytes):
  302. command = command.encode('utf8')
  303. return self._SimpleCommand(
  304. b'oem %s' % command, timeout_ms=timeout_ms, info_cb=info_cb)
  305. def Continue(self):
  306. """Continues execution past fastboot into the system."""
  307. return self._SimpleCommand(b'continue')
  308. def Reboot(self, target_mode=b'', timeout_ms=None):
  309. """Reboots the device.
  310. Args:
  311. target_mode: Normal reboot when unspecified. Can specify other target
  312. modes such as 'recovery' or 'bootloader'.
  313. timeout_ms: Optional timeout in milliseconds to wait for a response.
  314. Returns:
  315. Usually the empty string. Depends on the bootloader and the target_mode.
  316. """
  317. return self._SimpleCommand(
  318. b'reboot', arg=target_mode or None, timeout_ms=timeout_ms)
  319. def RebootBootloader(self, timeout_ms=None):
  320. """Reboots into the bootloader, usually equiv to Reboot('bootloader')."""
  321. return self._SimpleCommand(b'reboot-bootloader', timeout_ms=timeout_ms)