diff options
Diffstat (limited to 'tools/moot/mtldr')
-rwxr-xr-x | tools/moot/mtldr | 318 |
1 files changed, 318 insertions, 0 deletions
diff --git a/tools/moot/mtldr b/tools/moot/mtldr new file mode 100755 index 00000000..e7eb58cd --- /dev/null +++ b/tools/moot/mtldr @@ -0,0 +1,318 @@ +#! /usr/bin/env python + +""" + Copyright 2016 Google Inc. All Rights Reserved. + Author: gkalsi@google.com (Gurjant Kalsi) + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +""" +import argparse +import binascii +import logging +import struct +import time + +import usb.core +import usb.util + + +class Command: + flash = 0x01 + boot = 0x02 + devinfo = 0x03 + + +class DataPhaseType: + none = 0 + host_to_device = 1 + device_to_host = 2 + + +class Retcode: + # Normal operation + no_error = (0x00) + xmit_ready = (0x01) + recv_ready = (0x02) + + # Malformed reqeust + bad_data_len = (0xAAA0) + bad_magic = (0xAAA1) + unknown_command = (0xAAA2) + + # Device side system error. + sys_image_too_big = (0xFFF1) + err_open_sys_flash = (0xFFF2) + err_erase_sys_flash = (0xFFF3) + err_write_sys_flash = (0xFFF4) + cant_find_buildsig = (0xFFF5) + usb_read_error = (0xFFF6) + + +class CommandParam: + def __init__(self, data_phase_type): + self.data_phase_type = data_phase_type + +VENDOR_ID = 0x18D1 +PRODUCT_ID = 0xA010 +CLASS_VENDOR_SPECIFIC = 0xFF +SUBCLASS_MTLDR_DEBUG = 0x01 + +# create logger +logger = logging.getLogger('mtldr') +logger.setLevel(logging.INFO) + + +class FindByDeviceClass(object): + # Callable object that selects a USB device by a Sub/Device class pair + + def __init__(self, device, subdevice): + self._device_class = device + self._subdevice_class = subdevice + + def __call__(self, device): + for cfg in device: + intf = usb.util.find_descriptor( + cfg, bInterfaceClass=self._device_class, + bInterfaceSubClass=self._subdevice_class) + if intf: + return True + return False + + +class CommandDispatcher: + header_struct = struct.Struct("< i i i") + header_struct_len = 12 # bytes + + response_struct = struct.Struct("< i i i") + response_struct_len = 12 # bytes + + cmd_magic = 0x4d4f4f54 + resp_magic = 0x52455350 + + data_phase_timeout = 10000 # ms + + CommandParams = { + Command.flash: CommandParam(DataPhaseType.host_to_device), + Command.boot: CommandParam(DataPhaseType.none), + Command.devinfo: CommandParam(DataPhaseType.device_to_host), + } + + def __init__(self, ep_in, ep_out): + self._ep_in = ep_in + self._ep_out = ep_out + + def __read_response(self, timeout=None): + """ + reads a standard response from the USB device. + """ + if timeout: + resp = self._ep_in.read(CommandDispatcher.response_struct_len, timeout=timeout).tostring() + else: + resp = self._ep_in.read(CommandDispatcher.response_struct_len).tostring() + + logger.debug( + ("Read %d bytes: " % CommandDispatcher.response_struct_len) + + str(binascii.hexlify(resp)) + ) + + resp = CommandDispatcher.response_struct.unpack(resp) + if resp[0] != CommandDispatcher.resp_magic: + raise("Device responded with an unexpected magic value.") + + logger.debug( + ("Read Response - retcode = %d, nbytes = %d" % (resp[1], resp[2])) + ) + + return (resp[1], int(resp[2])) + + def __dispatch_no_data(self, command): + """ + Dispatches a command that has no data phase. + """ + logger.debug("Write %d bytes, command = %d" % (0, command)) + command = CommandDispatcher.header_struct.pack(CommandDispatcher.cmd_magic, command, 0) + self._ep_out.write(command) + + retcode, datalen = self.__read_response() + assert datalen == 0 # A command with no data can't have a datalen + + return (retcode, list()) + + def __dispatch_device_to_host(self, command): + """ + Dispatches a command that has a device to host data phase. + """ + logger.debug("Write %d bytes, command = %d" % (0, command)) + command = CommandDispatcher.header_struct.pack(CommandDispatcher.cmd_magic, command, 0) + self._ep_out.write(command) + + retcode, datalen = self.__read_response() + if retcode != Retcode.xmit_ready: + return (retcode, list()) + + logger.debug("Read %d bytes, retcode = %d" % (int(datalen), retcode)) + resp = self._ep_in.read(int(datalen), timeout=CommandDispatcher.data_phase_timeout) + + retcode, datalen = self.__read_response() + + return (retcode, resp) + + def __dispatch_host_to_device(self, command, data): + """ + Dispatches a command that has a host to device data phase. + """ + logger.debug("Write %d bytes, command = %d" % (len(data), command)) + # Tell the device that we're about to send it data. Also mention how + # much data we're about to send. + command = CommandDispatcher.header_struct.pack(CommandDispatcher.cmd_magic, command, len(data)) + self._ep_out.write(command) + + # The device will signal back to us that it's ready to read data. + retcode, datalen = self.__read_response(CommandDispatcher.data_phase_timeout) + assert datalen == 0 + + if retcode != Retcode.recv_ready: + # The device experienced an error and is not ready to receive data. + return (retcode, list()) + + # Write the data back to the device. + self._ep_out.write(data, timeout=CommandDispatcher.data_phase_timeout) + + # The device will reply to us to let us know whether or not it received + # our data correctly. + retcode, datalen = self.__read_response() + return (retcode, list()) + + def dispatch(self, command, data=None): + """ + Dispatches a command to the connected USB device. + + A command is composed of a command phase followed by an optional data + phase. If the data parameter is specified, dispatch(...) will also + attempt to send data to the device. + + Returns a 2-Tuple as follows: (command result, optional data). If the + device returned data during the data phase, optional data will contain + that data, otherwise it will be None. + """ + # Make sure the command actually exists. + params = CommandDispatcher.CommandParams.get(command) + if not params: + raise("Command " + str(command) + " does not exist.") + + if params.data_phase_type == DataPhaseType.none: + result = self.__dispatch_no_data(command) + elif params.data_phase_type == DataPhaseType.host_to_device: + result = self.__dispatch_host_to_device(command, data) + elif params.data_phase_type == DataPhaseType.device_to_host: + result = self.__dispatch_device_to_host(command) + + return result + + +def cmd_devinfo(dispatcher, args): + retcode, data = dispatcher.dispatch(Command.devinfo) + + if retcode != Retcode.no_error: + print ("Error %d while reading devinfo" % retcode) + else: + print data.tostring() + + +def cmd_flash(dispatcher, args): + with open(args.bin, 'rb') as file: + binary = file.read() + + retcode, data = dispatcher.dispatch(Command.flash, binary) + if retcode != Retcode.no_error: + print ("Error %d while flashing device" % retcode) + + + +def cmd_boot(dispatcher, args): + retcode, data = dispatcher.dispatch(Command.boot) + if retcode != Retcode.no_error: + print ("Error %d while booting device" % retcode) + + +def main(): + # Setup the Logger + ch = logging.StreamHandler() + ch.setLevel(logging.DEBUG) + formatter = logging.Formatter( + '%(asctime)s - %(name)s - %(levelname)s - %(message)s') + ch.setFormatter(formatter) + logger.addHandler(ch) + + # Setup the argument parser + parser = argparse.ArgumentParser(prog='PROG') + subparsers = parser.add_subparsers(help='sub-command help') + + devinfo_parser = subparsers.add_parser('devinfo', help='a help') + devinfo_parser.set_defaults(func=cmd_devinfo) + + flash_parser = subparsers.add_parser('flash', help='b help') + flash_parser.add_argument('bin', help="Path to the LK Binary to flash") + flash_parser.set_defaults(func=cmd_flash) + + boot_parser = subparsers.add_parser('boot', help='b help') + boot_parser.set_defaults(func=cmd_boot) + + args = parser.parse_args() + + logger.info("Waiting for device (vid=0x%04x, pid=0x%04x, " + "class=0x%02x, subclass=0x%02x)" % + (VENDOR_ID, PRODUCT_ID, CLASS_VENDOR_SPECIFIC, + SUBCLASS_MTLDR_DEBUG)) + while True: + dev = usb.core.find( + idVendor=VENDOR_ID, + idProduct=PRODUCT_ID, + custom_match=FindByDeviceClass(CLASS_VENDOR_SPECIFIC, + SUBCLASS_MTLDR_DEBUG)) + if dev: + break + time.sleep(0.5) + + logger.info("Found USB Device!") + dev.set_configuration() + + cfg = dev.get_active_configuration() + intf = usb.util.find_descriptor( + cfg, bInterfaceClass=CLASS_VENDOR_SPECIFIC, + bInterfaceSubClass=SUBCLASS_MTLDR_DEBUG) + + ep_out = usb.util.find_descriptor( + intf, + custom_match=lambda e: + usb.util.endpoint_direction(e.bEndpointAddress) == + usb.util.ENDPOINT_OUT) + ep_in = usb.util.find_descriptor( + intf, + custom_match=lambda e: + usb.util.endpoint_direction(e.bEndpointAddress) == + usb.util.ENDPOINT_IN) + + dispatcher = CommandDispatcher(ep_in, ep_out) + args.func(dispatcher, args) + + +if __name__ == "__main__": + main() |