diff options
Diffstat (limited to 'extra/usb_updater')
-rw-r--r-- | extra/usb_updater/Makefile | 9 | ||||
-rw-r--r-- | extra/usb_updater/desc_parser.c | 13 | ||||
-rw-r--r-- | extra/usb_updater/desc_parser.h | 2 | ||||
-rwxr-xr-x | extra/usb_updater/fw_update.py | 779 | ||||
-rw-r--r-- | extra/usb_updater/sample_descriptor | 2 | ||||
-rwxr-xr-x | extra/usb_updater/servo_updater.py | 799 | ||||
-rw-r--r-- | extra/usb_updater/usb_updater2.c | 272 |
7 files changed, 967 insertions, 909 deletions
diff --git a/extra/usb_updater/Makefile b/extra/usb_updater/Makefile index 1dfbc55645..5a8dc82c28 100644 --- a/extra/usb_updater/Makefile +++ b/extra/usb_updater/Makefile @@ -1,4 +1,4 @@ -# Copyright 2015 The Chromium OS Authors. All rights reserved. +# Copyright 2015 The ChromiumOS Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -19,10 +19,10 @@ CFLAGS := -std=gnu99 \ -Wredundant-decls \ -Wmissing-declarations -ifeq (DEBUG,) -CFLAGS += -O3 -else +ifneq ($(DEBUG),) CFLAGS += -O0 +else +CFLAGS += -O3 endif # @@ -52,4 +52,3 @@ clean: parser_debug: desc_parser.c gcc -g -O0 -DTEST_PARSER desc_parser.c -o dp - diff --git a/extra/usb_updater/desc_parser.c b/extra/usb_updater/desc_parser.c index 5bd996bdda..7e9f583902 100644 --- a/extra/usb_updater/desc_parser.c +++ b/extra/usb_updater/desc_parser.c @@ -1,5 +1,5 @@ /* - * Copyright 2018 The Chromium OS Authors. All rights reserved. + * Copyright 2018 The ChromiumOS Authors * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ @@ -75,8 +75,7 @@ static int get_next_token(char *input, size_t expected_size, char **output) next_colon = strchr(input, ':'); if (next_colon) *next_colon = '\0'; - if (!next_colon || (expected_size && - strlen(input) != expected_size)) { + if (!next_colon || (expected_size && strlen(input) != expected_size)) { fprintf(stderr, "Invalid entry in section %d\n", section_count_); return -EINVAL; @@ -98,16 +97,15 @@ static int get_hex_value(char *input, char **output) value = strtol(input, &e, 16); if ((e && *e) || (strlen(input) > 8)) { - fprintf(stderr, "Invalid hex value %s in section %d\n", - input, section_count_); + fprintf(stderr, "Invalid hex value %s in section %d\n", input, + section_count_); return -EINVAL; } return value; } -static int parse_range(char *next_line, - size_t line_len, +static int parse_range(char *next_line, size_t line_len, struct addr_range *parsed_range) { char *line_cursor; @@ -299,7 +297,6 @@ int parser_get_next_range(struct addr_range **range) *range = new_range; return 0; - } int parser_find_board(const char *hash_file_name, const char *board_id) diff --git a/extra/usb_updater/desc_parser.h b/extra/usb_updater/desc_parser.h index faa80d1a63..e459927b57 100644 --- a/extra/usb_updater/desc_parser.h +++ b/extra/usb_updater/desc_parser.h @@ -1,5 +1,5 @@ /* - * Copyright 2018 The Chromium OS Authors. All rights reserved. + * Copyright 2018 The ChromiumOS Authors * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ diff --git a/extra/usb_updater/fw_update.py b/extra/usb_updater/fw_update.py index 0d7a570fc3..a77de94a7c 100755 --- a/extra/usb_updater/fw_update.py +++ b/extra/usb_updater/fw_update.py @@ -1,11 +1,7 @@ #!/usr/bin/env python -# Copyright 2016 The Chromium OS Authors. All rights reserved. +# Copyright 2016 The ChromiumOS Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -# -# Ignore indention messages, since legacy scripts use 2 spaces instead of 4. -# pylint: disable=bad-indentation,docstring-section-indent -# pylint: disable=docstring-trailing-quotes # Upload firmware over USB # Note: This is a py2/3 compatible file. @@ -20,407 +16,436 @@ import struct import sys import time from pprint import pprint -import usb +import usb # pylint:disable=import-error +from ecusb.stm32usb import SusbError debug = False -def debuglog(msg): - if debug: - print(msg) - -def log(msg): - print(msg) - sys.stdout.flush() - - -"""Sends firmware update to CROS EC usb endpoint.""" - -class Supdate(object): - """Class to access firmware update endpoints. - - Usage: - d = Supdate() - - Instance Variables: - _dev: pyUSB device object - _read_ep: pyUSB read endpoint for this interface - _write_ep: pyUSB write endpoint for this interface - """ - USB_SUBCLASS_GOOGLE_UPDATE = 0x53 - USB_CLASS_VENDOR = 0xFF - def __init__(self): - pass +def debuglog(msg): + if debug: + print(msg) - def connect_usb(self, serialname=None): - """Initial discovery and connection to USB endpoint. - - This searches for a USB device matching the VID:PID specified - in the config file, optionally matching a specified serialname. - - Args: - serialname: Find the device with this serial, in case multiple - devices are attached. - - Returns: - True on success. - Raises: - Exception on error. - """ - # Find the stm32. - vendor = self._brdcfg['vid'] - product = self._brdcfg['pid'] - - dev_g = usb.core.find(idVendor=vendor, idProduct=product, find_all=True) - dev_list = list(dev_g) - if dev_list is None: - raise Exception("Update", "USB device not found") - - # Check if we have multiple stm32s and we've specified the serial. - dev = None - if serialname: - for d in dev_list: - if usb.util.get_string(d, d.iSerialNumber) == serialname: - dev = d - break - if dev is None: - raise SusbError("USB device(%s) not found" % serialname) - else: - try: - dev = dev_list[0] - except: - dev = dev_list.next() - - debuglog("Found stm32: %04x:%04x" % (vendor, product)) - self._dev = dev - - # Get an endpoint instance. - try: - dev.set_configuration() - except: - pass - cfg = dev.get_active_configuration() - - intf = usb.util.find_descriptor(cfg, custom_match=lambda i: \ - i.bInterfaceClass==self.USB_CLASS_VENDOR and \ - i.bInterfaceSubClass==self.USB_SUBCLASS_GOOGLE_UPDATE) - - self._intf = intf - debuglog("Interface: %s" % intf) - debuglog("InterfaceNumber: %s" % intf.bInterfaceNumber) - - read_ep = usb.util.find_descriptor( - intf, - # match the first IN endpoint - custom_match = \ - lambda e: \ - usb.util.endpoint_direction(e.bEndpointAddress) == \ - usb.util.ENDPOINT_IN - ) - - self._read_ep = read_ep - debuglog("Reader endpoint: 0x%x" % read_ep.bEndpointAddress) - - write_ep = usb.util.find_descriptor( - intf, - # match the first OUT endpoint - custom_match = \ - lambda e: \ - usb.util.endpoint_direction(e.bEndpointAddress) == \ - usb.util.ENDPOINT_OUT - ) - - self._write_ep = write_ep - debuglog("Writer endpoint: 0x%x" % write_ep.bEndpointAddress) - - return True - - - def wr_command(self, write_list, read_count=1, wtimeout=100, rtimeout=2000): - """Write command to logger logic.. - - This function writes byte command values list to stm, then reads - byte status. - - Args: - write_list: list of command byte values [0~255]. - read_count: number of status byte values to read. - wtimeout: mS to wait for write success - rtimeout: mS to wait for read success - - Returns: - status byte, if one byte is read, - byte list, if multiple bytes are read, - None, if no bytes are read. - - Interface: - write: [command, data ... ] - read: [status ] - """ - debuglog("wr_command(write_list=[%s] (%d), read_count=%s)" % ( - list(bytearray(write_list)), len(write_list), read_count)) - - # Clean up args from python style to correct types. - write_length = 0 - if write_list: - write_length = len(write_list) - if not read_count: - read_count = 0 - - # Send command to stm32. - if write_list: - cmd = write_list - ret = self._write_ep.write(cmd, wtimeout) - debuglog("RET: %s " % ret) - - # Read back response if necessary. - if read_count: - bytesread = self._read_ep.read(512, rtimeout) - debuglog("BYTES: [%s]" % bytesread) - - if len(bytesread) != read_count: - debuglog("Unexpected bytes read: %d, expected: %d" % (len(bytesread), read_count)) - pass - - debuglog("STATUS: 0x%02x" % int(bytesread[0])) - if read_count == 1: - return bytesread[0] - else: - return bytesread - - return None - - def stop(self): - """Finalize system flash and exit.""" - cmd = struct.pack(">I", 0xB007AB1E) - read = self.wr_command(cmd, read_count=4) - if len(read) == 4: - log("Finished flashing") - return +def log(msg): + print(msg) + sys.stdout.flush() - raise Exception("Update", "Stop failed [%s]" % read) +"""Sends firmware update to CROS EC usb endpoint.""" - def write_file(self): - """Write the update region packet by packet to USB - This sends write packets of size 128B out, in 32B chunks. - Overall, this will write all data in the inactive code region. +class Supdate(object): + """Class to access firmware update endpoints. - Raises: - Exception if write failed or address out of bounds. - """ - region = self._region - flash_base = self._brdcfg["flash"] - offset = self._base - flash_base - if offset != self._brdcfg['regions'][region][0]: - raise Exception("Update", "Region %s offset 0x%x != available offset 0x%x" % ( - region, self._brdcfg['regions'][region][0], offset)) - - length = self._brdcfg['regions'][region][1] - log("Sending") - - # Go to the correct region in the ec.bin file. - self._binfile.seek(offset) - - # Send 32 bytes at a time. Must be less than the endpoint's max packet size. - maxpacket = 32 - - # While data is left, create update packets. - while length > 0: - # Update packets are 128B. We can use any number - # but the micro must malloc this memory. - pagesize = min(length, 128) - - # Packet is: - # packet size: page bytes transferred plus 3 x 32b values header. - # cmd: n/a - # base: flash address to write this packet. - # data: 128B of data to write into flash_base - cmd = struct.pack(">III", pagesize + 12, 0, offset + flash_base) - read = self.wr_command(cmd, read_count=0) - - # Push 'todo' bytes out the pipe. - todo = pagesize - while todo > 0: - packetsize = min(maxpacket, todo) - data = self._binfile.read(packetsize) - if len(data) != packetsize: - raise Exception("Update", "No more data from file") - for i in range(0, 10): - try: - self.wr_command(data, read_count=0) - break - except: - log("Timeout fail") - todo -= packetsize - # Done with this packet, move to the next one. - length -= pagesize - offset += pagesize - - # Validate that the micro thinks it successfully wrote the data. - read = self.wr_command(''.encode(), read_count=4) - result = struct.unpack("<I", read) - result = result[0] - if result != 0: - raise Exception("Update", "Upload failed with rc: 0x%x" % result) - - - def start(self): - """Start a transaction and erase currently inactive region. - - This function sends a start command, and receives the base of the - preferred inactive region. This could be RW, RW_B, - or RO (if there's no RW_B) - - Note that the region is erased here, so you'd better program the RO if - you just erased it. TODO(nsanders): Modify the protocol to allow active - region select or query before erase. - """ + Usage: + d = Supdate() - # Size is 3 uint32 fields - # packet: [packetsize, cmd, base] - size = 4 + 4 + 4 - # Return value is [status, base_addr] - expected = 4 + 4 - - cmd = struct.pack("<III", size, 0, 0) - read = self.wr_command(cmd, read_count=expected) - - if len(read) == 4: - raise Exception("Update", "Protocol version 0 not supported") - elif len(read) == expected: - base, version = struct.unpack(">II", read) - log("Update protocol v. %d" % version) - log("Available flash region base: %x" % base) - else: - raise Exception("Update", "Start command returned %d bytes" % len(read)) - - if base < 256: - raise Exception("Update", "Start returned error code 0x%x" % base) - - self._base = base - flash_base = self._brdcfg["flash"] - self._offset = self._base - flash_base - - # Find our active region. - for region in self._brdcfg['regions']: - if (self._offset >= self._brdcfg['regions'][region][0]) and \ - (self._offset < (self._brdcfg['regions'][region][0] + \ - self._brdcfg['regions'][region][1])): - log("Active region: %s" % region) - self._region = region - - - def load_board(self, brdfile): - """Load firmware layout file. - - example as follows: - { - "board": "servo micro", - "vid": 6353, - "pid": 20506, - "flash": 134217728, - "regions": { - "RW": [65536, 65536], - "PSTATE": [61440, 4096], - "RO": [0, 61440] - } - } - - Args: - brdfile: path to board description file. + Instance Variables: + _dev: pyUSB device object + _read_ep: pyUSB read endpoint for this interface + _write_ep: pyUSB write endpoint for this interface """ - with open(brdfile) as data_file: - data = json.load(data_file) - - # TODO(nsanders): validate this data before moving on. - self._brdcfg = data; - if debug: - pprint(data) - - log("Board is %s" % self._brdcfg['board']) - # Cast hex strings to int. - self._brdcfg['flash'] = int(self._brdcfg['flash'], 0) - self._brdcfg['vid'] = int(self._brdcfg['vid'], 0) - self._brdcfg['pid'] = int(self._brdcfg['pid'], 0) - - log("Flash Base is %x" % self._brdcfg['flash']) - self._flashsize = 0 - for region in self._brdcfg['regions']: - base = int(self._brdcfg['regions'][region][0], 0) - length = int(self._brdcfg['regions'][region][1], 0) - log("region %s\tbase:0x%08x size:0x%08x" % ( - region, base, length)) - self._flashsize += length - # Convert these to int because json doesn't support hex. - self._brdcfg['regions'][region][0] = base - self._brdcfg['regions'][region][1] = length + USB_SUBCLASS_GOOGLE_UPDATE = 0x53 + USB_CLASS_VENDOR = 0xFF - log("Flash Size: 0x%x" % self._flashsize) - - def load_file(self, binfile): - """Open and verify size of the target ec.bin file. - - Args: - binfile: path to ec.bin - - Raises: - Exception on file not found or filesize not matching. - """ - self._filesize = os.path.getsize(binfile) - self._binfile = open(binfile, 'rb') - - if self._filesize != self._flashsize: - raise Exception("Update", "Flash size 0x%x != file size 0x%x" % (self._flashsize, self._filesize)) + def __init__(self): + pass + def connect_usb(self, serialname=None): + """Initial discovery and connection to USB endpoint. + + This searches for a USB device matching the VID:PID specified + in the config file, optionally matching a specified serialname. + + Args: + serialname: Find the device with this serial, in case multiple + devices are attached. + + Returns: + True on success. + Raises: + Exception on error. + """ + # Find the stm32. + vendor = self._brdcfg["vid"] + product = self._brdcfg["pid"] + + dev_g = usb.core.find(idVendor=vendor, idProduct=product, find_all=True) + dev_list = list(dev_g) + if dev_list is None: + raise Exception("Update", "USB device not found") + + # Check if we have multiple stm32s and we've specified the serial. + dev = None + if serialname: + for d in dev_list: + if usb.util.get_string(d, d.iSerialNumber) == serialname: + dev = d + break + if dev is None: + raise SusbError("USB device(%s) not found" % serialname) + else: + dev = dev_list[0] + + debuglog("Found stm32: %04x:%04x" % (vendor, product)) + self._dev = dev + + # Get an endpoint instance. + try: + dev.set_configuration() + except: + pass + cfg = dev.get_active_configuration() + + intf = usb.util.find_descriptor( + cfg, + custom_match=lambda i: i.bInterfaceClass == self.USB_CLASS_VENDOR + and i.bInterfaceSubClass == self.USB_SUBCLASS_GOOGLE_UPDATE, + ) + + self._intf = intf + debuglog("Interface: %s" % intf) + debuglog("InterfaceNumber: %s" % intf.bInterfaceNumber) + + read_ep = usb.util.find_descriptor( + intf, + # match the first IN endpoint + custom_match=lambda e: usb.util.endpoint_direction( + e.bEndpointAddress + ) + == usb.util.ENDPOINT_IN, + ) + + self._read_ep = read_ep + debuglog("Reader endpoint: 0x%x" % read_ep.bEndpointAddress) + + write_ep = usb.util.find_descriptor( + intf, + # match the first OUT endpoint + custom_match=lambda e: usb.util.endpoint_direction( + e.bEndpointAddress + ) + == usb.util.ENDPOINT_OUT, + ) + + self._write_ep = write_ep + debuglog("Writer endpoint: 0x%x" % write_ep.bEndpointAddress) + + return True + + def wr_command(self, write_list, read_count=1, wtimeout=100, rtimeout=2000): + """Write command to logger logic.. + + This function writes byte command values list to stm, then reads + byte status. + + Args: + write_list: list of command byte values [0~255]. + read_count: number of status byte values to read. + wtimeout: mS to wait for write success + rtimeout: mS to wait for read success + + Returns: + status byte, if one byte is read, + byte list, if multiple bytes are read, + None, if no bytes are read. + + Interface: + write: [command, data ... ] + read: [status ] + """ + debuglog( + "wr_command(write_list=[%s] (%d), read_count=%s)" + % (list(bytearray(write_list)), len(write_list), read_count) + ) + + # Clean up args from python style to correct types. + write_length = 0 + if write_list: + write_length = len(write_list) + if not read_count: + read_count = 0 + + # Send command to stm32. + if write_list: + cmd = write_list + ret = self._write_ep.write(cmd, wtimeout) + debuglog("RET: %s " % ret) + + # Read back response if necessary. + if read_count: + bytesread = self._read_ep.read(512, rtimeout) + debuglog("BYTES: [%s]" % bytesread) + + if len(bytesread) != read_count: + debuglog( + "Unexpected bytes read: %d, expected: %d" + % (len(bytesread), read_count) + ) + pass + + debuglog("STATUS: 0x%02x" % int(bytesread[0])) + if read_count == 1: + return bytesread[0] + else: + return bytesread + + return None + + def stop(self): + """Finalize system flash and exit.""" + cmd = struct.pack(">I", 0xB007AB1E) + read = self.wr_command(cmd, read_count=4) + + if len(read) == 4: + log("Finished flashing") + return + + raise Exception("Update", "Stop failed [%s]" % read) + + def write_file(self): + """Write the update region packet by packet to USB + + This sends write packets of size 128B out, in 32B chunks. + Overall, this will write all data in the inactive code region. + + Raises: + Exception if write failed or address out of bounds. + """ + region = self._region + flash_base = self._brdcfg["flash"] + offset = self._base - flash_base + if offset != self._brdcfg["regions"][region][0]: + raise Exception( + "Update", + "Region %s offset 0x%x != available offset 0x%x" + % (region, self._brdcfg["regions"][region][0], offset), + ) + + length = self._brdcfg["regions"][region][1] + log("Sending") + + # Go to the correct region in the ec.bin file. + self._binfile.seek(offset) + + # Send 32 bytes at a time. Must be less than the endpoint's max packet size. + maxpacket = 32 + + # While data is left, create update packets. + while length > 0: + # Update packets are 128B. We can use any number + # but the micro must malloc this memory. + pagesize = min(length, 128) + + # Packet is: + # packet size: page bytes transferred plus 3 x 32b values header. + # cmd: n/a + # base: flash address to write this packet. + # data: 128B of data to write into flash_base + cmd = struct.pack(">III", pagesize + 12, 0, offset + flash_base) + read = self.wr_command(cmd, read_count=0) + + # Push 'todo' bytes out the pipe. + todo = pagesize + while todo > 0: + packetsize = min(maxpacket, todo) + data = self._binfile.read(packetsize) + if len(data) != packetsize: + raise Exception("Update", "No more data from file") + for i in range(0, 10): + try: + self.wr_command(data, read_count=0) + break + except: + log("Timeout fail") + todo -= packetsize + # Done with this packet, move to the next one. + length -= pagesize + offset += pagesize + + # Validate that the micro thinks it successfully wrote the data. + read = self.wr_command("".encode(), read_count=4) + result = struct.unpack("<I", read) + result = result[0] + if result != 0: + raise Exception( + "Update", "Upload failed with rc: 0x%x" % result + ) + + def start(self): + """Start a transaction and erase currently inactive region. + + This function sends a start command, and receives the base of the + preferred inactive region. This could be RW, RW_B, + or RO (if there's no RW_B) + + Note that the region is erased here, so you'd better program the RO if + you just erased it. TODO(nsanders): Modify the protocol to allow active + region select or query before erase. + """ + + # Size is 3 uint32 fields + # packet: [packetsize, cmd, base] + size = 4 + 4 + 4 + # Return value is [status, base_addr] + expected = 4 + 4 + + cmd = struct.pack("<III", size, 0, 0) + read = self.wr_command(cmd, read_count=expected) + + if len(read) == 4: + raise Exception("Update", "Protocol version 0 not supported") + elif len(read) == expected: + base, version = struct.unpack(">II", read) + log("Update protocol v. %d" % version) + log("Available flash region base: %x" % base) + else: + raise Exception( + "Update", "Start command returned %d bytes" % len(read) + ) + + if base < 256: + raise Exception("Update", "Start returned error code 0x%x" % base) + + self._base = base + flash_base = self._brdcfg["flash"] + self._offset = self._base - flash_base + + # Find our active region. + for region in self._brdcfg["regions"]: + if (self._offset >= self._brdcfg["regions"][region][0]) and ( + self._offset + < ( + self._brdcfg["regions"][region][0] + + self._brdcfg["regions"][region][1] + ) + ): + log("Active region: %s" % region) + self._region = region + + def load_board(self, brdfile): + """Load firmware layout file. + + example as follows: + { + "board": "servo micro", + "vid": 6353, + "pid": 20506, + "flash": 134217728, + "regions": { + "RW": [65536, 65536], + "PSTATE": [61440, 4096], + "RO": [0, 61440] + } + } + + Args: + brdfile: path to board description file. + """ + with open(brdfile) as data_file: + data = json.load(data_file) + + # TODO(nsanders): validate this data before moving on. + self._brdcfg = data + if debug: + pprint(data) + + log("Board is %s" % self._brdcfg["board"]) + # Cast hex strings to int. + self._brdcfg["flash"] = int(self._brdcfg["flash"], 0) + self._brdcfg["vid"] = int(self._brdcfg["vid"], 0) + self._brdcfg["pid"] = int(self._brdcfg["pid"], 0) + + log("Flash Base is %x" % self._brdcfg["flash"]) + self._flashsize = 0 + for region in self._brdcfg["regions"]: + base = int(self._brdcfg["regions"][region][0], 0) + length = int(self._brdcfg["regions"][region][1], 0) + log("region %s\tbase:0x%08x size:0x%08x" % (region, base, length)) + self._flashsize += length + + # Convert these to int because json doesn't support hex. + self._brdcfg["regions"][region][0] = base + self._brdcfg["regions"][region][1] = length + + log("Flash Size: 0x%x" % self._flashsize) + + def load_file(self, binfile): + """Open and verify size of the target ec.bin file. + + Args: + binfile: path to ec.bin + + Raises: + Exception on file not found or filesize not matching. + """ + self._filesize = os.path.getsize(binfile) + self._binfile = open(binfile, "rb") + + if self._filesize != self._flashsize: + raise Exception( + "Update", + "Flash size 0x%x != file size 0x%x" + % (self._flashsize, self._filesize), + ) # Generate command line arguments parser = argparse.ArgumentParser(description="Update firmware over usb") -parser.add_argument('-b', '--board', type=str, help="Board configuration json file", default="board.json") -parser.add_argument('-f', '--file', type=str, help="Complete ec.bin file", default="ec.bin") -parser.add_argument('-s', '--serial', type=str, help="Serial number", default="") -parser.add_argument('-l', '--list', action="store_true", help="List regions") -parser.add_argument('-v', '--verbose', action="store_true", help="Chatty output") +parser.add_argument( + "-b", + "--board", + type=str, + help="Board configuration json file", + default="board.json", +) +parser.add_argument( + "-f", "--file", type=str, help="Complete ec.bin file", default="ec.bin" +) +parser.add_argument( + "-s", "--serial", type=str, help="Serial number", default="" +) +parser.add_argument("-l", "--list", action="store_true", help="List regions") +parser.add_argument( + "-v", "--verbose", action="store_true", help="Chatty output" +) + def main(): - global debug - args = parser.parse_args() + global debug + args = parser.parse_args() + brdfile = args.board + serial = args.serial + binfile = args.file + if args.verbose: + debug = True - brdfile = args.board - serial = args.serial - binfile = args.file - if args.verbose: - debug = True + with open(brdfile) as data_file: + names = json.load(data_file) - with open(brdfile) as data_file: - names = json.load(data_file) + p = Supdate() + p.load_board(brdfile) + p.connect_usb(serialname=serial) + p.load_file(binfile) - p = Supdate() - p.load_board(brdfile) - p.connect_usb(serialname=serial) - p.load_file(binfile) + # List solely prints the config. + if args.list: + return - # List solely prints the config. - if (args.list): - return + # Start transfer and erase. + p.start() + # Upload the bin file + log("Uploading %s" % binfile) + p.write_file() - # Start transfer and erase. - p.start() - # Upload the bin file - log("Uploading %s" % binfile) - p.write_file() + # Finalize + log("Done. Finalizing.") + p.stop() - # Finalize - log("Done. Finalizing.") - p.stop() if __name__ == "__main__": - main() - - + main() diff --git a/extra/usb_updater/sample_descriptor b/extra/usb_updater/sample_descriptor index 1566e9e2e1..3be408b642 100644 --- a/extra/usb_updater/sample_descriptor +++ b/extra/usb_updater/sample_descriptor @@ -1,4 +1,4 @@ -# Copyright 2018 The Chromium OS Authors. All rights reserved. +# Copyright 2018 The ChromiumOS Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. # diff --git a/extra/usb_updater/servo_updater.py b/extra/usb_updater/servo_updater.py index fa0d21670c..c0be11fdde 100755 --- a/extra/usb_updater/servo_updater.py +++ b/extra/usb_updater/servo_updater.py @@ -1,53 +1,55 @@ #!/usr/bin/env python -# Copyright 2016 The Chromium OS Authors. All rights reserved. +# Copyright 2016 The ChromiumOS Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -# -# Ignore indention messages, since legacy scripts use 2 spaces instead of 4. -# pylint: disable=bad-indentation,docstring-section-indent -# pylint: disable=docstring-trailing-quotes # Note: This is a py2/3 compatible file. +"""USB updater tool for servo and similar boards.""" + from __future__ import print_function import argparse -import errno +import json import os import re import subprocess import time -import tempfile - -import json -import fw_update import ecusb.tiny_servo_common as c +import fw_update from ecusb import tiny_servod + class ServoUpdaterException(Exception): - """Raised on exceptions generated by servo_updater.""" + """Raised on exceptions generated by servo_updater.""" -BOARD_C2D2 = 'c2d2' -BOARD_SERVO_MICRO = 'servo_micro' -BOARD_SERVO_V4 = 'servo_v4' -BOARD_SERVO_V4P1 = 'servo_v4p1' -BOARD_SWEETBERRY = 'sweetberry' + +BOARD_C2D2 = "c2d2" +BOARD_SERVO_MICRO = "servo_micro" +BOARD_SERVO_V4 = "servo_v4" +BOARD_SERVO_V4P1 = "servo_v4p1" +BOARD_SWEETBERRY = "sweetberry" DEFAULT_BOARD = BOARD_SERVO_V4 # These lists are to facilitate exposing choices in the command-line tool # below. -BOARDS = [BOARD_C2D2, BOARD_SERVO_MICRO, BOARD_SERVO_V4, BOARD_SERVO_V4P1, - BOARD_SWEETBERRY] +BOARDS = [ + BOARD_C2D2, + BOARD_SERVO_MICRO, + BOARD_SERVO_V4, + BOARD_SERVO_V4P1, + BOARD_SWEETBERRY, +] # Servo firmware bundles four channels of firmware. We need to make sure the # user does not request a non-existing channel, so keep the lists around to # guard on command-line usage. -DEFAULT_CHANNEL = STABLE_CHANNEL = 'stable' +DEFAULT_CHANNEL = STABLE_CHANNEL = "stable" -PREV_CHANNEL = 'prev' +PREV_CHANNEL = "prev" # The ordering here matters. From left to right it's the channel that the user # is most likely to be running. This is used to inform and warn the user if @@ -55,12 +57,12 @@ PREV_CHANNEL = 'prev' # user know they are running the 'stable' version before letting them know they # are running 'dev' or even 'alpah' which (while true) might cause confusion. -CHANNELS = [DEFAULT_CHANNEL, PREV_CHANNEL, 'dev', 'alpha'] +CHANNELS = [DEFAULT_CHANNEL, PREV_CHANNEL, "dev", "alpha"] -DEFAULT_BASE_PATH = '/usr/' -TEST_IMAGE_BASE_PATH = '/usr/local/' +DEFAULT_BASE_PATH = "/usr/" +TEST_IMAGE_BASE_PATH = "/usr/local/" -COMMON_PATH = 'share/servo_updater' +COMMON_PATH = "share/servo_updater" FIRMWARE_DIR = "firmware/" CONFIGS_DIR = "configs/" @@ -68,389 +70,444 @@ CONFIGS_DIR = "configs/" RETRIES_COUNT = 10 RETRIES_DELAY = 1 + def do_with_retries(func, *args): - """ - Call function passed as argument and check if no error happened. - If exception was raised by function, - it will be retried up to RETRIES_COUNT times. - - Args: - func: function that will be called - args: arguments passed to 'func' - - Returns: - If call to function was successful, its result will be returned. - If retries count was exceeded, exception will be raised. - """ - - retry = 0 - while retry < RETRIES_COUNT: - try: - return func(*args) - except Exception as e: - print("Retrying function %s: %s" % (func.__name__, e)) - retry = retry + 1 - time.sleep(RETRIES_DELAY) - continue - - raise Exception("'{}' failed after {} retries".format(func.__name__, RETRIES_COUNT)) + """Try a function several times + + Call function passed as argument and check if no error happened. + If exception was raised by function, + it will be retried up to RETRIES_COUNT times. + + Args: + func: function that will be called + args: arguments passed to 'func' + + Returns: + If call to function was successful, its result will be returned. + If retries count was exceeded, exception will be raised. + """ + + retry = 0 + while retry < RETRIES_COUNT: + try: + return func(*args) + except Exception as e: + print("Retrying function %s: %s" % (func.__name__, e)) + retry = retry + 1 + time.sleep(RETRIES_DELAY) + continue + + raise Exception( + "'{}' failed after {} retries".format(func.__name__, RETRIES_COUNT) + ) + def flash(brdfile, serialno, binfile): - """ - Call fw_update to upload to updater USB endpoint. - - Args: - brdfile: path to board configuration file - serialno: device serial number - binfile: firmware file - """ - - p = fw_update.Supdate() - p.load_board(brdfile) - p.connect_usb(serialname=serialno) - p.load_file(binfile) - - # Start transfer and erase. - p.start() - # Upload the bin file - print("Uploading %s" % binfile) - p.write_file() - - # Finalize - print("Done. Finalizing.") - p.stop() + """Call fw_update to upload to updater USB endpoint. + + Args: + brdfile: path to board configuration file + serialno: device serial number + binfile: firmware file + """ + + p = fw_update.Supdate() + p.load_board(brdfile) + p.connect_usb(serialname=serialno) + p.load_file(binfile) + + # Start transfer and erase. + p.start() + # Upload the bin file + print("Uploading %s" % binfile) + p.write_file() + + # Finalize + print("Done. Finalizing.") + p.stop() + def flash2(vidpid, serialno, binfile): - """ - Call fw update via usb_updater2 commandline. - - Args: - vidpid: vendor id and product id of device - serialno: device serial number (optional) - binfile: firmware file - """ - - tool = 'usb_updater2' - cmd = "%s -d %s" % (tool, vidpid) - if serialno: - cmd += " -S %s" % serialno - cmd += " -n" - cmd += " %s" % binfile - - print(cmd) - help_cmd = '%s --help' % tool - with open('/dev/null') as devnull: - valid_check = subprocess.call(help_cmd.split(), stdout=devnull, - stderr=devnull) - if valid_check: - raise ServoUpdaterException('%s exit with res = %d. Make sure the tool ' - 'is available on the device.' % (help_cmd, - valid_check)) - res = subprocess.call(cmd.split()) - - if res in (0, 1, 2): - return res - else: - raise ServoUpdaterException("%s exit with res = %d" % (cmd, res)) + """Call fw update via usb_updater2 commandline. + + Args: + vidpid: vendor id and product id of device + serialno: device serial number (optional) + binfile: firmware file + """ + + tool = "usb_updater2" + cmd = "%s -d %s" % (tool, vidpid) + if serialno: + cmd += " -S %s" % serialno + cmd += " -n" + cmd += " %s" % binfile + + print(cmd) + help_cmd = "%s --help" % tool + with open("/dev/null") as devnull: + valid_check = subprocess.call( + help_cmd.split(), stdout=devnull, stderr=devnull + ) + if valid_check: + raise ServoUpdaterException( + "%s exit with res = %d. Make sure the tool " + "is available on the device." % (help_cmd, valid_check) + ) + res = subprocess.call(cmd.split()) + + if res in (0, 1, 2): + return res + else: + raise ServoUpdaterException("%s exit with res = %d" % (cmd, res)) + def select(tinys, region): - """ - Ensure the servo is in the expected ro/rw region. - This function jumps to the required region and verify if jump was - successful by executing 'sysinfo' command and reading current region. - If response was not received or region is invalid, exception is raised. + """Jump to specified boot region - Args: - tinys: TinyServod object - region: region to jump to, only "rw" and "ro" is allowed - """ + Ensure the servo is in the expected ro/rw region. + This function jumps to the required region and verify if jump was + successful by executing 'sysinfo' command and reading current region. + If response was not received or region is invalid, exception is raised. - if region not in ["rw", "ro"]: - raise Exception("Region must be ro or rw") + Args: + tinys: TinyServod object + region: region to jump to, only "rw" and "ro" is allowed + """ + + if region not in ["rw", "ro"]: + raise Exception("Region must be ro or rw") + + if region == "ro": + cmd = "reboot" + else: + cmd = "sysjump %s" % region - if region is "ro": - cmd = "reboot" - else: - cmd = "sysjump %s" % region + tinys.pty._issue_cmd(cmd) - tinys.pty._issue_cmd(cmd) + tinys.close() + time.sleep(2) + tinys.reinitialize() - tinys.close() - time.sleep(2) - tinys.reinitialize() + res = tinys.pty._issue_cmd_get_results("sysinfo", [r"Copy:[\s]+(RO|RW)"]) + current_region = res[0][1].lower() + if current_region != region: + raise Exception("Invalid region: %s/%s" % (current_region, region)) - res = tinys.pty._issue_cmd_get_results("sysinfo", ["Copy:[\s]+(RO|RW)"]) - current_region = res[0][1].lower() - if current_region != region: - raise Exception("Invalid region: %s/%s" % (current_region, region)) def do_version(tinys): - """Check version via ec console 'pty'. + """Check version via ec console 'pty'. - Args: - tinys: TinyServod object + Args: + tinys: TinyServod object - Returns: - detected version number + Returns: + detected version number - Commands are: - # > version - # ... - # Build: tigertail_v1.1.6749-74d1a312e - """ - cmd = '\r\nversion\r\n' - regex = 'Build:\s+(\S+)[\r\n]+' + Commands are: + # > version + # ... + # Build: tigertail_v1.1.6749-74d1a312e + """ + cmd = "version" + regex = r"Build:\s+(\S+)[\r\n]+" - results = tinys.pty._issue_cmd_get_results(cmd, [regex])[0] + results = tinys.pty._issue_cmd_get_results(cmd, [regex])[0] + + return results[1].strip(" \t\r\n\0") - return results[1].strip(' \t\r\n\0') def do_updater_version(tinys): - """Check whether this uses python updater or c++ updater - - Args: - tinys: TinyServod object - - Returns: - updater version number. 2 or 6. - """ - vers = do_version(tinys) - - # Servo versions below 58 are from servo-9040.B. Versions starting with _v2 - # are newer than anything _v1, no need to check the exact number. Updater - # version is not directly queryable. - if re.search('_v[2-9]\.\d', vers): - return 6 - m = re.search('_v1\.1\.(\d\d\d\d)', vers) - if m: - version_number = int(m.group(1)) - if version_number < 5800: - return 2 - else: - return 6 - raise ServoUpdaterException( - "Can't determine updater target from vers: [%s]" % vers) + """Check whether this uses python updater or c++ updater -def _extract_version(boardname, binfile): - """Find the version string from |binfile|. + Args: + tinys: TinyServod object - Args: - boardname: the name of the board, eg. "servo_micro" - binfile: path to the binary to search + Returns: + updater version number. 2 or 6. + """ + vers = do_version(tinys) - Returns: - the version string. - """ - if boardname is None: - # cannot extract the version if the name is None - return None - rawstrings = subprocess.check_output( - ['cbfstool', binfile, 'read', '-r', 'RO_FRID', '-f', '/dev/stdout'], - **c.get_subprocess_args()) - m = re.match(r'%s_v\S+' % boardname, rawstrings) - if m: - newvers = m.group(0).strip(' \t\r\n\0') - else: - raise ServoUpdaterException("Can't find version from file: %s." % binfile) + # Servo versions below 58 are from servo-9040.B. Versions starting with _v2 + # are newer than anything _v1, no need to check the exact number. Updater + # version is not directly queryable. + if re.search(r"_v[2-9]\.\d", vers): + return 6 + m = re.search(r"_v1\.1\.(\d\d\d\d)", vers) + if m: + version_number = int(m.group(1)) + if version_number < 5800: + return 2 + else: + return 6 + raise ServoUpdaterException( + "Can't determine updater target from vers: [%s]" % vers + ) + + +def _extract_version(boardname, binfile): + """Find the version string from |binfile|. + + Args: + boardname: the name of the board, eg. "servo_micro" + binfile: path to the binary to search + + Returns: + the version string. + """ + if boardname is None: + # cannot extract the version if the name is None + return None + rawstrings = subprocess.check_output( + ["cbfstool", binfile, "read", "-r", "RO_FRID", "-f", "/dev/stdout"], + **c.get_subprocess_args() + ) + m = re.match(r"%s_v\S+" % boardname, rawstrings) + if m: + newvers = m.group(0).strip(" \t\r\n\0") + else: + raise ServoUpdaterException( + "Can't find version from file: %s." % binfile + ) + + return newvers - return newvers def get_firmware_channel(bname, version): - """Find out which channel |version| for |bname| came from. - - Args: - bname: board name - version: current version string - - Returns: - one of the channel names if |version| came from one of those, or None - """ - for channel in CHANNELS: - # Pass |bname| as cname to find the board specific file, and pass None as - # fname to ensure the default directory is searched - _, _, vers = get_files_and_version(bname, None, channel=channel) - if version == vers: - return channel - # None of the channels matched. This firmware is currently unknown. - return None + """Find out which channel |version| for |bname| came from. + + Args: + bname: board name + version: current version string + + Returns: + one of the channel names if |version| came from one of those, or None + """ + for channel in CHANNELS: + # Pass |bname| as cname to find the board specific file, and pass None as + # fname to ensure the default directory is searched + _, _, vers = get_files_and_version(bname, None, channel=channel) + if version == vers: + return channel + # None of the channels matched. This firmware is currently unknown. + return None + def get_files_and_version(cname, fname=None, channel=DEFAULT_CHANNEL): - """Select config and firmware binary files. - - This checks default file names and paths. - In: /usr/share/servo_updater/[firmware|configs] - check for board.json, board.bin - - Args: - cname: board name, or config name. eg. "servo_v4" or "servo_v4.json" - fname: firmware binary name. Can be None to try default. - channel: the channel requested for servo firmware. See |CHANNELS| above. - - Returns: - cname, fname, version: validated filenames selected from the path. - """ - for p in (DEFAULT_BASE_PATH, TEST_IMAGE_BASE_PATH): - updater_path = os.path.join(p, COMMON_PATH) - if os.path.exists(updater_path): - break - else: - raise ServoUpdaterException('servo_updater/ dir not found in known spots.') - - firmware_path = os.path.join(updater_path, FIRMWARE_DIR) - configs_path = os.path.join(updater_path, CONFIGS_DIR) - - for p in (firmware_path, configs_path): - if not os.path.exists(p): - raise ServoUpdaterException('Could not find required path %r' % p) - - if not os.path.isfile(cname): - # If not an existing file, try checking on the default path. - newname = os.path.join(configs_path, cname) - if os.path.isfile(newname): - cname = newname + """Select config and firmware binary files. + + This checks default file names and paths. + In: /usr/share/servo_updater/[firmware|configs] + check for board.json, board.bin + + Args: + cname: board name, or config name. eg. "servo_v4" or "servo_v4.json" + fname: firmware binary name. Can be None to try default. + channel: the channel requested for servo firmware. See |CHANNELS| above. + + Returns: + cname, fname, version: validated filenames selected from the path. + """ + for p in (DEFAULT_BASE_PATH, TEST_IMAGE_BASE_PATH): + updater_path = os.path.join(p, COMMON_PATH) + if os.path.exists(updater_path): + break else: - # Try appending ".json" to convert board name to config file. - cname = newname + ".json" + raise ServoUpdaterException( + "servo_updater/ dir not found in known spots." + ) + + firmware_path = os.path.join(updater_path, FIRMWARE_DIR) + configs_path = os.path.join(updater_path, CONFIGS_DIR) + + for p in (firmware_path, configs_path): + if not os.path.exists(p): + raise ServoUpdaterException("Could not find required path %r" % p) + if not os.path.isfile(cname): - raise ServoUpdaterException("Can't find config file: %s." % cname) - - # Always retrieve the boardname - with open(cname) as data_file: - data = json.load(data_file) - boardname = data['board'] - - if not fname: - # If no |fname| supplied, look for the default locations with the board - # and channel requested. - binary_file = '%s.%s.bin' % (boardname, channel) - newname = os.path.join(firmware_path, binary_file) - if os.path.isfile(newname): - fname = newname - else: - raise ServoUpdaterException("Can't find firmware binary: %s." % - binary_file) - elif not os.path.isfile(fname): - # If a name is specified but not found, try the default path. - newname = os.path.join(firmware_path, fname) - if os.path.isfile(newname): - fname = newname + # If not an existing file, try checking on the default path. + newname = os.path.join(configs_path, cname) + if os.path.isfile(newname): + cname = newname + else: + # Try appending ".json" to convert board name to config file. + cname = newname + ".json" + if not os.path.isfile(cname): + raise ServoUpdaterException("Can't find config file: %s." % cname) + + # Always retrieve the boardname + with open(cname) as data_file: + data = json.load(data_file) + boardname = data["board"] + + if not fname: + # If no |fname| supplied, look for the default locations with the board + # and channel requested. + binary_file = "%s.%s.bin" % (boardname, channel) + newname = os.path.join(firmware_path, binary_file) + if os.path.isfile(newname): + fname = newname + else: + raise ServoUpdaterException( + "Can't find firmware binary: %s." % binary_file + ) + elif not os.path.isfile(fname): + # If a name is specified but not found, try the default path. + newname = os.path.join(firmware_path, fname) + if os.path.isfile(newname): + fname = newname + else: + raise ServoUpdaterException("Can't find file: %s." % fname) + + # Lastly, retrieve the version as well for decision making, debug, and + # informational purposes. + binvers = _extract_version(boardname, fname) + + return cname, fname, binvers + + +def main(): + parser = argparse.ArgumentParser(description="Image a servo device") + parser.add_argument( + "-p", + "--print", + dest="print_only", + action="store_true", + default=False, + help="only print available firmware for board/channel", + ) + parser.add_argument( + "-s", + "--serialno", + type=str, + help="serial number to program", + default=None, + ) + parser.add_argument( + "-b", + "--board", + type=str, + help="Board configuration json file", + default=DEFAULT_BOARD, + choices=BOARDS, + ) + parser.add_argument( + "-c", + "--channel", + type=str, + help="Firmware channel to use", + default=DEFAULT_CHANNEL, + choices=CHANNELS, + ) + parser.add_argument( + "-f", "--file", type=str, help="Complete ec.bin file", default=None + ) + parser.add_argument( + "--force", + action="store_true", + help="Update even if version match", + default=False, + ) + parser.add_argument( + "-v", "--verbose", action="store_true", help="Chatty output" + ) + parser.add_argument( + "-r", + "--reboot", + action="store_true", + help="Always reboot, even after probe.", + ) + + args = parser.parse_args() + + brdfile, binfile, newvers = get_files_and_version( + args.board, args.file, args.channel + ) + + # If the user only cares about the information then just print it here, + # and exit. + if args.print_only: + output = ("board: %s\nchannel: %s\nfirmware: %s") % ( + args.board, + args.channel, + newvers, + ) + print(output) + return + + serialno = args.serialno + + with open(brdfile) as data_file: + data = json.load(data_file) + vid, pid = int(data["vid"], 0), int(data["pid"], 0) + vidpid = "%04x:%04x" % (vid, pid) + iface = int(data["console"], 0) + boardname = data["board"] + + # Make sure device is up. + print("===== Waiting for USB device =====") + c.wait_for_usb(vidpid, serialname=serialno) + # We need a tiny_servod to query some information. Set it up first. + tinys = tiny_servod.TinyServod(vid, pid, iface, serialno, args.verbose) + + if not args.force: + vers = do_version(tinys) + print("Current %s version is %s" % (boardname, vers)) + print("Available %s version is %s" % (boardname, newvers)) + + if newvers == vers: + print("No version update needed") + if args.reboot: + select(tinys, "ro") + return + else: + print("Updating to recommended version.") + + # Make sure the servo MCU is in RO + print("===== Jumping to RO =====") + do_with_retries(select, tinys, "ro") + + print("===== Flashing RW =====") + vers = do_with_retries(do_updater_version, tinys) + # To make sure that the tiny_servod here does not interfere with other + # processes, close it out. + tinys.close() + + if vers == 2: + flash(brdfile, serialno, binfile) + elif vers == 6: + do_with_retries(flash2, vidpid, serialno, binfile) else: - raise ServoUpdaterException("Can't find file: %s." % fname) + raise ServoUpdaterException("Can't detect updater version") - # Lastly, retrieve the version as well for decision making, debug, and - # informational purposes. - binvers = _extract_version(boardname, fname) + # Make sure device is up. + c.wait_for_usb(vidpid, serialname=serialno) + # After we have made sure that it's back/available, reconnect the tiny servod. + tinys.reinitialize() - return cname, fname, binvers + # Make sure the servo MCU is in RW + print("===== Jumping to RW =====") + do_with_retries(select, tinys, "rw") -def main(): - parser = argparse.ArgumentParser(description="Image a servo device") - parser.add_argument('-p', '--print', dest='print_only', action='store_true', - default=False, - help='only print available firmware for board/channel') - parser.add_argument('-s', '--serialno', type=str, - help="serial number to program", default=None) - parser.add_argument('-b', '--board', type=str, - help="Board configuration json file", - default=DEFAULT_BOARD, choices=BOARDS) - parser.add_argument('-c', '--channel', type=str, - help="Firmware channel to use", - default=DEFAULT_CHANNEL, choices=CHANNELS) - parser.add_argument('-f', '--file', type=str, - help="Complete ec.bin file", default=None) - parser.add_argument('--force', action="store_true", - help="Update even if version match", default=False) - parser.add_argument('-v', '--verbose', action="store_true", - help="Chatty output") - parser.add_argument('-r', '--reboot', action="store_true", - help="Always reboot, even after probe.") - - args = parser.parse_args() - - brdfile, binfile, newvers = get_files_and_version(args.board, args.file, - args.channel) - - # If the user only cares about the information then just print it here, - # and exit. - if args.print_only: - output = ('board: %s\n' - 'channel: %s\n' - 'firmware: %s') % (args.board, args.channel, newvers) - print(output) - return - - serialno = args.serialno - - with open(brdfile) as data_file: - data = json.load(data_file) - vid, pid = int(data['vid'], 0), int(data['pid'], 0) - vidpid = "%04x:%04x" % (vid, pid) - iface = int(data['console'], 0) - boardname = data['board'] - - # Make sure device is up. - print("===== Waiting for USB device =====") - c.wait_for_usb(vidpid, serialname=serialno) - # We need a tiny_servod to query some information. Set it up first. - tinys = tiny_servod.TinyServod(vid, pid, iface, serialno, args.verbose) - - if not args.force: - vers = do_version(tinys) - print("Current %s version is %s" % (boardname, vers)) - print("Available %s version is %s" % (boardname, newvers)) - - if newvers == vers: - print("No version update needed") - if args.reboot: - select(tinys, 'ro') - return + print("===== Flashing RO =====") + vers = do_with_retries(do_updater_version, tinys) + + if vers == 2: + flash(brdfile, serialno, binfile) + elif vers == 6: + do_with_retries(flash2, vidpid, serialno, binfile) else: - print("Updating to recommended version.") - - # Make sure the servo MCU is in RO - print("===== Jumping to RO =====") - do_with_retries(select, tinys, 'ro') - - print("===== Flashing RW =====") - vers = do_with_retries(do_updater_version, tinys) - # To make sure that the tiny_servod here does not interfere with other - # processes, close it out. - tinys.close() - - if vers == 2: - flash(brdfile, serialno, binfile) - elif vers == 6: - do_with_retries(flash2, vidpid, serialno, binfile) - else: - raise ServoUpdaterException("Can't detect updater version") - - # Make sure device is up. - c.wait_for_usb(vidpid, serialname=serialno) - # After we have made sure that it's back/available, reconnect the tiny servod. - tinys.reinitialize() - - # Make sure the servo MCU is in RW - print("===== Jumping to RW =====") - do_with_retries(select, tinys, 'rw') - - print("===== Flashing RO =====") - vers = do_with_retries(do_updater_version, tinys) - - if vers == 2: - flash(brdfile, serialno, binfile) - elif vers == 6: - do_with_retries(flash2, vidpid, serialno, binfile) - else: - raise ServoUpdaterException("Can't detect updater version") - - # Make sure the servo MCU is in RO - print("===== Rebooting =====") - do_with_retries(select, tinys, 'ro') - # Perform additional reboot to free USB/UART resources, taken by tiny servod. - # See https://issuetracker.google.com/196021317 for background. - tinys.pty._issue_cmd("reboot") - - print("===== Finished =====") + raise ServoUpdaterException("Can't detect updater version") + + # Make sure the servo MCU is in RO + print("===== Rebooting =====") + do_with_retries(select, tinys, "ro") + # Perform additional reboot to free USB/UART resources, taken by tiny servod. + # See https://issuetracker.google.com/196021317 for background. + tinys.pty._issue_cmd("reboot") + + print("===== Finished =====") + if __name__ == "__main__": - main() + main() diff --git a/extra/usb_updater/usb_updater2.c b/extra/usb_updater/usb_updater2.c index 81cf48a680..d591811a2b 100644 --- a/extra/usb_updater/usb_updater2.c +++ b/extra/usb_updater/usb_updater2.c @@ -1,5 +1,5 @@ /* - * Copyright 2017 The Chromium OS Authors. All rights reserved. + * Copyright 2017 The ChromiumOS Authors * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ @@ -46,16 +46,16 @@ #define PROTOCOL USB_PROTOCOL_GOOGLE_UPDATE enum exit_values { - noop = 0, /* All up to date, no update needed. */ - all_updated = 1, /* Update completed, reboot required. */ - rw_updated = 2, /* RO was not updated, reboot required. */ - update_error = 3 /* Something went wrong. */ + noop = 0, /* All up to date, no update needed. */ + all_updated = 1, /* Update completed, reboot required. */ + rw_updated = 2, /* RO was not updated, reboot required. */ + update_error = 3 /* Something went wrong. */ }; struct usb_endpoint { struct libusb_device_handle *devh; uint8_t ep_num; - int chunk_len; + int chunk_len; }; struct transfer_descriptor { @@ -76,22 +76,22 @@ static char *progname; static char *short_opts = "bd:efg:hjlnp:rsS:tuw"; static const struct option long_opts[] = { /* name hasarg *flag val */ - {"binvers", 1, NULL, 'b'}, - {"device", 1, NULL, 'd'}, - {"entropy", 0, NULL, 'e'}, - {"fwver", 0, NULL, 'f'}, - {"tp_debug", 1, NULL, 'g'}, - {"help", 0, NULL, 'h'}, - {"jump_to_rw", 0, NULL, 'j'}, - {"follow_log", 0, NULL, 'l'}, - {"no_reset", 0, NULL, 'n'}, - {"tp_update", 1, NULL, 'p'}, - {"reboot", 0, NULL, 'r'}, - {"stay_in_ro", 0, NULL, 's'}, - {"serial", 1, NULL, 'S'}, - {"tp_info", 0, NULL, 't'}, - {"unlock_rollback", 0, NULL, 'u'}, - {"unlock_rw", 0, NULL, 'w'}, + { "binvers", 1, NULL, 'b' }, + { "device", 1, NULL, 'd' }, + { "entropy", 0, NULL, 'e' }, + { "fwver", 0, NULL, 'f' }, + { "tp_debug", 1, NULL, 'g' }, + { "help", 0, NULL, 'h' }, + { "jump_to_rw", 0, NULL, 'j' }, + { "follow_log", 0, NULL, 'l' }, + { "no_reset", 0, NULL, 'n' }, + { "tp_update", 1, NULL, 'p' }, + { "reboot", 0, NULL, 'r' }, + { "stay_in_ro", 0, NULL, 's' }, + { "serial", 1, NULL, 'S' }, + { "tp_info", 0, NULL, 't' }, + { "unlock_rollback", 0, NULL, 'u' }, + { "unlock_rw", 0, NULL, 'w' }, {}, }; @@ -113,7 +113,7 @@ static void usage(int errs) "Options:\n" "\n" " -b,--binvers Report versions of image's " - "RW and RO, do not update\n" + "RW and RO, do not update\n" " -d,--device VID:PID USB device (default %04x:%04x)\n" " -e,--entropy Add entropy to device secret\n" " -f,--fwver Report running firmware versions.\n" @@ -128,7 +128,8 @@ static void usage(int errs) " -t,--tp_info Get touchpad information\n" " -u,--unlock_rollback Tell EC to unlock the rollback region\n" " -w,--unlock_rw Tell EC to unlock the RW region\n" - "\n", progname, VID, PID); + "\n", + progname, VID, PID); exit(errs ? update_error : noop); } @@ -138,7 +139,7 @@ static void str2hex(const char *str, uint8_t *data, int *len) int i; int slen = strlen(str); - if (slen/2 > *len) { + if (slen / 2 > *len) { fprintf(stderr, "Hex string too long.\n"); exit(update_error); } @@ -153,7 +154,7 @@ static void str2hex(const char *str, uint8_t *data, int *len) char tmp[3]; tmp[0] = str[i]; - tmp[1] = str[i+1]; + tmp[1] = str[i + 1]; tmp[2] = 0; data[*len] = strtol(tmp, &end, 16); @@ -250,9 +251,9 @@ static uint8_t *get_file_or_die(const char *filename, size_t *len_ptr) return data; } -#define USB_ERROR(m, r) \ - fprintf(stderr, "%s:%d, %s returned %d (%s)\n", __FILE__, __LINE__, \ - m, r, libusb_strerror(r)) +#define USB_ERROR(m, r) \ + fprintf(stderr, "%s:%d, %s returned %d (%s)\n", __FILE__, __LINE__, m, \ + r, libusb_strerror(r)) /* * Actual USB transfer function, the 'allow_less' flag indicates that the @@ -261,17 +262,14 @@ static uint8_t *get_file_or_die(const char *filename, size_t *len_ptr) * bytes were received. */ static void do_xfer(struct usb_endpoint *uep, void *outbuf, int outlen, - void *inbuf, int inlen, int allow_less, - size_t *rxed_count) + void *inbuf, int inlen, int allow_less, size_t *rxed_count) { - int r, actual; /* Send data out */ if (outbuf && outlen) { actual = 0; - r = libusb_bulk_transfer(uep->devh, uep->ep_num, - outbuf, outlen, + r = libusb_bulk_transfer(uep->devh, uep->ep_num, outbuf, outlen, &actual, 2000); if (r < 0) { USB_ERROR("libusb_bulk_transfer", r); @@ -286,11 +284,9 @@ static void do_xfer(struct usb_endpoint *uep, void *outbuf, int outlen, /* Read reply back */ if (inbuf && inlen) { - actual = 0; - r = libusb_bulk_transfer(uep->devh, uep->ep_num | 0x80, - inbuf, inlen, - &actual, 5000); + r = libusb_bulk_transfer(uep->devh, uep->ep_num | 0x80, inbuf, + inlen, &actual, 5000); if (r < 0) { USB_ERROR("libusb_bulk_transfer", r); exit(update_error); @@ -307,8 +303,8 @@ static void do_xfer(struct usb_endpoint *uep, void *outbuf, int outlen, } } -static void xfer(struct usb_endpoint *uep, void *outbuf, - size_t outlen, void *inbuf, size_t inlen, int allow_less) +static void xfer(struct usb_endpoint *uep, void *outbuf, size_t outlen, + void *inbuf, size_t inlen, int allow_less) { do_xfer(uep, outbuf, outlen, inbuf, inlen, allow_less, NULL); } @@ -321,8 +317,7 @@ static int find_endpoint(const struct libusb_interface_descriptor *iface, if (iface->bInterfaceClass == 255 && iface->bInterfaceSubClass == SUBCLASS && - iface->bInterfaceProtocol == PROTOCOL && - iface->bNumEndpoints) { + iface->bInterfaceProtocol == PROTOCOL && iface->bNumEndpoints) { ep = &iface->endpoint[0]; uep->ep_num = ep->bEndpointAddress & 0x7f; uep->chunk_len = ep->wMaxPacketSize; @@ -377,19 +372,19 @@ static int parse_vidpid(const char *input, uint16_t *vid_ptr, uint16_t *pid_ptr) return 0; *s++ = '\0'; - *vid_ptr = (uint16_t) strtoull(copy, &e, 16); + *vid_ptr = (uint16_t)strtoull(copy, &e, 16); if (!*optarg || (e && *e)) return 0; - *pid_ptr = (uint16_t) strtoull(s, &e, 16); + *pid_ptr = (uint16_t)strtoull(s, &e, 16); if (!*optarg || (e && *e)) return 0; return 1; } -static libusb_device_handle *check_device(libusb_device *dev, - uint16_t vid, uint16_t pid, char *serialno) +static libusb_device_handle *check_device(libusb_device *dev, uint16_t vid, + uint16_t pid, char *serialno) { struct libusb_device_descriptor desc; libusb_device_handle *handle = NULL; @@ -409,7 +404,9 @@ static libusb_device_handle *check_device(libusb_device *dev, if (desc.iSerialNumber) { ret = libusb_get_string_descriptor_ascii(handle, - desc.iSerialNumber, (unsigned char *)sn, sizeof(sn)); + desc.iSerialNumber, + (unsigned char *)sn, + sizeof(sn)); if (ret > 0) snvalid = 1; } @@ -428,8 +425,8 @@ static libusb_device_handle *check_device(libusb_device *dev, return NULL; } -static void usb_findit(uint16_t vid, uint16_t pid, - char *serialno, struct usb_endpoint *uep) +static void usb_findit(uint16_t vid, uint16_t pid, char *serialno, + struct usb_endpoint *uep) { int iface_num, r, i; libusb_device **devs; @@ -475,8 +472,8 @@ static void usb_findit(uint16_t vid, uint16_t pid, shut_down(uep); } - printf("found interface %d endpoint %d, chunk_len %d\n", - iface_num, uep->ep_num, uep->chunk_len); + printf("found interface %d endpoint %d, chunk_len %d\n", iface_num, + uep->ep_num, uep->chunk_len); libusb_set_auto_detach_kernel_driver(uep->devh, 1); r = libusb_claim_interface(uep->devh, iface_num); @@ -511,9 +508,8 @@ static int transfer_block(struct usb_endpoint *uep, } /* Now get the reply. */ - r = libusb_bulk_transfer(uep->devh, uep->ep_num | 0x80, - (void *) &reply, sizeof(reply), - &actual, 5000); + r = libusb_bulk_transfer(uep->devh, uep->ep_num | 0x80, (void *)&reply, + sizeof(reply), &actual, 5000); if (r) { if (r == -7) { fprintf(stderr, "Timeout!\n"); @@ -541,10 +537,8 @@ static int transfer_block(struct usb_endpoint *uep, * data_len - section size * smart_update - non-zero to enable the smart trailing of 0xff. */ -static void transfer_section(struct transfer_descriptor *td, - uint8_t *data_ptr, - uint32_t section_addr, - size_t data_len, +static void transfer_section(struct transfer_descriptor *td, uint8_t *data_ptr, + uint32_t section_addr, size_t data_len, uint8_t smart_update) { /* @@ -571,17 +565,16 @@ static void transfer_section(struct transfer_descriptor *td, struct update_frame_header ufh; ufh.block_size = htobe32(payload_size + - sizeof(struct update_frame_header)); + sizeof(struct update_frame_header)); ufh.cmd.block_base = block_base; ufh.cmd.block_digest = 0; for (max_retries = 10; max_retries; max_retries--) - if (!transfer_block(&td->uep, &ufh, - data_ptr, payload_size)) + if (!transfer_block(&td->uep, &ufh, data_ptr, + payload_size)) break; if (!max_retries) { - fprintf(stderr, - "Failed to transfer block, %zd to go\n", + fprintf(stderr, "Failed to transfer block, %zd to go\n", data_len); exit(update_error); } @@ -596,30 +589,27 @@ static void transfer_section(struct transfer_descriptor *td, * states. */ enum upgrade_status { - not_needed = 0, /* Version below or equal that on the target. */ - not_possible, /* - * RO is newer, but can't be transferred due to - * target RW shortcomings. - */ - needed /* - * This section needs to be transferred to the - * target. - */ + not_needed = 0, /* Version below or equal that on the target. */ + not_possible, /* + * RO is newer, but can't be transferred due to + * target RW shortcomings. + */ + needed /* + * This section needs to be transferred to the + * target. + */ }; /* This array describes all sections of the new image. */ static struct { const char *name; - uint32_t offset; - uint32_t size; - enum upgrade_status ustatus; + uint32_t offset; + uint32_t size; + enum upgrade_status ustatus; char version[32]; int32_t rollback; uint32_t key_version; -} sections[] = { - {"RO"}, - {"RW"} -}; +} sections[] = { { "RO" }, { "RW" } }; static const struct fmap_area *fmap_find_area_or_die(const struct fmap *fmap, const char *name) @@ -650,7 +640,7 @@ static void fetch_header_versions(const uint8_t *image, size_t len) fprintf(stderr, "Cannot find FMAP in image\n"); exit(update_error); } - fmap = (const struct fmap *)(image+offset); + fmap = (const struct fmap *)(image + offset); /* FIXME: validate fmap struct more than this? */ if (fmap->size != len) { @@ -693,15 +683,15 @@ static void fetch_header_versions(const uint8_t *image, size_t len) fprintf(stderr, "Invalid fwid size\n"); exit(update_error); } - memcpy(sections[i].version, image+fmaparea->offset, - fmaparea->size); + memcpy(sections[i].version, image + fmaparea->offset, + fmaparea->size); sections[i].rollback = -1; if (fmap_rollback_name) { fmaparea = fmap_find_area(fmap, fmap_rollback_name); if (fmaparea) memcpy(§ions[i].rollback, - image+fmaparea->offset, + image + fmaparea->offset, sizeof(sections[i].rollback)); } @@ -710,7 +700,8 @@ static void fetch_header_versions(const uint8_t *image, size_t len) fmaparea = fmap_find_area(fmap, fmap_key_name); if (fmaparea) { const struct vb21_packed_key *key = - (const void *)(image+fmaparea->offset); + (const void *)(image + + fmaparea->offset); sections[i].key_version = key->key_version; } } @@ -723,9 +714,9 @@ static int show_headers_versions(const void *image) for (i = 0; i < ARRAY_SIZE(sections); i++) { printf("%s off=%08x/%08x v=%.32s rb=%d kv=%d\n", - sections[i].name, sections[i].offset, sections[i].size, - sections[i].version, sections[i].rollback, - sections[i].key_version); + sections[i].name, sections[i].offset, sections[i].size, + sections[i].version, sections[i].rollback, + sections[i].key_version); } return 0; } @@ -772,17 +763,16 @@ static void setup_connection(struct transfer_descriptor *td) int actual = 0; /* Flush all data from endpoint to recover in case of error. */ - while (!libusb_bulk_transfer(td->uep.devh, - td->uep.ep_num | 0x80, - (void *)&inbuf, td->uep.chunk_len, - &actual, 10)) { + while (!libusb_bulk_transfer(td->uep.devh, td->uep.ep_num | 0x80, + (void *)&inbuf, td->uep.chunk_len, &actual, + 10)) { printf("flush\n"); } memset(&ufh, 0, sizeof(ufh)); ufh.block_size = htobe32(sizeof(ufh)); - do_xfer(&td->uep, &ufh, sizeof(ufh), &start_resp, - sizeof(start_resp), 1, &rxed_size); + do_xfer(&td->uep, &ufh, sizeof(ufh), &start_resp, sizeof(start_resp), 1, + &rxed_size); /* We got something. Check for errors in response */ if (rxed_size < 8) { @@ -803,10 +793,9 @@ static void setup_connection(struct transfer_descriptor *td) header_type = be16toh(start_resp.rpdu.header_type); printf("target running protocol version %d (type %d)\n", - protocol_version, header_type); + protocol_version, header_type); if (header_type != UPDATE_HEADER_TYPE_COMMON) { - fprintf(stderr, "Unsupported header type %d\n", - header_type); + fprintf(stderr, "Unsupported header type %d\n", header_type); exit(update_error); } @@ -820,7 +809,7 @@ static void setup_connection(struct transfer_descriptor *td) td->offset = be32toh(start_resp.rpdu.common.offset); memcpy(targ.common.version, start_resp.rpdu.common.version, - sizeof(start_resp.rpdu.common.version)); + sizeof(start_resp.rpdu.common.version)); targ.common.maximum_pdu_size = be32toh(start_resp.rpdu.common.maximum_pdu_size); targ.common.flash_protection = @@ -845,21 +834,20 @@ static void setup_connection(struct transfer_descriptor *td) * if it is - of what maximum size. */ static int ext_cmd_over_usb(struct usb_endpoint *uep, uint16_t subcommand, - void *cmd_body, size_t body_size, - void *resp, size_t *resp_size, - int allow_less) + void *cmd_body, size_t body_size, void *resp, + size_t *resp_size, int allow_less) { struct update_frame_header *ufh; uint16_t *frame_ptr; size_t usb_msg_size; - usb_msg_size = sizeof(struct update_frame_header) + - sizeof(subcommand) + body_size; + usb_msg_size = sizeof(struct update_frame_header) + sizeof(subcommand) + + body_size; ufh = malloc(usb_msg_size); if (!ufh) { - printf("%s: failed to allocate %zd bytes\n", - __func__, usb_msg_size); + printf("%s: failed to allocate %zd bytes\n", __func__, + usb_msg_size); return -1; } @@ -895,30 +883,28 @@ static void send_done(struct usb_endpoint *uep) } static void send_subcommand(struct transfer_descriptor *td, uint16_t subcommand, - void *cmd_body, size_t body_size, - uint8_t *response, size_t response_size) + void *cmd_body, size_t body_size, uint8_t *response, + size_t response_size) { send_done(&td->uep); - ext_cmd_over_usb(&td->uep, subcommand, - cmd_body, body_size, - response, &response_size, 0); + ext_cmd_over_usb(&td->uep, subcommand, cmd_body, body_size, response, + &response_size, 0); printf("sent command %x, resp %x\n", subcommand, response[0]); } /* Returns number of successfully transmitted image sections. */ -static int transfer_image(struct transfer_descriptor *td, - uint8_t *data, size_t data_len) +static int transfer_image(struct transfer_descriptor *td, uint8_t *data, + size_t data_len) { size_t i; int num_txed_sections = 0; for (i = 0; i < ARRAY_SIZE(sections); i++) if (sections[i].ustatus == needed) { - transfer_section(td, - data + sections[i].offset, - sections[i].offset, - sections[i].size, 1); + transfer_section(td, data + sections[i].offset, + sections[i].offset, sections[i].size, + 1); num_txed_sections++; } @@ -968,9 +954,8 @@ static void generate_reset_request(struct transfer_descriptor *td) command_body_size = 0; response_size = 1; subcommand = UPDATE_EXTRA_CMD_IMMEDIATE_RESET; - ext_cmd_over_usb(&td->uep, subcommand, - command_body, command_body_size, - &response, &response_size, 0); + ext_cmd_over_usb(&td->uep, subcommand, command_body, command_body_size, + &response, &response_size, 0); printf("reboot not triggered\n"); } @@ -987,7 +972,7 @@ static void get_random(uint8_t *data, int len) } while (i < len) { - int ret = fread(data+i, len-i, 1, fp); + int ret = fread(data + i, len - i, 1, fp); if (ret < 0) { perror("fread"); @@ -1005,7 +990,8 @@ static void read_console(struct transfer_descriptor *td) uint8_t payload[] = { 0x1 }; uint8_t response[64]; size_t response_size = 64; - struct timespec sleep_duration = { /* 100 ms */ + struct timespec sleep_duration = { + /* 100 ms */ .tv_sec = 0, .tv_nsec = 100l * 1000l * 1000l, }; @@ -1015,17 +1001,15 @@ static void read_console(struct transfer_descriptor *td) printf("\n"); while (1) { response_size = 1; - ext_cmd_over_usb(&td->uep, - UPDATE_EXTRA_CMD_CONSOLE_READ_INIT, - NULL, 0, - response, &response_size, 0); + ext_cmd_over_usb(&td->uep, UPDATE_EXTRA_CMD_CONSOLE_READ_INIT, + NULL, 0, response, &response_size, 0); while (1) { response_size = 64; ext_cmd_over_usb(&td->uep, UPDATE_EXTRA_CMD_CONSOLE_READ_NEXT, - payload, sizeof(payload), - response, &response_size, 1); + payload, sizeof(payload), response, + &response_size, 1); if (response[0] == 0) break; /* make sure it's null-terminated. */ @@ -1067,7 +1051,7 @@ int main(int argc, char *argv[]) memset(&td, 0, sizeof(td)); errorcnt = 0; - opterr = 0; /* quiet, you */ + opterr = 0; /* quiet, you */ while ((i = getopt_long(argc, argv, short_opts, long_opts, 0)) != -1) { switch (i) { case 'b': @@ -1091,8 +1075,8 @@ int main(int argc, char *argv[]) extra_command = UPDATE_EXTRA_CMD_TOUCHPAD_DEBUG; /* Maximum length. */ extra_command_data_len = 50; - str2hex(optarg, - extra_command_data, &extra_command_data_len); + str2hex(optarg, extra_command_data, + &extra_command_data_len); hexdump(extra_command_data, extra_command_data_len); extra_command_answer_len = 64; break; @@ -1112,8 +1096,8 @@ int main(int argc, char *argv[]) touchpad_update = 1; data = get_file_or_die(optarg, &data_len); - printf("read %zd(%#zx) bytes from %s\n", - data_len, data_len, argv[optind - 1]); + printf("read %zd(%#zx) bytes from %s\n", data_len, + data_len, argv[optind - 1]); break; case 'r': @@ -1127,8 +1111,7 @@ int main(int argc, char *argv[]) break; case 't': extra_command = UPDATE_EXTRA_CMD_TOUCHPAD_INFO; - extra_command_answer_len = - sizeof(struct touchpad_info); + extra_command_answer_len = sizeof(struct touchpad_info); break; case 'u': extra_command = UPDATE_EXTRA_CMD_UNLOCK_ROLLBACK; @@ -1136,7 +1119,7 @@ int main(int argc, char *argv[]) case 'w': extra_command = UPDATE_EXTRA_CMD_UNLOCK_RW; break; - case 0: /* auto-handled option */ + case 0: /* auto-handled option */ break; case '?': if (optopt) @@ -1167,8 +1150,8 @@ int main(int argc, char *argv[]) } data = get_file_or_die(argv[optind], &data_len); - printf("read %zd(%#zx) bytes from %s\n", - data_len, data_len, argv[optind]); + printf("read %zd(%#zx) bytes from %s\n", data_len, data_len, + argv[optind]); fetch_header_versions(data, data_len); @@ -1190,16 +1173,13 @@ int main(int argc, char *argv[]) if (data) { if (touchpad_update) { - transfer_section(&td, - data, - 0x80000000, - data_len, 0); + transfer_section(&td, data, 0x80000000, data_len, 0); free(data); send_done(&td.uep); } else { - transferred_sections = transfer_image(&td, - data, data_len); + transferred_sections = + transfer_image(&td, data, data_len); free(data); if (transferred_sections && !no_reset_request) @@ -1208,16 +1188,16 @@ int main(int argc, char *argv[]) } else if (extra_command == UPDATE_EXTRA_CMD_CONSOLE_READ_INIT) { read_console(&td); } else if (extra_command > -1) { - send_subcommand(&td, extra_command, - extra_command_data, extra_command_data_len, - extra_command_answer, extra_command_answer_len); + send_subcommand(&td, extra_command, extra_command_data, + extra_command_data_len, extra_command_answer, + extra_command_answer_len); switch (extra_command) { case UPDATE_EXTRA_CMD_TOUCHPAD_INFO: dump_touchpad_info(extra_command_answer, extra_command_answer_len); break; - case UPDATE_EXTRA_CMD_TOUCHPAD_DEBUG: + case UPDATE_EXTRA_CMD_TOUCHPAD_DEBUG: hexdump(extra_command_answer, extra_command_answer_len); break; } |