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 <alecaberg@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/203375
Reviewed-by: Todd Broch <tbroch@chromium.org>
This commit is contained in:
Vincent Palatin
2014-05-14 12:28:49 -07:00
committed by chrome-internal-fetch
parent 550669cbfb
commit 8ed54fbf07
6 changed files with 498 additions and 7 deletions

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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;

View File

@@ -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",

313
util/flash_pd.py Executable file
View File

@@ -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 [<switch args>] <firmware.bin>'
'\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()