From 8ed54fbf07c409d64b9fa75014f0b3f98c41f549 Mon Sep 17 00:00:00 2001 From: Vincent Palatin Date: Wed, 14 May 2014 12:28:49 -0700 Subject: pd: add commands to test flashing using PD communication Add console commands to send the Vendor-Defined Messages used to flash a USB-PD target. Also add a simple test script to flash Zinger through its CC line. To run the script, the board must have CONFIG_USB_PD_CUSTOM_VDM defined. By default fruitpie has this config option enabled. BRANCH=none BUG=chrome-os-partner:28330 TEST=With a fruitpie connected to a zinger run ./util/flash_pd.py ./build/zinger/ec.RW.flat and see Zinger booting on RW. Change-Id: I06f8f545e28b93b2e646e668d81b594eb7976a2d Signed-off-by: Alec Berg Reviewed-on: https://chromium-review.googlesource.com/203375 Reviewed-by: Todd Broch --- board/fruitpie/board.h | 1 + board/fruitpie/usb_pd_policy.c | 30 ++++ board/samus_pd/board.h | 1 + board/samus_pd/usb_pd_policy.c | 33 +++++ common/usb_pd_protocol.c | 127 ++++++++++++++++- util/flash_pd.py | 313 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 498 insertions(+), 7 deletions(-) create mode 100755 util/flash_pd.py diff --git a/board/fruitpie/board.h b/board/fruitpie/board.h index c9e4598042..c9924a9ba4 100644 --- a/board/fruitpie/board.h +++ b/board/fruitpie/board.h @@ -18,6 +18,7 @@ /* Optional features */ #define CONFIG_STM_HWTIMER32 #define CONFIG_USB_POWER_DELIVERY +#define CONFIG_USB_PD_CUSTOM_VDM #define CONFIG_USB_PD_DUAL_ROLE #define CONFIG_USB_PD_INTERNAL_COMP #define CONFIG_USBC_SS_MUX diff --git a/board/fruitpie/usb_pd_policy.c b/board/fruitpie/usb_pd_policy.c index 0ddeed9916..9ae9720db2 100644 --- a/board/fruitpie/usb_pd_policy.c +++ b/board/fruitpie/usb_pd_policy.c @@ -127,3 +127,33 @@ int pd_power_negotiation_allowed(void) { return 1; } + +/* ----------------- Vendor Defined Messages ------------------ */ +int pd_custom_vdm(int port, int cnt, uint32_t *payload, uint32_t **rpayload) +{ + int cmd = PD_VDO_CMD(payload[0]); + int i; + ccprintf("VDM/%d [%d] %08x\n", cnt, cmd, payload[0]); + + /* make sure we have some payload */ + if (cnt == 0) + return 0; + + switch (cmd) { + case VDO_CMD_VERSION: + /* guarantee last byte of payload is null character */ + *(payload + cnt - 1) = 0; + ccprintf("version: %s\n", (char *)(payload+1)); + break; + case VDO_CMD_RW_HASH: + ccprintf("RW Hash: "); + payload++; /* skip cmd */ + for (i = 0; i < cnt - 1; i++) + ccprintf("%08x ", *payload++); + ccprintf("\n"); + break; + } + + return 0; +} + diff --git a/board/samus_pd/board.h b/board/samus_pd/board.h index e7355b7ef3..329c3b7e03 100644 --- a/board/samus_pd/board.h +++ b/board/samus_pd/board.h @@ -19,6 +19,7 @@ #define CONFIG_BOARD_PRE_INIT #define CONFIG_STM_HWTIMER32 #define CONFIG_USB_POWER_DELIVERY +#define CONFIG_USB_PD_CUSTOM_VDM #define CONFIG_USB_PD_DUAL_ROLE #define CONFIG_USB_PD_INTERNAL_COMP #define CONFIG_USBC_SS_MUX diff --git a/board/samus_pd/usb_pd_policy.c b/board/samus_pd/usb_pd_policy.c index a99900b307..eeac9362cf 100644 --- a/board/samus_pd/usb_pd_policy.c +++ b/board/samus_pd/usb_pd_policy.c @@ -183,6 +183,37 @@ static void dual_role_force_sink(void) } DECLARE_HOOK(HOOK_CHIPSET_SHUTDOWN, dual_role_force_sink, HOOK_PRIO_DEFAULT); +/* ----------------- Vendor Defined Messages ------------------ */ +int pd_custom_vdm(int port, int cnt, uint32_t *payload, uint32_t **rpayload) +{ + int cmd = PD_VDO_CMD(payload[0]); + int i; + ccprintf("VDM/%d [%d] %08x\n", cnt, cmd, payload[0]); + + /* make sure we have some payload */ + if (cnt == 0) + return 0; + + switch (cmd) { + case VDO_CMD_VERSION: + /* guarantee last byte of payload is null character */ + *(payload + cnt - 1) = 0; + ccprintf("version: %s\n", (char *)(payload+1)); + break; + case VDO_CMD_RW_HASH: + ccprintf("RW Hash: "); + payload++; /* skip cmd */ + for (i = 0; i < cnt - 1; i++) + ccprintf("%08x ", *payload++); + ccprintf("\n"); + break; + } + + return 0; +} + +/****************************************************************************/ +/* Console commands */ static int command_ec_int(int argc, char **argv) { pd_send_ec_int(); @@ -194,6 +225,8 @@ DECLARE_CONSOLE_COMMAND(ecint, command_ec_int, "Toggle EC interrupt line", NULL); +/****************************************************************************/ +/* Host commands */ static int ec_status_host_cmd(struct host_cmd_handler_args *args) { const struct ec_params_pd_status *p = args->params; diff --git a/common/usb_pd_protocol.c b/common/usb_pd_protocol.c index e496e3e821..545eb36f8f 100644 --- a/common/usb_pd_protocol.c +++ b/common/usb_pd_protocol.c @@ -203,6 +203,7 @@ enum pd_states { PD_STATE_SNK_REQUESTED, PD_STATE_SNK_TRANSITION, PD_STATE_SNK_READY, + PD_STATE_VDM_COMM, #endif /* CONFIG_USB_PD_DUAL_ROLE */ PD_STATE_SRC_DISCONNECTED, @@ -432,6 +433,10 @@ static int send_request(int port, uint32_t rdo) return bit_len; } + +/* next Vendor Defined Message to send */ +static int vdo_count[PD_PORT_COUNT]; +static uint32_t vdo_data[PD_PORT_COUNT][7]; #endif /* CONFIG_USB_PD_DUAL_ROLE */ static int send_bist_cmd(int port) @@ -504,8 +509,13 @@ static void handle_vdm_request(int port, int cnt, uint32_t *payload) #ifdef CONFIG_USB_PD_CUSTOM_VDM int rlen; uint32_t *rdata; +#endif if (vid == USB_VID_GOOGLE) { +#ifdef CONFIG_USB_PD_DUAL_ROLE + vdo_count[port] = 0; /* Done */ +#endif +#ifdef CONFIG_USB_PD_CUSTOM_VDM rlen = pd_custom_vdm(port, cnt, payload, &rdata); if (rlen > 0) { uint16_t header = PD_HEADER(PD_DATA_VENDOR_DEF, @@ -513,9 +523,9 @@ static void handle_vdm_request(int port, int cnt, uint32_t *payload) rlen); send_validate_message(port, header, rlen, rdata); } +#endif return; } -#endif CPRINTF("Unhandled VDM VID %04x CMD %04x\n", vid, payload[0] & 0xFFFF); } @@ -836,7 +846,8 @@ static void execute_hard_reset(int port) { pd[port].msg_id = 0; #ifdef CONFIG_USB_PD_DUAL_ROLE - pd[port].task_state = pd[port].role == PD_ROLE_SINK ? + if (pd[port].task_state != PD_STATE_VDM_COMM) + pd[port].task_state = pd[port].role == PD_ROLE_SINK ? PD_STATE_SNK_DISCONNECTED : PD_STATE_SRC_DISCONNECTED; #else pd[port].task_state = PD_STATE_SRC_DISCONNECTED; @@ -1112,6 +1123,23 @@ void pd_task(void) } timeout = 100*MSEC; break; + case PD_STATE_VDM_COMM: + if (vdo_count[port] > 7) { /* TIMEOUT */ + vdo_count[port] = -EC_ERROR_TIMEOUT; + } else if (vdo_count[port] > 0) { + int len; + uint16_t header = PD_HEADER(PD_DATA_VENDOR_DEF, + pd[port].role, pd[port].msg_id, + vdo_count[port]); + len = send_validate_message(port, header, + vdo_count[port], + vdo_data[port]); + vdo_count[port] = 8; /* Transmitting */ + if (len < 0) + vdo_count[port] = -EC_ERROR_BUSY; + } + timeout = 500*MSEC; + break; #endif /* CONFIG_USB_PD_DUAL_ROLE */ case PD_STATE_HARD_RESET: send_hard_reset(port); @@ -1147,10 +1175,12 @@ void pd_task(void) #ifdef CONFIG_USB_PD_DUAL_ROLE if (pd[port].role == PD_ROLE_SINK && !pd_snk_is_vbus_provided(port)) { - /* Sink: detect disconnect by monitoring VBUS */ - pd[port].task_state = PD_STATE_SNK_DISCONNECTED; - /* set timeout small to reconnect fast */ - timeout = 5*MSEC; + if (pd[port].task_state != PD_STATE_VDM_COMM) { + /* Sink: detect disconnect by monitoring VBUS */ + pd[port].task_state = PD_STATE_SNK_DISCONNECTED; + /* set timeout small to reconnect fast */ + timeout = 5*MSEC; + } } #endif /* CONFIG_USB_PD_DUAL_ROLE */ } @@ -1169,6 +1199,87 @@ void pd_set_suspend(int port, int enable) task_wake(PORT_TO_TASK_ID(port)); } +static int hex8tou32(char *str, uint32_t *val) +{ + char *ptr = str; + uint32_t tmp = 0; + + while (*ptr) { + char c = *ptr++; + if (c >= '0' && c <= '9') + tmp = (tmp << 4) + (c - '0'); + else if (c >= 'A' && c <= 'F') + tmp = (tmp << 4) + (c - 'A' + 10); + else if (c >= 'a' && c <= 'f') + tmp = (tmp << 4) + (c - 'a' + 10); + else + return EC_ERROR_INVAL; + } + if (ptr != str + 8) + return EC_ERROR_INVAL; + *val = tmp; + return EC_SUCCESS; +} + +static int remote_flashing(int argc, char **argv) +{ + int port; + char *e; + static int flash_offset[PD_PORT_COUNT]; + + if (argc < 4) + return EC_ERROR_PARAM_COUNT; + + port = strtoi(argv[1], &e, 10); + if (*e || port >= PD_PORT_COUNT) + return EC_ERROR_PARAM2; + + if (!strcasecmp(argv[3], "erase")) { + vdo_data[port][0] = VDO(USB_VID_GOOGLE, VDO_CMD_FLASH_ERASE); + vdo_count[port] = 1; + flash_offset[port] = 0; + ccprintf("ERASE ..."); + } else if (!strcasecmp(argv[3], "reboot")) { + vdo_data[port][0] = VDO(USB_VID_GOOGLE, VDO_CMD_REBOOT); + vdo_count[port] = 1; + ccprintf("REBOOT ..."); + } else if (!strcasecmp(argv[3], "hash")) { + int i; + for (i = 4; i < argc; i++) + if (hex8tou32(argv[i], vdo_data[port] + i - 3)) + return EC_ERROR_INVAL; + vdo_data[port][0] = VDO(USB_VID_GOOGLE, VDO_CMD_FLASH_HASH); + vdo_count[port] = argc - 3; + ccprintf("HASH ..."); + } else if (!strcasecmp(argv[3], "rw_hash")) { + vdo_data[port][0] = VDO(USB_VID_GOOGLE, VDO_CMD_RW_HASH); + vdo_count[port] = 1; + ccprintf("RW HASH..."); + } else if (!strcasecmp(argv[3], "version")) { + vdo_data[port][0] = VDO(USB_VID_GOOGLE, VDO_CMD_VERSION); + vdo_count[port] = 1; + ccprintf("VERSION..."); + } else { + int i; + for (i = 3; i < argc; i++) + if (hex8tou32(argv[i], vdo_data[port] + i - 2)) + return EC_ERROR_INVAL; + vdo_data[port][0] = VDO(USB_VID_GOOGLE, VDO_CMD_FLASH_WRITE); + vdo_count[port] = argc - 2; + ccprintf("WRITE %d @%04x ...", (argc - 3) * 4, + flash_offset[port]); + flash_offset[port] += (argc - 3) * 4; + } + pd[port].task_state = PD_STATE_VDM_COMM; + task_wake(PORT_TO_TASK_ID(port)); + + /* Wait until VDO is done */ + while (vdo_count[port] > 0) + task_wait_event(100*MSEC); + ccprintf("DONE\n"); + return EC_SUCCESS; +} + void pd_request_source_voltage(int port, int mv) { pd_set_max_voltage(mv); @@ -1264,11 +1375,13 @@ static int command_pd(int argc, char **argv) else return EC_ERROR_PARAM3; } + } else if (!strncasecmp(argv[2], "flash", 4)) { + return remote_flashing(argc, argv); } else if (!strncasecmp(argv[2], "state", 5)) { const char * const state_names[] = { "DISABLED", "SUSPENDED", "SNK_DISCONNECTED", "SNK_DISCOVERY", "SNK_REQUESTED", - "SNK_TRANSITION", "SNK_READY", + "SNK_TRANSITION", "SNK_READY", "VDM_COMM", "SRC_DISCONNECTED", "SRC_DISCOVERY", "SRC_NEGOCIATE", "SRC_ACCEPTED", "SRC_TRANSITION", "SRC_READY", "HARD_RESET", "BIST", diff --git a/util/flash_pd.py b/util/flash_pd.py new file mode 100755 index 0000000000..b93e2628cc --- /dev/null +++ b/util/flash_pd.py @@ -0,0 +1,313 @@ +#!/usr/bin/env python +# Copyright (c) 2014 The Chromium OS Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +"""Flash PD PSU RW firmware over the USBPD comm channel using console.""" + +import array +import errno +import hashlib +import logging +import optparse +import os +import re +import socket +import sys +import time + +import serial +# TODO(tbroch): Discuss adding hdctools as an EC package RDEPENDS +from servo import client +from servo import multiservo + +VERSION = '0.0.1' + +# RW area is half of the 32-kB flash minus the hash storage area +MAX_FW_SIZE = 16 * 1024 - 32 +# Hash of RW when erased (set to all F's) +ERASED_RW_HASH = 'd582e94d 0d12a61c 1199927e 5610c036 2e2870a9' + + +class FlashPDError(Exception): + """Exception class for flash_pd utility.""" + + +class FlashPD(client.ServoClient): + """class to flash PD MCU. + + Note, + Some designs(samus) have multiple embedded MCUs. In that case the convention + is to name the pty associated with usbpd as 'usbpd_uart_pty'. In the case + where there is only one MCU (fruitpie) we prefer 'usbpd_uart_pty' but will + also associate 'ecu_uart_pty' with having capability to flash the UBS-PD + capable PSU(zinger). + + Attributes: + _options : Values instance from optparse. + + Public Methods: + expect : Examine console output for an expected response. + flash_command : Write a PD flash command and interrogate its result. + get_version : Retrieve current version of PD FW. + """ + + def __init__(self, options): + """Constructor. + + Args: + options : Values instance from optparse. + + Raises: + FlashPDError: If unable to determine the console pty + """ + super(FlashPD, self).__init__(host=options.server, port=options.port) + self._options = options + self._serial = None + + try: + pty = self.get('usbpd_uart_pty') + except socket.error as e: + raise FlashPDError('Can\'t connect to servod :: %s' % e) + except client.ServoClientError: + pty = self.get('ec_uart_pty') + if not pty: + raise FlashPDError('Unable to determine EC uart from servod') + + logging.debug('Opening serial connection to %s', pty) + try: + self._serial = serial.Serial(pty, timeout=1) + except OSError as e: + if e.errno == errno.EAGAIN: + # try twice if already open EAGAIN failure causes disconnect. + self._serial = serial.Serial(pty, timeout=1) + else: + raise FlashPDError('%s' % e) + + # quiet other channels that might pollute console. + self._serial.write('chan 1\n') + self._serial.flushOutput() + self._serial.flushInput() + + def __del__(self): + """Deconstructor.""" + if self._serial: + for l in self._serial: + logging.debug('flash: %s', l) + self._serial.write('chan 0xffffffff\n') + self._serial.write('chan restore\n') + self._serial.close() + + def expect(self, val, timeout=5): + """Scan serial output for particular string. + + Args: + val : string to look for + timeout : integer seconds to look before timing out. + + Returns: + tuple : boolean if 'val' found in console output. + string of line that had 'val' in it. + """ + done = False + deadline = time.time() + timeout + while not done and (time.time() < deadline): + l = None + for l in self._serial: + done = val in l + logging.debug('Is %s in: %s', val, l) + if done or time.time() > deadline: + break + if not done: + logging.error('\"%s\" missing', val) + return (done, l) + + def flash_command(self, cmd, expect='DONE'): + """Send PD Flash command and interrogate output. + + Args: + cmd : string of 'pd port flash' command to execute + expect : string of expected response after 'cmd' + + Returns: + found : boolean, whether response matches expected. + """ + self._serial.write('pd %d flash %s\n' % (self._options.multiport, cmd)) + (found, line) = self.expect(expect) + return (found, line) + + def get_version(self): + """Retreive PSU firmware version. + + Looks like: 'version: zinger_v1.1.1917-bfd' + + Returns: + version : string of version + Raises: + FlashPDError : if can't determine version + """ + (found, line) = self.flash_command('version', expect='version:') + logging.debug('is version in: %s', line) + m = False + if found: + m = re.match(r'.*version:\s+(\w+_v\d+\.\d+\.\d+-\S+).*', line) + if not m: + raise FlashPDError('Unable to determine PD FW version') + return m.group(1) + + +def flash_pd(options): + """Flash power delivery firmware.""" + + ec = FlashPD(options) + + with open(options.firmware) as fd: + fw = fd.read() + fw_size = len(fw) + # Compute SHA-1 hash for the full (padded) RW firmware + padded_fw = fw + '\xff' * (MAX_FW_SIZE - fw_size) + sha = hashlib.sha1(padded_fw).digest() + sha_str = ' '.join(['%08x' % (w) for w in array.array('I', sha)]) + + # pad the firmware to a multiple of 6 U32 + if fw_size % 24: + fw += '\xff'*(24 - fw_size % 24) + words = array.array('I', fw) + + logging.info('Current PD FW version is %s', ec.get_version()) + if options.versiononly: + return + + logging.info('Flashing %d bytes', fw_size) + + # reset flashed hash to reboot in RO + ec.flash_command('hash' + ' 00000000' * 5) + # reboot in RO + ec.flash_command('reboot') + # delay to give time to reboot + time.sleep(0.5) + # erase all RW partition + ec.flash_command('erase') + + # verify that erase was successful by reading hash of RW + (done, _) = ec.flash_command('rw_hash', expect=ERASED_RW_HASH) + if done: + done = ec.expect('DONE') + + if not done: + raise FlashPDError('Erase failed') + + logging.info('Successfully erased flash.') + + if options.eraseonly: + ec.flash_command('reboot') + logging.info('After erase, FW version is %s', ec.get_version()) + return + + # write firmware content + for i in xrange(len(words) / 6): + chunk = words[i * 6: (i + 1) * 6] + cmd = ' '.join(['%08x' % (w) for w in chunk]) + ec.flash_command(cmd) + + # write new firmware hash + ec.flash_command('hash ' + sha_str) + # reboot in RW + ec.flash_command('reboot') + # delay for reboot + time.sleep(0.2) + + logging.info('Flashing DONE.') + logging.info('SHA-1: %s', sha_str) + logging.info('New PD FW version is %s', ec.get_version()) + + +def parse_args(): + """Parse commandline arguments. + + Note, reads sys.argv directly + + Returns: + options : dict of from optparse.parse_args(). + + Raises: + FlashPDError : If problems with arguments + """ + description = ( + '%prog [] ' + '\n' + '%prog is a utility for flashing the USB-PD charger RW firmware over ' + 'the USB-PD communication channel using PD MCU console commands.' + ) + examples = ( + '\nExamples:\n' + ' %prog build/zinger/ec.RW.flat\n' + ) + parser = optparse.OptionParser(version='%prog ' + VERSION) + parser.description = description + parser.add_option('-d', '--debug', action='store_true', default=False, + help='enable debug messages.') + parser.add_option('-s', '--server', help='host where servod is running', + default=client.DEFAULT_HOST) + parser.add_option('-p', '--port', default=client.DEFAULT_PORT, type=int, + help='port servod is listening on.') + parser.add_option('-m', '--multiport', default=0, type=int, + help='If design has multiple type-C ports, this identifies ' + 'which one has USB PD PSU.') + parser.add_option('', '--timeout', default=5, type=int, + help='Timeout seconds to wait for console output.') + parser.add_option('', '--eraseonly', action='store_true', default=False, + help='Only erase RW portion and exit.') + parser.add_option('-V', '--versiononly', action='store_true', default=False, + help='Only read version and exit.') + multiservo.add_multiservo_parser_options(parser) + + parser.set_usage(parser.get_usage() + examples) + (options, args) = parser.parse_args() + + # TODO(tbroch) Add this once we refactor module to ease use in scripts. + if options.name: + raise NotImplementedError('Multiservo support TBD') + + # Add after to enumerate options.firmware but outside 'help' generation + parser.add_option('-f', '', action='store', type='string', dest='firmware') + + if len(args) != 1: + raise FlashPDError('Must supply power delivery firmware to write.') + + options.firmware = args[0] + if not os.path.exists(options.firmware): + raise FlashPDError('Unable to find file %s' % options.firmware) + + fw_size = os.path.getsize(options.firmware) + if fw_size > MAX_FW_SIZE: + raise FlashPDError('Firmware too large %d/%d' % (fw_size, MAX_FW_SIZE)) + + return options + + +def main_function(): + options = parse_args() + + loglevel = logging.INFO + log_format = '%(asctime)s - %(name)s - %(levelname)s' + if options.debug: + loglevel = logging.DEBUG + log_format += ' - %(filename)s:%(lineno)d:%(funcName)s' + log_format += ' - %(message)s' + logging.basicConfig(level=loglevel, format=log_format) + + flash_pd(options) + + +def main(): + """Main function wrapper to catch exceptions properly.""" + try: + main_function() + except KeyboardInterrupt: + sys.exit(0) + except FlashPDError as e: + print 'Error: ', e.message + sys.exit(1) + +if __name__ == '__main__': + main() -- cgit v1.2.1