diff options
Diffstat (limited to 'extra')
115 files changed, 0 insertions, 18414 deletions
diff --git a/extra/README b/extra/README deleted file mode 100644 index 874a7b1b32..0000000000 --- a/extra/README +++ /dev/null @@ -1,5 +0,0 @@ - -This directory is for experiments only. It is not built automatically, -required, supported, guaranteed to work, or necessarily well-documented. - -The contents may change without warning at any time. diff --git a/extra/cr50_rma_open/cr50_rma_open.py b/extra/cr50_rma_open/cr50_rma_open.py deleted file mode 100755 index 42ddbbac2d..0000000000 --- a/extra/cr50_rma_open/cr50_rma_open.py +++ /dev/null @@ -1,665 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# Copyright 2018 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. - -# Used to access the cr50 console and handle RMA Open -"""Open cr50 using RMA authentication. - -Run RMA Open to enable CCD on Cr50. The utility can be used to get a -url that will generate an authcode to open cr50. It can also be used to -try opening cr50 with the generated authcode. - -The last challenge is the only valid one, so don't generate a challenge -10 times and then use the first URL. You can only use the last one. - -For RMA Open: -Connect suzyq to the dut and your workstation. - -Check the basic setup with - sudo python cr50_rma_open.py -c - -If the setup is broken. Follow the debug print statements to try to fix -the error. Rerun until the script says Cr50 setup ok. - -After the setup is verified, run the following command to generate the -challenge url - sudo python cr50_rma_open.py -g -i $HWID - -Go to the URL from by that command to generate an authcode. Once you have -the authcode, you can use it to open cr50. - sudo python cr50_rma_open.py -a $AUTHCODE - -If for some reason hardware write protect doesn't get disabled during rma -open or gets enabled at some point the script can be used to disable -write protect. - sudo python cr50_rma_open.py -w - -When prepping devices for the testlab, you need to enable testlab mode. -Prod cr50 images can't enable testlab mode. If the device is running a -prod image, you can skip this step. - sudo python cr50_rma_open.py -t -""" - -import argparse -import glob -import logging -import re -import subprocess -import sys -import time - -import serial - -SCRIPT_VERSION = 5 -CCD_IS_UNRESTRICTED = 1 << 0 -WP_IS_DISABLED = 1 << 1 -TESTLAB_IS_ENABLED = 1 << 2 -RMA_OPENED = CCD_IS_UNRESTRICTED | WP_IS_DISABLED -URL = ('https://www.google.com/chromeos/partner/console/cr50reset?' - 'challenge=%s&hwid=%s') -RMA_SUPPORT_PROD = '0.3.3' -RMA_SUPPORT_PREPVT = '0.4.5' -DEV_MODE_OPEN_PROD = '0.3.9' -DEV_MODE_OPEN_PREPVT = '0.4.7' -TESTLAB_PROD = '0.3.10' -CR50_USB = '18d1:5014' -CR50_LSUSB_CMD = ['lsusb', '-vd', CR50_USB] -ERASED_BID = 'ffffffff' - -DEBUG_MISSING_USB = """ -Unable to find Cr50 Device 18d1:5014 - -DEBUG MISSING USB: - - Make sure suzyq is plugged into the correct DUT port - - Try flipping the cable - - unplug the cable for 5s then plug it back in -""" - -DEBUG_DEVICE = """ -DEBUG DEVICE COMMUNICATION: -Issues communicating with %s - -A 18d1:5014 device exists, so make sure you have selected the correct -/dev/ttyUSB -""" - -DEBUG_SERIALNAME = """ -DEBUG SERIALNAME: -Found the USB device, but can't match the usb serialname. Check the -serialname you passed into cr50_rma_open or try running without a -serialname. -""" - -DEBUG_CONNECTION = """ -DEBUG CONNECTION: -Found the USB device but cant communicate with any of the consoles. - -Try Running cr50_rma_open again. If it still fails unplug the ccd cable -for 5 seconds and plug it back in. -""" - -DEBUG_TOO_MANY_USB_DEVICES = """ -DEBUG SELECT USB: -More than one cr50 usb device was found. Disconnect all but one device -or use the -s option with the correct usb serialname. -""" - -DEBUG_ERASED_BOARD_ID = """ -DEBUG ERASED BOARD ID: -If you are using a prePVT device run -/usr/share/cros/cr50-set-board-id.sh proto - -If you are running a MP device, please talk to someone. -""" - -DEBUG_AUTHCODE_MISMATCH = """ -DEBUG AUTHCODE MISMATCH: - - Check the URL matches the one generated by the last cr50_rma_open - run. - - Check you used the correct authcode. - - Make sure the cr50 version is greater than 3.3. - - try generating another URL by rerunning the generate command and - rerunning the process. -""" - -DEBUG_DUT_CONTROL_OSERROR = """ -Run from chroot if you are trying to use a /dev/pts ccd servo console -""" - -class RMAOpen(object): - """Used to find the cr50 console and run RMA open""" - - ENABLE_TESTLAB_CMD = 'ccd testlab enabled\n' - - def __init__(self, device=None, usb_serial=None, servo_port=None, ip=None): - self.servo_port = servo_port if servo_port else '9999' - self.ip = ip - if device: - self.set_cr50_device(device) - elif servo_port: - self.find_cr50_servo_uart() - else: - self.find_cr50_device(usb_serial) - logging.info('DEVICE: %s', self.device) - self.check_version() - self.print_platform_info() - logging.info('Cr50 setup ok') - self.update_ccd_state() - self.using_ccd = self.device_is_running_with_servo_ccd() - - def _dut_control(self, control): - """Run dut-control and return the response""" - try: - cmd = ['dut-control', '-p', self.servo_port, control] - return subprocess.check_output(cmd, encoding='utf-8').strip() - except OSError: - logging.warning(DEBUG_DUT_CONTROL_OSERROR) - raise - - def find_cr50_servo_uart(self): - """Save the device used for the console. - - Find the console and configure it, so it can be used with this script. - """ - self._dut_control('cr50_uart_timestamp:off') - self.device = self._dut_control('cr50_uart_pty').split(':')[-1] - - def set_cr50_device(self, device): - """Save the device used for the console""" - self.device = device - - def send_cmd_get_output(self, cmd, nbytes=0): - """Send a cr50 command and get the output - - Args: - cmd: The cr50 command string - nbytes: The number of bytes to read from the console. If 0 read all - of the console output. - Returns: - The command output - """ - try: - ser = serial.Serial(self.device, timeout=1) - except OSError: - logging.warning('Permission denied %s', self.device) - logging.warning('Try running cr50_rma_open with sudo') - raise - write_cmd = cmd + '\n\n' - ser.write(write_cmd.encode('utf-8')) - if nbytes: - output = ser.read(nbytes) - else: - output = ser.readall() - ser.close() - - output = output.decode('utf-8').strip() if output else '' - # Return only the command output - split_cmd = cmd + '\r' - if cmd and split_cmd in output: - return ''.join(output.rpartition(split_cmd)[1::]).split('>')[0] - return output - - def device_is_running_with_servo_ccd(self): - """Return True if the device is a servod ccd console""" - # servod uses /dev/pts consoles. Non-servod uses /dev/ttyUSBX - if '/dev/pts' not in self.device: - return False - # If cr50 doesn't show rdd is connected, cr50 the device must not be - # a ccd device - if 'Rdd: connected' not in self.send_cmd_get_output('ccdstate'): - return False - # Check if the servod is running with ccd. This requires the script - # is run in the chroot, so run it last. - if 'ccd_cr50' not in self._dut_control('servo_type'): - return False - logging.info('running through servod ccd') - return True - - def get_rma_challenge(self): - """Get the rma_auth challenge - - There are two challenge formats - - " - ABEQ8 UGA4F AVEQP SHCKV - DGGPR N8JHG V8PNC LCHR2 - T27VF PRGBS N3ZXF RCCT2 - UBMKP ACM7E WUZUA A4GTN - " - and - " - generated challenge: - - CBYRYBEMH2Y75TC...rest of challenge - " - support extracting the challenge from both. - - Returns: - The RMA challenge with all whitespace removed. - """ - output = self.send_cmd_get_output('rma_auth').strip() - logging.info('rma_auth output:\n%s', output) - # Extract the challenge from the console output - if 'generated challenge:' in output: - return output.split('generated challenge:')[-1].strip() - challenge = ''.join(re.findall(r' \S{5}' * 4, output)) - # Remove all whitespace - return re.sub(r'\s', '', challenge) - - def generate_challenge_url(self, hwid): - """Get the rma_auth challenge - - Returns: - The RMA challenge with all whitespace removed. - """ - - challenge = self.get_rma_challenge() - self.print_platform_info() - logging.info('CHALLENGE: %s', challenge) - logging.info('HWID: %s', hwid) - url = URL % (challenge, hwid) - logging.info('GOTO:\n %s', url) - logging.info('If the server fails to debug the challenge make sure the ' - 'RLZ is allowlisted') - - def try_authcode(self, authcode): - """Try opening cr50 with the authcode - - Raises: - ValueError if there was no authcode match and ccd isn't open - """ - # rma_auth may cause the system to reboot. Don't wait to read all that - # output. Read the first 300 bytes and call it a day. - output = self.send_cmd_get_output('rma_auth ' + authcode, nbytes=300) - logging.info('CR50 RESPONSE: %s', output) - logging.info('waiting for cr50 reboot') - # Cr50 may be rebooting. Wait a bit - time.sleep(5) - if self.using_ccd: - # After reboot, reset the ccd endpoints - self._dut_control('power_state:ccd_reset') - # Update the ccd state after the authcode attempt - self.update_ccd_state() - - authcode_match = 'process_response: success!' in output - if not self.check(CCD_IS_UNRESTRICTED): - if not authcode_match: - logging.warning(DEBUG_AUTHCODE_MISMATCH) - message = 'Authcode mismatch. Check args and url' - else: - message = 'Could not set all capability privileges to Always' - raise ValueError(message) - - def wp_is_force_disabled(self): - """Returns True if write protect is forced disabled""" - output = self.send_cmd_get_output('wp') - wp_state = output.split('Flash WP:', 1)[-1].split('\n', 1)[0].strip() - logging.info('wp: %s', wp_state) - return wp_state == 'forced disabled' - - def testlab_is_enabled(self): - """Returns True if testlab mode is enabled""" - output = self.send_cmd_get_output('ccd testlab') - testlab_state = output.split('mode')[-1].strip().lower() - logging.info('testlab: %s', testlab_state) - return testlab_state == 'enabled' - - def ccd_is_restricted(self): - """Returns True if any of the capabilities are still restricted""" - output = self.send_cmd_get_output('ccd') - if 'Capabilities' not in output: - raise ValueError('Could not get ccd output') - logging.debug('CURRENT CCD SETTINGS:\n%s', output) - restricted = 'IfOpened' in output or 'IfUnlocked' in output - logging.info('ccd: %srestricted', '' if restricted else 'Un') - return restricted - - def update_ccd_state(self): - """Get the wp and ccd state from cr50. Save it in _ccd_state""" - self._ccd_state = 0 - if not self.ccd_is_restricted(): - self._ccd_state |= CCD_IS_UNRESTRICTED - if self.wp_is_force_disabled(): - self._ccd_state |= WP_IS_DISABLED - if self.testlab_is_enabled(): - self._ccd_state |= TESTLAB_IS_ENABLED - - def check(self, setting): - """Returns true if the all of the 1s in setting are 1 in _ccd_state""" - return self._ccd_state & setting == setting - - def _has_testlab_support(self): - """Return True if you can enable testlab mode""" - # all prepvt images can enable testlab - if self.is_prepvt: - return True - return not self._running_version_is_older(DEV_MODE_OPEN_PROD) - - def _capabilities_allow_open_from_console(self): - """Return True if ccd open is Always allowed from usb""" - output = self.send_cmd_get_output('ccd') - return (re.search('OpenNoDevMode.*Always', output) and - re.search('OpenFromUSB.*Always', output)) - - def _requires_dev_mode_open(self): - """Return True if the image requires dev mode to open""" - if self._capabilities_allow_open_from_console(): - return False - # All prod images that support 'open' require dev mode - if not self.is_prepvt: - return True - return not self._running_version_is_older(DEV_MODE_OPEN_PREPVT) - - def _run_on_dut(self, command): - """Run the command on the DUT.""" - return subprocess.check_output(['ssh', self.ip, command], - encoding='utf-8') - - def _open_in_dev_mode(self): - """Open Cr50 when it's in dev mode""" - output = self.send_cmd_get_output('ccd') - # If the device is already open, nothing needs to be done. - if 'State: Open' not in output: - # Verify the device is in devmode before trying to run open. - if 'dev_mode' not in output: - logging.warning('Enter dev mode to open ccd or update to %s', - TESTLAB_PROD) - raise ValueError('DUT not in dev mode') - if not self.ip: - logging.warning("If your DUT doesn't have ssh support, run " - "'gsctool -a -o' from the AP") - raise ValueError('Cannot run ccd open without dut ip') - self._run_on_dut('gsctool -a -o') - # Wait >1 second for cr50 to update ccd state - time.sleep(3) - output = self.send_cmd_get_output('ccd') - if 'State: Open' not in output: - raise ValueError('Could not open cr50') - logging.info('ccd is open') - - def enable_testlab(self): - """Disable write protect""" - if not self._has_testlab_support(): - logging.warning('Testlab mode is not supported in prod iamges') - return - # Some cr50 images need to be in dev mode before they can be opened. - if self._requires_dev_mode_open(): - self._open_in_dev_mode() - else: - self.send_cmd_get_output('ccd open') - logging.info('Enabling testlab mode reqires pressing the power button.') - logging.info('Once the process starts keep tapping the power button ' - 'for 10 seconds.') - input("Press Enter when you're ready to start...") - end_time = time.time() + 15 - - ser = serial.Serial(self.device, timeout=1) - printed_lines = '' - output = '' - # start ccd testlab enable - ser.write(self.ENABLE_TESTLAB_CMD.encode('utf-8')) - logging.info('start pressing the power button\n\n') - # Print all of the cr50 output as we get it, so the user will have more - # information about pressing the power button. Tapping the power button - # a couple of times should do it, but this will give us more confidence - # the process is still running/worked. - try: - while time.time() < end_time: - output += ser.read(100).decode('utf-8') - full_lines = output.rsplit('\n', 1)[0] - new_lines = full_lines - if printed_lines: - new_lines = full_lines.split(printed_lines, 1)[-1].strip() - logging.info('\n%s', new_lines) - printed_lines = full_lines - - # Make sure the process hasn't ended. If it has, print the last - # of the output and exit. - new_lines = output.split(printed_lines, 1)[-1] - if 'CCD test lab mode enabled' in output: - # print the last of the ou - logging.info(new_lines) - break - elif 'Physical presence check timeout' in output: - logging.info(new_lines) - logging.warning('Did not detect power button press in time') - raise ValueError('Could not enable testlab mode try again') - finally: - ser.close() - # Wait for the ccd hook to update things - time.sleep(3) - # Update the state after attempting to disable write protect - self.update_ccd_state() - if not self.check(TESTLAB_IS_ENABLED): - raise ValueError('Could not enable testlab mode try again') - - def wp_disable(self): - """Disable write protect""" - logging.info('Disabling write protect') - self.send_cmd_get_output('wp disable') - # Update the state after attempting to disable write protect - self.update_ccd_state() - if not self.check(WP_IS_DISABLED): - raise ValueError('Could not disable write protect') - - def check_version(self): - """Make sure cr50 is running a version that supports RMA Open""" - output = self.send_cmd_get_output('version') - if not output.strip(): - logging.warning(DEBUG_DEVICE, self.device) - raise ValueError('Could not communicate with %s' % self.device) - - version = re.search(r'RW.*\* ([\d\.]+)/', output).group(1) - logging.info('Running Cr50 Version: %s', version) - self.running_ver_fields = [int(field) for field in version.split('.')] - - # prePVT images have even major versions. Prod have odd - self.is_prepvt = self.running_ver_fields[1] % 2 == 0 - rma_support = RMA_SUPPORT_PREPVT if self.is_prepvt else RMA_SUPPORT_PROD - - logging.info('%s RMA support added in: %s', - 'prePVT' if self.is_prepvt else 'prod', rma_support) - if not self.is_prepvt and self._running_version_is_older(TESTLAB_PROD): - raise ValueError('Update cr50. No testlab support in old prod ' - 'images.') - if self._running_version_is_older(rma_support): - raise ValueError('%s does not have RMA support. Update to at ' - 'least %s' % (version, rma_support)) - - def _running_version_is_older(self, target_ver): - """Returns True if running version is older than target_ver.""" - target_ver_fields = [int(field) for field in target_ver.split('.')] - for i, field in enumerate(self.running_ver_fields): - if field > int(target_ver_fields[i]): - return False - return True - - def device_matches_devid(self, devid, device): - """Return True if the device matches devid. - - Use the sysinfo output from device to determine if it matches devid - - Returns: - True if sysinfo from device shows the given devid. False if there - is no output or sysinfo doesn't contain the devid. - """ - self.set_cr50_device(device) - sysinfo = self.send_cmd_get_output('sysinfo') - # Make sure there is some output, and it shows it's from Cr50 - if not sysinfo or 'cr50' not in sysinfo: - return False - logging.debug('Sysinfo output: %s', sysinfo) - # The cr50 device id should be in the sysinfo output, if we found - # the right console. Make sure it is - return devid in sysinfo - - def find_cr50_device(self, usb_serial): - """Find the cr50 console device - - The Cr50 usb serialname matches the cr50 devid. Convert the serialname - to devid. Use that to check all of the consoles and find cr50's. - - Args: - usb_serial: an optional string. The serialname of the cr50 usb - device - Raises: - ValueError if the console can't be found with the given serialname - """ - usb_serial = self.find_cr50_usb(usb_serial) - logging.info('SERIALNAME: %s', usb_serial) - devid = '0x' + ' 0x'.join(usb_serial.lower().split('-')) - logging.info('DEVID: %s', devid) - - # Get all the usb devices - devices = glob.glob('/dev/ttyUSB*') - # Typically Cr50 has the lowest number. Sort the devices, so we're more - # likely to try the cr50 console first. - devices.sort() - - # Find the one that is the cr50 console - for device in devices: - logging.info('testing %s', device) - if self.device_matches_devid(devid, device): - logging.info('found device: %s', device) - return - logging.warning(DEBUG_CONNECTION) - raise ValueError('Found USB device, but could not communicate with ' - 'cr50 console') - - def print_platform_info(self): - """Print the cr50 BID RLZ code""" - bid_output = self.send_cmd_get_output('bid') - bid = re.search(r'Board ID: (\S+?)[:,]', bid_output).group(1) - if bid == ERASED_BID: - logging.warning(DEBUG_ERASED_BOARD_ID) - raise ValueError('Cannot run RMA Open when board id is erased') - bid = int(bid, 16) - chrs = [chr((bid >> (8 * i)) & 0xff) for i in range(4)] - logging.info('RLZ: %s', ''.join(chrs[::-1])) - - @staticmethod - def find_cr50_usb(usb_serial): - """Make sure the Cr50 USB device exists""" - try: - output = subprocess.check_output(CR50_LSUSB_CMD, encoding='utf-8') - except: - logging.warning(DEBUG_MISSING_USB) - raise ValueError('Could not find Cr50 USB device') - serialnames = re.findall(r'iSerial +\d+ (\S+)\s', output) - if usb_serial: - if usb_serial not in serialnames: - logging.warning(DEBUG_SERIALNAME) - raise ValueError('Could not find usb device "%s"' % usb_serial) - return usb_serial - if len(serialnames) > 1: - logging.info('Found Cr50 device serialnames %s', - ', '.join(serialnames)) - logging.warning(DEBUG_TOO_MANY_USB_DEVICES) - raise ValueError('Too many cr50 usb devices') - return serialnames[0] - - def print_dut_state(self): - """Print CCD RMA and testlab mode state.""" - if not self.check(CCD_IS_UNRESTRICTED): - logging.info('CCD is still restricted.') - logging.info('Run cr50_rma_open.py -g -i $HWID to generate a url') - logging.info('Run cr50_rma_open.py -a $AUTHCODE to open cr50 with ' - 'an authcode') - elif not self.check(WP_IS_DISABLED): - logging.info('WP is still enabled.') - logging.info('Run cr50_rma_open.py -w to disable write protect') - if self.check(RMA_OPENED): - logging.info('RMA Open complete') - - if not self.check(TESTLAB_IS_ENABLED) and self.is_prepvt: - logging.info('testlab mode is disabled.') - logging.info('If you are prepping a device for the testlab, you ' - 'should enable testlab mode.') - logging.info('Run cr50_rma_open.py -t to enable testlab mode') - - -def parse_args(argv): - """Get cr50_rma_open args.""" - parser = argparse.ArgumentParser( - description=__doc__, formatter_class=argparse.RawTextHelpFormatter) - parser.add_argument('-g', '--generate_challenge', action='store_true', - help='Generate Cr50 challenge. Must be used with -i') - parser.add_argument('-t', '--enable_testlab', action='store_true', - help='enable testlab mode') - parser.add_argument('-w', '--wp_disable', action='store_true', - help='Disable write protect') - parser.add_argument('-c', '--check_connection', action='store_true', - help='Check cr50 console connection works') - parser.add_argument('-s', '--serialname', type=str, default='', - help='The cr50 usb serialname') - parser.add_argument('-D', '--debug', action='store_true', - help='print debug messages') - parser.add_argument('-d', '--device', type=str, default='', - help='cr50 console device ex /dev/ttyUSB0') - parser.add_argument('-i', '--hwid', type=str, default='', - help='The board hwid. Needed to generate a challenge') - parser.add_argument('-a', '--authcode', type=str, default='', - help='The authcode string from the challenge url') - parser.add_argument('-P', '--servo_port', type=str, default='', - help='the servo port') - parser.add_argument('-I', '--ip', type=str, default='', - help='The DUT IP. Necessary to do ccd open') - return parser.parse_args(argv) - - -def main(argv): - """Run cr50 rma open.""" - opts = parse_args(argv) - - loglevel = logging.INFO - log_format = '%(levelname)7s' - if opts.debug: - loglevel = logging.DEBUG - log_format += ' - %(lineno)3d:%(funcName)-15s' - log_format += ' - %(message)s' - logging.basicConfig(level=loglevel, format=log_format) - - tried_authcode = False - logging.info('Running cr50_rma_open version %s', SCRIPT_VERSION) - - cr50_rma_open = RMAOpen(opts.device, opts.serialname, opts.servo_port, - opts.ip) - if opts.check_connection: - sys.exit(0) - - if not cr50_rma_open.check(CCD_IS_UNRESTRICTED): - if opts.generate_challenge: - if not opts.hwid: - logging.warning('--hwid necessary to generate challenge url') - sys.exit(0) - cr50_rma_open.generate_challenge_url(opts.hwid) - sys.exit(0) - elif opts.authcode: - logging.info('Using authcode: %s', opts.authcode) - cr50_rma_open.try_authcode(opts.authcode) - tried_authcode = True - - if not cr50_rma_open.check(WP_IS_DISABLED) and (tried_authcode or - opts.wp_disable): - if not cr50_rma_open.check(CCD_IS_UNRESTRICTED): - raise ValueError("Can't disable write protect unless ccd is " - "open. Run through the rma open process first") - if tried_authcode: - logging.warning('RMA Open did not disable write protect. File a ' - 'bug') - logging.warning('Trying to disable it manually') - cr50_rma_open.wp_disable() - - if not cr50_rma_open.check(TESTLAB_IS_ENABLED) and opts.enable_testlab: - if not cr50_rma_open.check(CCD_IS_UNRESTRICTED): - raise ValueError("Can't enable testlab mode unless ccd is open." - "Run through the rma open process first") - cr50_rma_open.enable_testlab() - - cr50_rma_open.print_dut_state() - - -if __name__ == '__main__': - sys.exit(main(sys.argv[1:])) diff --git a/extra/ftdi_hostcmd/.gitignore b/extra/ftdi_hostcmd/.gitignore deleted file mode 100644 index 9831af29be..0000000000 --- a/extra/ftdi_hostcmd/.gitignore +++ /dev/null @@ -1 +0,0 @@ -test_cmds diff --git a/extra/ftdi_hostcmd/Makefile b/extra/ftdi_hostcmd/Makefile deleted file mode 100644 index d46b4b1c72..0000000000 --- a/extra/ftdi_hostcmd/Makefile +++ /dev/null @@ -1,37 +0,0 @@ -# Copyright 2015 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. - -# Use your own libmpsse if you want, but we're going to use the files -# that are part of the Chromium OS trunks_client program. -PLATFORM2 = ../../../../platform2 -MPSSE_DIR = $(PLATFORM2)/trunks/ftdi - -PROG = test_cmds -SRCS = test_cmds.c $(MPSSE_DIR)/mpsse.c $(MPSSE_DIR)/support.c - -CFLAGS = \ - -std=gnu99 \ - -g3 \ - -O3 \ - -Wall \ - -Werror \ - -Wpointer-arith \ - -Wcast-align \ - -Wcast-qual \ - -Wundef \ - -Wsign-compare \ - -Wredundant-decls \ - -Wmissing-declarations - -CFLAGS += -I../../include -I${MPSSE_DIR} -I${PLATFORM2} - -CFLAGS += $(shell pkg-config --cflags libusb-1.0 libftdi1) -LIBS += $(shell pkg-config --libs libusb-1.0 libftdi1) - -$(PROG): $(SRCS) Makefile - gcc $(CFLAGS) $(SRCS) $(LDFLAGS) $(LIBS) -o $@ - -.PHONY: clean -clean: - rm -rf $(PROG) diff --git a/extra/ftdi_hostcmd/README b/extra/ftdi_hostcmd/README deleted file mode 100644 index 4de2f45fb3..0000000000 --- a/extra/ftdi_hostcmd/README +++ /dev/null @@ -1,6 +0,0 @@ - -Ubuntu Trusty uses an ancient version of libftdi. - -You'll probably want to grab the latest libftdi1-1.2.tar.bz2 from -http://www.intra2net.com/en/developer/libftdi/ and install it into /usr -instead. diff --git a/extra/ftdi_hostcmd/test_cmds.c b/extra/ftdi_hostcmd/test_cmds.c deleted file mode 100644 index 4552476d0f..0000000000 --- a/extra/ftdi_hostcmd/test_cmds.c +++ /dev/null @@ -1,620 +0,0 @@ -/* Copyright 2015 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. - */ - -#include <signal.h> -#include <stdint.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> - -#include "mpsse.h" - -#include "ec_commands.h" - -static int opt_verbose; - -/* Communication handle */ -static struct mpsse_context *mpsse; - -/* enum ec_status meaning */ -static const char *ec_strerr(enum ec_status r) -{ - static const char * const strs[] = { - "SUCCESS", - "INVALID_COMMAND", - "ERROR", - "INVALID_PARAM", - "ACCESS_DENIED", - "INVALID_RESPONSE", - "INVALID_VERSION", - "INVALID_CHECKSUM", - "IN_PROGRESS", - "UNAVAILABLE", - "TIMEOUT", - "OVERFLOW", - "INVALID_HEADER", - "REQUEST_TRUNCATED", - "RESPONSE_TOO_BIG", - "BUS_ERROR", - "BUSY", - }; - if (r >= EC_RES_SUCCESS && r <= EC_RES_BUSY) - return strs[r]; - - return "<undefined result>"; -}; - - -/**************************************************************************** - * Debugging output - */ - -#define LINELEN 16 - -static void showline(uint8_t *buf, int len) -{ - int i; - printf(" "); - for (i = 0; i < len; i++) - printf(" %02x", buf[i]); - for (i = len; i < LINELEN; i++) - printf(" "); - printf(" "); - for (i = 0; i < len; i++) - printf("%c", - (buf[i] >= ' ' && buf[i] <= '~') ? buf[i] : '.'); - printf("\n"); -} - -static void show(const char *fmt, uint8_t *buf, int len) -{ - int i, m, n; - - if (!opt_verbose) - return; - - printf(fmt, len); - - m = len / LINELEN; - n = len % LINELEN; - - for (i = 0; i < m; i++) - showline(buf + i * LINELEN, LINELEN); - if (n) - showline(buf + m * LINELEN, n); -} - -/**************************************************************************** - * Send command & receive result - */ - -/* - * With proto v3, the kernel driver asks the EC for the max param size - * (EC_CMD_GET_PROTOCOL_INFO) at probe time, because it can vary depending on - * the bus and/or the supported commands. - * - * FIXME: For now we'll just hard-code a size. - */ -static uint8_t txbuf[128]; - -/* - * Load the output buffer with a proto v3 request (header, then data, with - * checksum correct in header). - */ -static size_t prepare_request(int cmd, int version, - const uint8_t *data, size_t data_len) -{ - struct ec_host_request *request; - size_t i, total_len; - uint8_t csum = 0; - - total_len = sizeof(*request) + data_len; - if (total_len > sizeof(txbuf)) { - printf("Request too large (%zd > %zd)\n", - total_len, sizeof(txbuf)); - return -1; - } - - /* Header first */ - request = (struct ec_host_request *)txbuf; - request->struct_version = EC_HOST_REQUEST_VERSION; - request->checksum = 0; - request->command = cmd; - request->command_version = version; - request->reserved = 0; - request->data_len = data_len; - - /* Then data */ - memcpy(txbuf + sizeof(*request), data, data_len); - - /* Update checksum */ - for (i = 0; i < total_len; i++) - csum += txbuf[i]; - request->checksum = -csum; - - return total_len; -} - -/* - * Sends prepared proto v3 command using the SPI protocol - * - * Returns zero if command was sent, nonzero otherwise. - */ -static int send_request(uint8_t *txbuf, size_t len) -{ - uint8_t *tptr; - size_t i; - int ret = 0; - - show("Transfer(%d) =>\n", txbuf, len); - tptr = Transfer(mpsse, txbuf, len); - - if (!tptr) { - fprintf(stderr, "Transfer failed: %s\n", - ErrorString(mpsse)); - return -1; - } - - show("Transfer(%d) <=\n", tptr, len); - - /* Make sure the EC was listening */ - for (i = 0; i < len; i++) { - switch (tptr[i]) { - case EC_SPI_PAST_END: - case EC_SPI_RX_BAD_DATA: - case EC_SPI_NOT_READY: - ret = tptr[i]; - /* FALLTHROUGH */ - default: - break; - } - if (ret) - break; - } - free(tptr); - return ret; -} - - -/* Timeout flag, so we don't wait forever */ -static int timedout; -static void alarm_handler(int sig) -{ - timedout = 1; -} - -/* - * Read proto v3 response from SPI bus - * - * The response header and data are copied into the provided locations. - * - * Return value: - * 0 = response received (check hdr for EC result and body size) - * -1 = problems - */ -static int get_response(struct ec_host_response *hdr, - uint8_t *bodydest, size_t bodylen) -{ - uint8_t *hptr = 0, *bptr = 0; - uint8_t sum = 0; - int ret = -1; - size_t i; - - /* Give up eventually */ - timedout = 0; - if (SIG_ERR == signal(SIGALRM, alarm_handler)) { - perror("Problem with signal handler"); - goto out; - } - alarm(3); - - /* Read a byte at a time until we see the start of the frame. - * This is slow, but still faster than the EC. */ - while (1) { - uint8_t *ptr = Read(mpsse, 1); - if (!ptr) { - fprintf(stderr, "Read failed: %s\n", - ErrorString(mpsse)); - alarm(0); - goto out; - } - if (*ptr == EC_SPI_FRAME_START) { - free(ptr); - break; - } - free(ptr); - - if (timedout) { - fprintf(stderr, "timed out\n"); - goto out; - } - } - alarm(0); - - /* Now read the response header */ - hptr = Read(mpsse, sizeof(*hdr)); - if (!hptr) { - fprintf(stderr, "Read failed: %s\n", - ErrorString(mpsse)); - goto out; - } - show("Header(%d):\n", hptr, sizeof(*hdr)); - memcpy(hdr, hptr, sizeof(*hdr)); - - /* Check the header */ - if (hdr->struct_version != EC_HOST_RESPONSE_VERSION) { - printf("response version %d (should be %d)\n", - hdr->struct_version, - EC_HOST_RESPONSE_VERSION); - goto out; - } - - if (hdr->data_len > bodylen) { - printf("response data_len %d is > %zd\n", - hdr->data_len, - bodylen); - goto out; - } - - /* Read the data */ - if (hdr->data_len) { - bptr = Read(mpsse, hdr->data_len); - if (!bptr) { - fprintf(stderr, "Read failed: %s\n", - ErrorString(mpsse)); - goto out; - } - show("Body(%d):\n", bptr, hdr->data_len); - memcpy(bodydest, bptr, hdr->data_len); - } - - /* Verify the checksum */ - for (i = 0; i < sizeof(hdr); i++) - sum += hptr[i]; - for (i = 0; i < hdr->data_len; i++) - sum += bptr[i]; - if (sum) - - printf("Checksum invalid\n"); - else - ret = 0; - -out: - if (hptr) - free(hptr); - if (bptr) - free(bptr); - return ret; -} - - -/* - * Send command, wait for result. Return zero if communication succeeded; check - * response to see if the EC liked the command. - */ -static int send_cmd(int cmd, int version, - void *outbuf, - size_t outsize, - struct ec_host_response *resp, - void *inbuf, - size_t insize) -{ - - size_t len; - int ret = -1; - - /* Load up the txbuf with the stuff to send */ - len = prepare_request(cmd, version, outbuf, outsize); - if (len < 0) - return -1; - - if (MPSSE_OK != Start(mpsse)) { - fprintf(stderr, "Start failed: %s\n", - ErrorString(mpsse)); - return -1; - } - - if (0 == send_request(txbuf, len) && - 0 == get_response(resp, inbuf, insize)) - ret = 0; - - if (MPSSE_OK != Stop(mpsse)) { - fprintf(stderr, "Stop failed: %s\n", - ErrorString(mpsse)); - return -1; - } - - return ret; -} - - -/**************************************************************************** - * Probe for basic protocol info - */ - -/** - * Try to talk to the attached(?) device. - * - * @return zero on success - */ -static int probe_v3(void) -{ - struct ec_host_response resp; - struct ec_response_get_protocol_info info; - int i, ret; - - memset(&resp, 0, sizeof(resp)); - memset(&info, 0, sizeof(info)); - - if (opt_verbose) - printf("Trying EC_CMD_GET_PROTOCOL_INFO...\n"); - - ret = send_cmd(EC_CMD_GET_PROTOCOL_INFO, 0, - 0, 0, - &resp, - &info, sizeof(info)); - - if (ret) { - printf("EC_CMD_GET_PROTOCOL_INFO failed\n"); - return -1; - } - - if (EC_RES_SUCCESS != resp.result) { - printf("EC result is %d: %s\n", - resp.result, ec_strerr(resp.result)); - return -1; - } - - printf("EC_CMD_GET_PROTOCOL_INFO Success!\n"); - printf(" protocol_versions: "); - for (i = 0; i < 32; i++) - if (info.protocol_versions & (1 << i)) - printf(" %d", i); - printf("\n"); - printf(" max_request_packet_size: %d\n", - info.max_request_packet_size); - printf(" max_response_packet_size: %d\n", - info.max_response_packet_size); - printf(" flags: 0x%x\n", - info.flags); - - return 0; -} - -/**************************************************************************** - * Pretty-print the host commands that the device admits to having - */ - -struct lookup { - uint16_t cmd; - const char * const desc; -}; - -static struct lookup cmd_table[] = { - {0x00, "EC_CMD_PROTO_VERSION"}, - {0x01, "EC_CMD_HELLO"}, - {0x02, "EC_CMD_GET_VERSION"}, - {0x03, "EC_CMD_READ_TEST"}, - {0x04, "EC_CMD_GET_BUILD_INFO"}, - {0x05, "EC_CMD_GET_CHIP_INFO"}, - {0x06, "EC_CMD_GET_BOARD_VERSION"}, - {0x07, "EC_CMD_READ_MEMMAP"}, - {0x08, "EC_CMD_GET_CMD_VERSIONS"}, - {0x09, "EC_CMD_GET_COMMS_STATUS"}, - {0x0a, "EC_CMD_TEST_PROTOCOL"}, - {0x0b, "EC_CMD_GET_PROTOCOL_INFO"}, - {0x0c, "EC_CMD_GSV_PAUSE_IN_S5"}, - {0x0d, "EC_CMD_GET_FEATURES"}, - {0x10, "EC_CMD_FLASH_INFO"}, - {0x11, "EC_CMD_FLASH_READ"}, - {0x12, "EC_CMD_FLASH_WRITE"}, - {0x13, "EC_CMD_FLASH_ERASE"}, - {0x15, "EC_CMD_FLASH_PROTECT"}, - {0x16, "EC_CMD_FLASH_REGION_INFO"}, - {0x17, "EC_CMD_VBNV_CONTEXT"}, - {0x20, "EC_CMD_PWM_GET_FAN_TARGET_RPM"}, - {0x21, "EC_CMD_PWM_SET_FAN_TARGET_RPM"}, - {0x22, "EC_CMD_PWM_GET_KEYBOARD_BACKLIGHT"}, - {0x23, "EC_CMD_PWM_SET_KEYBOARD_BACKLIGHT"}, - {0x24, "EC_CMD_PWM_SET_FAN_DUTY"}, - {0x28, "EC_CMD_LIGHTBAR_CMD"}, - {0x29, "EC_CMD_LED_CONTROL"}, - {0x2a, "EC_CMD_VBOOT_HASH"}, - {0x2b, "EC_CMD_MOTION_SENSE_CMD"}, - {0x2c, "EC_CMD_FORCE_LID_OPEN"}, - {0x30, "EC_CMD_USB_CHARGE_SET_MODE"}, - {0x40, "EC_CMD_PSTORE_INFO"}, - {0x41, "EC_CMD_PSTORE_READ"}, - {0x42, "EC_CMD_PSTORE_WRITE"}, - {0x44, "EC_CMD_RTC_GET_VALUE"}, - {0x45, "EC_CMD_RTC_GET_ALARM"}, - {0x46, "EC_CMD_RTC_SET_VALUE"}, - {0x47, "EC_CMD_RTC_SET_ALARM"}, - {0x48, "EC_CMD_PORT80_LAST_BOOT"}, - {0x48, "EC_CMD_PORT80_READ"}, - {0x50, "EC_CMD_THERMAL_SET_THRESHOLD"}, - {0x51, "EC_CMD_THERMAL_GET_THRESHOLD"}, - {0x52, "EC_CMD_THERMAL_AUTO_FAN_CTRL"}, - {0x53, "EC_CMD_TMP006_GET_CALIBRATION"}, - {0x54, "EC_CMD_TMP006_SET_CALIBRATION"}, - {0x55, "EC_CMD_TMP006_GET_RAW"}, - {0x60, "EC_CMD_MKBP_STATE"}, - {0x61, "EC_CMD_MKBP_INFO"}, - {0x62, "EC_CMD_MKBP_SIMULATE_KEY"}, - {0x64, "EC_CMD_MKBP_SET_CONFIG"}, - {0x65, "EC_CMD_MKBP_GET_CONFIG"}, - {0x66, "EC_CMD_KEYSCAN_SEQ_CTRL"}, - {0x67, "EC_CMD_GET_NEXT_EVENT"}, - {0x70, "EC_CMD_TEMP_SENSOR_GET_INFO"}, - {0x87, "EC_CMD_HOST_EVENT_GET_B"}, - {0x88, "EC_CMD_HOST_EVENT_GET_SMI_MASK"}, - {0x89, "EC_CMD_HOST_EVENT_GET_SCI_MASK"}, - {0x8d, "EC_CMD_HOST_EVENT_GET_WAKE_MASK"}, - {0x8a, "EC_CMD_HOST_EVENT_SET_SMI_MASK"}, - {0x8b, "EC_CMD_HOST_EVENT_SET_SCI_MASK"}, - {0x8c, "EC_CMD_HOST_EVENT_CLEAR"}, - {0x8e, "EC_CMD_HOST_EVENT_SET_WAKE_MASK"}, - {0x8f, "EC_CMD_HOST_EVENT_CLEAR_B"}, - {0x90, "EC_CMD_SWITCH_ENABLE_BKLIGHT"}, - {0x91, "EC_CMD_SWITCH_ENABLE_WIRELESS"}, - {0x92, "EC_CMD_GPIO_SET"}, - {0x93, "EC_CMD_GPIO_GET"}, - {0x94, "EC_CMD_I2C_READ"}, - {0x95, "EC_CMD_I2C_WRITE"}, - {0x96, "EC_CMD_CHARGE_CONTROL"}, - {0x97, "EC_CMD_CONSOLE_SNAPSHOT"}, - {0x98, "EC_CMD_CONSOLE_READ"}, - {0x99, "EC_CMD_BATTERY_CUT_OFF"}, - {0x9a, "EC_CMD_USB_MUX"}, - {0x9b, "EC_CMD_LDO_SET"}, - {0x9c, "EC_CMD_LDO_GET"}, - {0x9d, "EC_CMD_POWER_INFO"}, - {0x9e, "EC_CMD_I2C_PASSTHRU"}, - {0x9f, "EC_CMD_HANG_DETECT"}, - {0xa0, "EC_CMD_CHARGE_STATE"}, - {0xa1, "EC_CMD_CHARGE_CURRENT_LIMIT"}, - {0xa2, "EC_CMD_EXT_POWER_CURRENT_LIMIT"}, - {0xb0, "EC_CMD_SB_READ_WORD"}, - {0xb1, "EC_CMD_SB_WRITE_WORD"}, - {0xb2, "EC_CMD_SB_READ_BLOCK"}, - {0xb3, "EC_CMD_SB_WRITE_BLOCK"}, - {0xb4, "EC_CMD_BATTERY_VENDOR_PARAM"}, - {0xb5, "EC_CMD_SB_FW_UPDATE"}, - {0xd2, "EC_CMD_REBOOT_EC"}, - {0xd3, "EC_CMD_GET_PANIC_INFO"}, - {0xd1, "EC_CMD_REBOOT"}, - {0xdb, "EC_CMD_RESEND_RESPONSE"}, - {0xdc, "EC_CMD_VERSION0"}, - {0x100, "EC_CMD_PD_EXCHANGE_STATUS"}, - {0x104, "EC_CMD_PD_HOST_EVENT_STATUS"}, - {0x101, "EC_CMD_USB_PD_CONTROL"}, - {0x102, "EC_CMD_USB_PD_PORTS"}, - {0x103, "EC_CMD_USB_PD_POWER_INFO"}, - {0x110, "EC_CMD_USB_PD_FW_UPDATE"}, - {0x111, "EC_CMD_USB_PD_RW_HASH_ENTRY"}, - {0x112, "EC_CMD_USB_PD_DEV_INFO"}, - {0x113, "EC_CMD_USB_PD_DISCOVERY"}, - {0x114, "EC_CMD_PD_CHARGE_PORT_OVERRIDE"}, - {0x115, "EC_CMD_PD_GET_LOG_ENTRY"}, - {0x116, "EC_CMD_USB_PD_GET_AMODE"}, - {0x117, "EC_CMD_USB_PD_SET_AMODE"}, - {0x118, "EC_CMD_PD_WRITE_LOG_ENTRY"}, - {0x200, "EC_CMD_BLOB"}, -}; - -#define ARRAY_SIZE(A) (sizeof(A) / sizeof(A[0])) - -static void show_command(uint16_t c) -{ - unsigned int i; - const char *desc = "<unknown>"; - - for (i = 0; i < ARRAY_SIZE(cmd_table); i++) - if (cmd_table[i].cmd == c) { - desc = cmd_table[i].desc; - break; - } - - printf(" %02x %s\n", c, desc); -} - -static void scan_commands(uint16_t start, uint16_t stop) -{ - struct ec_params_get_cmd_versions_v1 q_vers; - struct ec_response_get_cmd_versions r_vers; - struct ec_host_response ec_resp; - uint16_t i; - - memset(&ec_resp, 0, sizeof(ec_resp)); - - printf("Supported host commands:\n"); - for (i = start; i <= stop; i++) { - - if (opt_verbose) - printf("Querying CMD %02x\n", i); - - q_vers.cmd = i; - if (0 != send_cmd(EC_CMD_GET_CMD_VERSIONS, 1, - &q_vers, sizeof(q_vers), - &ec_resp, - &r_vers, sizeof(r_vers))) { - printf("query failed on cmd %02x - aborting\n", i); - return; - } - - switch (ec_resp.result) { - case EC_RES_SUCCESS: - if (opt_verbose) - printf("Yes: "); - show_command(i); - break; - case EC_RES_INVALID_PARAM: - if (opt_verbose) - printf("No\n"); - break; - default: - printf("lookup of cmd %02x returned %d %s\n", i, - ec_resp.result, - ec_strerr(ec_resp.result)); - } - } -} - -/****************************************************************************/ - -static void usage(char *progname) -{ - printf("Usage: %s [-v] [start [stop]]\n", progname); -} - -int main(int argc, char *argv[]) -{ - int retval = 1; - int errorcnt = 0; - int i; - uint16_t start = cmd_table[0].cmd; - uint16_t stop = cmd_table[ARRAY_SIZE(cmd_table) - 1].cmd; - - while ((i = getopt(argc, argv, ":v")) != -1) { - switch (i) { - case 'v': - opt_verbose++; - break; - case '?': - printf("unrecognized option: -%c\n", optopt); - errorcnt++; - break; - } - } - if (errorcnt) { - usage(argv[0]); - return 1; - } - - /* Range (no error checking) */ - if (optind < argc) - start = (uint16_t)strtoull(argv[optind++], 0, 0); - if (optind < argc) - stop = (uint16_t)strtoull(argv[optind++], 0, 0); - - /* Find something to talk to */ - mpsse = MPSSE(SPI0, 1000000, 0); - if (!mpsse) { - printf("Can't find a device to open\n"); - return 1; - } - - if (0 != probe_v3()) - goto out; - - scan_commands(start, stop); - - retval = 0; -out: - Close(mpsse); - mpsse = 0; - return retval; -} diff --git a/extra/i2c_pseudo/.gitignore b/extra/i2c_pseudo/.gitignore deleted file mode 100644 index 98ec2e970f..0000000000 --- a/extra/i2c_pseudo/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -.i2c-pseudo.ko.cmd -.i2c-pseudo.mod.o.cmd -.i2c-pseudo.o.cmd -.tmp_versions/ -Module.symvers -i2c-pseudo.ko -i2c-pseudo.mod.c -i2c-pseudo.mod.o -i2c-pseudo.o -modules.order diff --git a/extra/i2c_pseudo/50-i2c-pseudo.rules b/extra/i2c_pseudo/50-i2c-pseudo.rules deleted file mode 100644 index 22a4c8daf0..0000000000 --- a/extra/i2c_pseudo/50-i2c-pseudo.rules +++ /dev/null @@ -1 +0,0 @@ -DEVPATH=="/devices/virtual/i2c-pseudo/*", GROUP="plugdev", MODE="0660" diff --git a/extra/i2c_pseudo/Documentation.txt b/extra/i2c_pseudo/Documentation.txt deleted file mode 100644 index 77de99574b..0000000000 --- a/extra/i2c_pseudo/Documentation.txt +++ /dev/null @@ -1,291 +0,0 @@ -----Introduction---- - -Usually I2C adapters are implemented in a kernel driver. It is also possible to -implement an adapter in userspace, through the /dev/i2c-pseudo-controller -interface. Load module i2c-pseudo for this. - -Use cases for this module include: - -[A] Using local I2C device drivers, particularly i2c-dev, with I2C busses on -remote systems. For example, interacting with a Device Under Test (DUT) -connected to a Linux host through a debug interface, or interacting with a -remote host over a network. - -[B] Support I2C device driver tests that are too complex for the i2c-stub -module. For example, when simulating an I2C device where its driver might -issue a sequence of reads and writes without interruption, and the value a -certain address must change during the sequence. - -Any possible use case could of course be implemented as a kernel driver. -However, it can be much faster and easier to implement such things in userspace, -thanks to the far greater code reuse possibilities (libraries), the plethora of -programming language options for rapid iteration, and not needing to understand -how to implement Linux kernel drivers. - -This is not intended to replace kernel drivers for actual I2C busses on the -local host machine. - - - -----Details---- - -Each time /dev/i2c-pseudo-controller is opened, and the correct initialization -command is written to it (ADAPTER_START), a new I2C adapter is created. The -adapter will live until its file descriptor is closed. Multiple pseudo adapters -can co-exist simultaneously, controlled by the same or different userspace -processes. When an I2C device driver sends an I2C message to a pseudo adapter, -the message becomes readable from its file descriptor. If a reply is written -before the adapter timeout expires, that reply will be sent back to the I2C -device driver. - -Reads and writes are buffered inside i2c-pseudo such that userspace controllers -may split them up into arbitrarily small chunks. Multiple commands, or portions -of multiple commands, may be read or written together. - -Blocking I/O is the default. Non-blocking I/O is supported as well, enabled by -O_NONBLOCK. Polling is supported, with or without non-blocking I/O. A special -command (ADAPTER_SHUTDOWN) is available to unblock any pollers or blocked -reads or writes, as a convenience for a multi-threaded or multi-process program -that wants to exit. - -It is safe to access a single controller fd from multiple threads or processes -concurrently, though it is up to the controller to ensure proper ordering, and -to ensure that writes for different commands do not get interleaved. However, -it is recommended (not required) that controller implementations have only one -reader thread and one writer thread, which may or may not be the same thread. -Avoiding multiple readers and multiple writers greatly simplifies controller -implementation, and there is likely no performance benefit to be gained from -concurrent reads or concurrent writes due to how i2c-pseudo serializes them -internally. After all, on a real I2C bus only one I2C message can be active at -a time. - -Commands are newline-terminated, both those read from the controller device, and -those written to it. - - - -----Read Commands---- - -The commands that may be read from a pseudo controller device are: - - -Read Command: I2C_ADAPTER_NUM <num> -Example: "I2C_ADAPTER_NUM 5\n" - -Details: This is read in response to the GET_ADAPTER_NUM command being written. -The number is the I2C adapter number in decimal. This can only occur after -ADAPTER_START, because before that the number is not known and cannot be -predicted reliably. - - -Read Command: I2C_PSEUDO_ID <num> -Example: "I2C_PSEUDO_ID 98\n" - -Details: This is read in response to the GET_PSEUDO_ID command being written. -The number is the pseudo ID in decimal. - - -Read Command: I2C_BEGIN_XFER -Example: "I2C_BEGIN_XFER\n" - -Details: This indicates the start of an I2C transaction request, in other words -the start of the I2C messages from a single invocation of the I2C adapter's -master_xfer() callback. This can only occur after ADAPTER_START. - - -Read Command: I2C_XFER_REQ <xfer_id> <msg_id> <addr> <flags> <data_len> [<write_byte>[:...]] -Example: "I2C_XFER_REQ 3 0 0x0070 0x0000 2 AB:9F\n" -Example: "I2C_XFER_REQ 3 1 0x0070 0x0001 4\n" - -Details: This is a single I2C message that a device driver requested be sent on -the bus, in other words a single struct i2c_msg from master_xfer() msgs arg. - -The xfer_id is a number representing the whole I2C transaction, thus all -I2C_XFER_REQ between a I2C_BEGIN_XFER + I2C_COMMIT_XFER pair share an xfer_id. -The purpose is to ensure replies from the userspace controller are always -properly matched to the intended master_xfer() request. The first transaction -has xfer_id 0, and it increases by 1 with each transaction, however it will -eventually wrap back to 0 if enough transactions happen during the lifetime of a -pseudo adapter. It is guaranteed to have a large enough maximum value such that -there can never be multiple outstanding transactions with the same ID, due to an -internal limit in i2c-pseudo that will block master_xfer() calls when the -controller is falling behind in its replies. - -The msg_id is a decimal number representing the index of the I2C message within -its transaction, in other words the index in master_xfer() *msgs array arg. -This starts at 0 after each I2C_BEGIN_XFER. This is guaranteed to not wrap. - -The addr is the hexadecimal I2C address for this I2C message. The address is -right-aligned without any read/write bit. - -The flags are the same bitmask flags used in struct i2c_msg, in hexadecimal -form. Of particular importance to any pseudo controller is the read bit, which -is guaranteed to be 0x1 per Linux I2C documentation. - -The data_len is the decimal number of either how many bytes to write that will -follow, or how many bytes to read and reply with if this is a read request. - -If this is a read, data_len will be the final field in this command. If this is -a write, data_len will be followed by the given number of colon-separated -hexadecimal byte values, in the format shown in the example above. - - -Read Command: I2C_COMMIT_XFER -Example: "I2C_COMMIT_XFER\n" - -Details: This indicates the end of an I2C transaction request, in other words -the end of the I2C messages from a single invocation of the I2C adapter's -master_xfer() callback. This should be read exactly once after each -I2C_BEGIN_XFER, with a varying number of I2C_XFER_REQ between them. - - - -----Write Commands---- - -The commands that may be written to a pseudo controller device are: - - -Write Command: SET_ADAPTER_NAME_SUFFIX <suffix> -Example: "SET_ADAPTER_NAME_SUFFIX My Adapter\n" - -Details: Sets a suffix to append to the auto-generated I2C adapter name. Only -valid before ADAPTER_START. A space or other separator character will be placed -between the auto-generated name and the suffix, so there is no need to include a -leading separator in the suffix. If the resulting name is too long for the I2C -adapter name field, it will be quietly truncated. - - -Write Command: SET_ADAPTER_TIMEOUT_MS <ms> -Example: "SET_ADAPTER_TIMEOUT_MS 2000\n" - -Details: Sets the timeout in milliseconds for each I2C transaction, in other -words for each master_xfer() reply. Only valid before ADAPTER_START. The I2C -subsystem will automatically time out transactions based on this setting. Set -to 0 to use the I2C subsystem default timeout. The default timeout for new -pseudo adapters where this command has not been used is configurable at -i2c-pseudo module load time, and itself has a default independent from the I2C -subsystem default. (Though if the i2c-pseudo module level default is set to 0, -that has the same meaning as here.) - - -Write Command: ADAPTER_START -Example: "ADAPTER_START\n" - -Details: Tells i2c-pseudo to actually create the I2C adapter. Only valid once -per open controller fd. - - -Write Command: GET_ADAPTER_NUM -Example: "GET_ADAPTER_NUM\n" - -Details: Asks i2c-pseudo for the number assigned to this I2C adapter by the I2C -subsystem. Only valid after ADAPTER_START, because before that the number -is not known and cannot be predicted reliably. - - -Write Command: GET_PSEUDO_ID -Example: "GET_PSEUDO_ID\n" - -Details: Asks i2c-pseudo for the pseudo ID of this I2C adapter. The pseudo ID -will not be reused for the lifetime of the i2c-pseudo module, unless an internal -counter wraps. I2C clients can use this to track specific instances of pseudo -adapters, even when adapter numbers have been reused. - - -Write Command: I2C_XFER_REPLY <xfer_id> <msg_id> <addr> <flags> <errno> [<read_byte>[:...]] -Example: "I2C_XFER_REPLY 3 0 0x0070 0x0000 0\n" -Example: "I2C_XFER_REPLY 3 1 0x0070 0x0001 0 0B:29:02:D9\n" - -Details: This is how a pseudo controller can reply to I2C_XFER_REQ. Only valid -after I2C_XFER_REQ. A pseudo controller should write one of these for each -I2C_XFER_REQ it reads, including for failures, so that I2C device drivers need -not wait for the adapter timeout upon failure (if failure is known sooner). - -The fields in common with I2C_XFER_REQ have their same meanings, and their -values are expected to exactly match what was read in the I2C_XFER_REQ command -that this is in reply to. - -The errno field is how the pseudo controller indicates success or failure for -this I2C message. A 0 value indicates success. A non-zero value indicates a -failure. Pseudo controllers are encouraged to use errno values to encode some -meaning in a failure response, but that is not a requirement, and the I2C -adapter interface does not provide a way to pass per-message errno values to a -device driver anyways. - -Pseudo controllers are encouraged to reply in the same order as messages were -received, however i2c-pseudo will properly match up out-of-order replies with -their original requests. - - -Write Command: ADAPTER_SHUTDOWN -Example: "ADAPTER_SHUTDOWN\n" - -Details: This tells i2c-pseudo that the pseudo controller wants to shutdown and -intends to close the controller device fd soon. Use of this is OPTIONAL, it is -perfectly valid to close the controller device fd without ever using this -command. - -This commands unblocks any blocked controller I/O (reads, writes, or polls), and -that is its main purpose. - -Any I2C transactions attempted by a device driver after this command will fail, -and will not be passed on to the userspace controller. - -This DOES NOT delete the I2C adapter. Only closing the fd will do that. That -MAY CHANGE in the future, such that this does delete the I2C adapter. (However -this will never be required, it will always be okay to simply close the fd.) - - - -----Example userspace controller code---- - -In C, a simple exchange between i2c-pseudo and userspace might look like the -example below. Note that for brevity this lacks any error checking and -handling, which a real pseudo controller implementation should have. - -int fd; -char buf[1<<12]; - -fd = open("/dev/i2c-pseudo-controller", O_RDWR); -/* Create the I2C adapter. */ -dprintf(fd, "ADAPTER_START\n"); - -/* - * Pretend this I2C adapter number is 5, and the first I2C xfer sent to it was - * from this command (using its i2c-dev interface): - * $ i2cset -y 5 0x70 0xC2 - * - * Then this read would place the following into *buf: - * "I2C_BEGIN_XFER\n" - * "I2C_XFER_REQ 0 0 0x0070 0x0000 1 C2\n" - * "I2C_COMMIT_XFER\n" - */ -read(fd, buf, sizeof(buf)); - -/* This reply would allow the i2cset command above to exit successfully. */ -dprintf(fd, "I2C_XFER_REPLY 0 0 0x0070 0x0000 0\n"); - -/* - * Now pretend the next I2C xfer sent to this adapter was from: - * $ i2cget -y 5 0x70 0xAB - * - * Then this read would place the following into *buf: - * "I2C_BEGIN_XFER\n" - * "I2C_XFER_REQ 1 0 0x0070 0x0000 1 AB\n" - * "I2C_XFER_REQ 1 1 0x0070 0x0001 1\n'" - * "I2C_COMMIT_XFER\n" - */ -read(fd, buf, sizeof(buf)); - -/* - * These replies would allow the i2cget command above to print the following to - * stdout and exit successfully: - * 0x0b - * - * Note that it is also valid to write these together in one write(). - */ -dprintf(fd, "I2C_XFER_REPLY 1 0 0x0070 0x0000 0\n"); -dprintf(fd, "I2C_XFER_REPLY 1 1 0x0070 0x0001 0 0B\n"); - -/* Destroy the I2C adapter. */ -close(fd); diff --git a/extra/i2c_pseudo/Makefile b/extra/i2c_pseudo/Makefile deleted file mode 100644 index f7fda6e2de..0000000000 --- a/extra/i2c_pseudo/Makefile +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright 2020 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. -# -# Makefile for i2c-pseudo module. Typical usage: -# $ make -# $ sudo make modules_install -# $ make clean - -obj-m := i2c-pseudo.o - -.PHONY: all - -all: modules - -CFLAGS_i2c-pseudo.o += "-DHAVE_STREAM_OPEN=$(shell "$M"/check_stream_open.sh)" - -.DEFAULT: - $(MAKE) -C /lib/modules/$(shell uname -r)/build \ - M=$(shell pwd) \ - $(MAKECMDGOALS) diff --git a/extra/i2c_pseudo/README b/extra/i2c_pseudo/README deleted file mode 100644 index 96efa062b1..0000000000 --- a/extra/i2c_pseudo/README +++ /dev/null @@ -1,20 +0,0 @@ -This directory contains the i2c-pseudo Linux kernel module. - -The i2c-pseudo module was written with the intention of being submitted upstream -in the Linux kernel. This copy exists because of as 2019-03 this module is not -yet in the upstream kernel, and even if/when this is included, it may take years -before making its way to the prepackaged Linux distribution kernels typically -used by CrOS developers. - -See Documentation.txt for more information about the module itself. That file -is Documentation/i2c/pseudo-controller-interface in the upstream patch. - -When servod starts, if the i2c-pseudo module is loaded servod will automatically -create an I2C pseudo adapter for the Servo I2C bus. That I2C adapter may then -be used in userspace through i2c-dev (/dev/i2c-<N>). The i2c-tools package -provides command line utilities for interfacing with i2c-dev devices, and some -CrOS software can work directly with i2c-dev devices as well, such as iteflash -which is used by flash_ec when reflashing an ITE EC through a Servo. - -Automated installation: -$ ./install diff --git a/extra/i2c_pseudo/check_stream_open.sh b/extra/i2c_pseudo/check_stream_open.sh deleted file mode 100755 index da802cb282..0000000000 --- a/extra/i2c_pseudo/check_stream_open.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/sh -# -# Copyright 2020 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. -# -# This checks whether stream_open symbol is available from the target kernel. -# -# Output meanings: -# -1 : stream_open is not available -# 0 : unknown whether or not stream_open is available -# 1 : stream_open is available - -symbols="$(< "/lib/modules/$(uname -r)/build/Module.symvers" \ - awk '{print $2}' | grep -E '^(nonseekable_open|stream_open)$')" - -if echo "${symbols}" | grep -q '^stream_open$'; then - echo 1 -elif echo "${symbols}" | grep -q '^nonseekable_open$'; then - echo -1 -else - echo 0 -fi diff --git a/extra/i2c_pseudo/i2c-pseudo.c b/extra/i2c_pseudo/i2c-pseudo.c deleted file mode 100644 index e4f1852cd8..0000000000 --- a/extra/i2c_pseudo/i2c-pseudo.c +++ /dev/null @@ -1,3212 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * This Linux kernel module implements pseudo I2C adapters that can be backed - * by userspace programs. This allows for implementing an I2C bus from - * userspace, which can tunnel the I2C commands through another communication - * channel to a remote I2C bus. - */ - -#include <linux/build_bug.h> -#include <linux/cdev.h> -#include <linux/completion.h> -#include <linux/device.h> -#include <linux/errno.h> -#include <linux/fs.h> -#include <linux/i2c.h> -#include <linux/init.h> -#include <linux/jiffies.h> -#include <linux/kernel.h> -#include <linux/kobject.h> -#include <linux/list.h> -#include <linux/module.h> -#include <linux/mutex.h> -#include <linux/poll.h> -#include <linux/slab.h> -#include <linux/string.h> -#include <linux/time64.h> -#include <linux/types.h> -#include <linux/uaccess.h> -#include <linux/wait.h> -#include <stdarg.h> - -/* Minimum i2cp_limit module parameter value. */ -#define I2CP_ADAPTERS_MIN 0 -/* Maximum i2cp_limit module parameter value. */ -#define I2CP_ADAPTERS_MAX 256 -/* Default i2cp_limit module parameter value. */ -#define I2CP_DEFAULT_LIMIT 8 -/* Value for alloc_chrdev_region() baseminor arg. */ -#define I2CP_CDEV_BASEMINOR 0 -#define I2CP_TIMEOUT_MS_MIN 0 -#define I2CP_TIMEOUT_MS_MAX (60 * MSEC_PER_SEC) -#define I2CP_DEFAULT_TIMEOUT_MS (3 * MSEC_PER_SEC) - -/* Used in struct device.kobj.name field. */ -#define I2CP_DEVICE_NAME "i2c-pseudo-controller" -/* Value for alloc_chrdev_region() name arg. */ -#define I2CP_CHRDEV_NAME "i2c_pseudo" -/* Value for class_create() name arg. */ -#define I2CP_CLASS_NAME "i2c-pseudo" -/* Value for alloc_chrdev_region() count arg. Should always be 1. */ -#define I2CP_CDEV_COUNT 1 - -#define I2CP_ADAP_START_CMD "ADAPTER_START" -#define I2CP_ADAP_SHUTDOWN_CMD "ADAPTER_SHUTDOWN" -#define I2CP_GET_NUMBER_CMD "GET_ADAPTER_NUM" -#define I2CP_NUMBER_REPLY_CMD "I2C_ADAPTER_NUM" -#define I2CP_GET_PSEUDO_ID_CMD "GET_PSEUDO_ID" -#define I2CP_PSEUDO_ID_REPLY_CMD "I2C_PSEUDO_ID" -#define I2CP_SET_NAME_SUFFIX_CMD "SET_ADAPTER_NAME_SUFFIX" -#define I2CP_SET_TIMEOUT_CMD "SET_ADAPTER_TIMEOUT_MS" -#define I2CP_BEGIN_MXFER_REQ_CMD "I2C_BEGIN_XFER" -#define I2CP_COMMIT_MXFER_REQ_CMD "I2C_COMMIT_XFER" -#define I2CP_MXFER_REQ_CMD "I2C_XFER_REQ" -#define I2CP_MXFER_REPLY_CMD "I2C_XFER_REPLY" - -/* Maximum size of a controller command. */ -#define I2CP_CTRLR_CMD_LIMIT 255 -/* Maximum number of controller read responses to allow enqueued at once. */ -#define I2CP_CTRLR_RSP_QUEUE_LIMIT 256 -/* The maximum size of a single controller read response. */ -#define I2CP_MAX_MSG_BUF_SIZE 16384 -/* Maximum size of a controller read or write. */ -#define I2CP_RW_SIZE_LIMIT 1048576 - -/* - * Marks the end of a controller command or read response. - * - * Fundamentally, controller commands and read responses could use different end - * marker characters, but for validity they should be the same. - * - * This must be a variable, not a macro, because it is passed to copy_to_user() - * by address. Taking the address of a character literal causes a compiler - * error. Making these C strings instead of characters would allow for that - * (with other implications), but then copy_to_user() itself refuses to compile, - * because of an assertion that the copy size (1) must match the size of the - * string literal (2 with its trailing null). - */ -static const char i2cp_ctrlr_end_char = '\n'; -/* Separator between I2C message header fields in the controller bytestream. */ -static const char i2cp_ctrlr_header_sep_char = ' '; -/* Separator between I2C message data bytes in the controller bytestream. */ -static const char i2cp_ctrlr_data_sep_char = ':'; - -/* - * This used instead of strcmp(in_str, other_str) because in_str may have null - * characters within its in_size boundaries, which could cause an unintended - * match. - */ -#define STRING_NEQ(in_str, in_size, other_str) \ - (in_size != strlen(other_str) || memcmp(other_str, in_str, in_size)) - -#define STR_HELPER(num) #num -#define STR(num) STR_HELPER(num) - -#define CONST_STRLEN(str) (sizeof(str) - 1) - -/* - * The number of pseudo I2C adapters permitted. This default value can be - * overridden at module load time. Must be in the range - * [I2CP_ADAPTERS_MIN, I2CP_ADAPTERS_MAX]. - * - * As currently used, this MUST NOT be changed during or after module - * initialization. If the ability to change this at runtime is desired, an - * audit of the uses of this variable will be necessary. - */ -static unsigned int i2cp_limit = I2CP_DEFAULT_LIMIT; -module_param(i2cp_limit, uint, 0444); - -/* - * The default I2C pseudo adapter timeout, in milliseconds. - * 0 means use Linux I2C adapter default. - * Can be changed per adapter by the controller. - */ -static unsigned int i2cp_default_timeout_ms = I2CP_DEFAULT_TIMEOUT_MS; -module_param(i2cp_default_timeout_ms, uint, 0444); - -struct i2cp_controller; - -/* This tracks all I2C pseudo adapters. */ -struct i2cp_counters { - /* This must be held while accessing any fields. */ - struct mutex lock; - unsigned int count; - /* - * This is used to make a strong attempt at avoiding ID reuse, - * especially during the lifetime of a userspace i2c-dev client. This - * can wrap by design, and thus makes no perfect guarantees. - */ - /* Same type as struct i2cp_controller.id field. */ - unsigned int next_ctrlr_id; - struct i2cp_controller **all_controllers; -}; - -static struct class *i2cp_class; -static dev_t i2cp_dev_num; - -struct i2cp_device { - struct i2cp_counters counters; - struct cdev cdev; - struct device device; -}; - -static struct i2cp_device *i2cp_device; - -/* - * An instance of this struct in i2cp_cmds[] array defines a command that a - * controller process may write to the I2C pseudo character device, hereafter a - * "write command." - * - * A write command consists of one or more header fields, followed optionally by - * data. Each header field is fully buffered before being sent to - * header_receiver(). Data is not fully buffered, it is chunked in fixed - * increments set by the return value of the final header_receiver() call. - * - * Every write command begins with its name. The name is used both to map the - * command to an instance of this struct, and as the first header field. - * - * A header field ends at either i2cp_ctrlr_end_char or - * i2cp_ctrlr_header_sep_char, neither of which is ever included in header field - * values passed to a callback. - * - * A command always ends at i2cp_ctrlr_end_char. Anything written after that by - * the controller is treated as a new command. - * - * After i2cp_ctrlr_header_sep_char the return value of header_receiver() from - * the previous header field is used to determine whether subsequent input is - * another header field, or data. - * - * Once header_receiver() has indicated that data is expected, all input until - * i2cp_ctrlr_end_char will be handled as data, and header_receiver() will not - * be called again for the command. - * - * For a given I2C pseudo controller instance there will never be more than one - * write command in flight at once, and there will never be more than one of - * these callbacks executing at once. These callbacks need not do any - * cross-thread synchronization among themselves. - * - * Note: Data may contain i2cp_ctrlr_header_sep_char. - * - * Note: There are no restrictions on the use of the null char ('\0') in either - * header fields or data. (If either i2cp_ctrlr_header_sep_char or - * i2cp_ctrlr_end_char is null then the respective restrictions around those - * characters apply as usual, of course.) Write command implementations need - * not use or expect null, but they must at least handle it gracefully and fail - * without bad side effects, same as with any unexpected input. - */ -struct i2cp_cmd { - /* - * Set these to the command name. - * - * The command name must not contain i2cp_ctrlr_header_sep_char or - * i2cp_ctrlr_end_char. The behavior otherwise is undefined; such a - * command would be uncallable, and could become either a build-time or - * runtime error. - * - * The command name must be unique in the i2cp_cmds[] array. The - * behavior with duplicate command names is undefined, subject to - * change, and subject to become either a build-time or runtime error. - */ - char *cmd_string; /* Must be non-NULL. */ - size_t cmd_size; /* Must be non-zero. */ - - /* - * This is called once for each I2C pseudo controller to initialize - * *data, prior to that pointer being passed to any other callbacks. - * - * This will only be called before the I2C adapter device is added. - * - * *data will be set to NULL before this is called. - * - * This callback may be NULL, in which case *data will remain NULL upon - * initialization. - * - * This should return -errno upon failure, 0 upon success. All - * non-negative return values are currently treated as success but - * positive values are reserved for potential future use. - * - * Initialization failure will cause the whole I2C pseudo controller to - * fail to initialize or function, thus *data will not be passed to any - * other callbacks. - */ - int (*data_creator)(void **data); - /* - * This is called once when shutdown of an I2C pseudo controller is - * imminent, and no further I2C replies can be processed. - * - * This callback may be NULL. - */ - void (*data_shutdown)(void *data); - /* - * This is called once upon termination of each I2C pseudo controller to - * free any resources held by @data. - * - * This will never be called while the I2C adapter device is active. - * Normally that means this is called after the I2C adapter device has - * been deleted, but it is also possible for this to be called during - * I2C pseudo controller initialization if a subsequent initialization - * step failed, as part of failure handling cleanup. - * - * This will only be called after a successful return value from - * data_creator(). - * - * This will be passed the same *data pointer that data_creator() placed - * in its **data output arg. - * - * The *data pointer will not be used again by the write command system - * after the start of this function call. - * - * This callback may be NULL. - */ - void (*data_destroyer)(void *data); - /* - * This is called to process write command header fields, including the - * command name itself as the first header field in every command. - * - * This is called once for each header field, in order, including the - * initial command name. - * - * @data is the value of *data from data_creator(). (Thus NULL if - * data_creator field is NULL.) - * - * @in and @in_size are the header value. It will never contain - * i2cp_ctrlr_header_sep_char or i2cp_ctrlr_end_char. - * - * in[in_size] is guaranteed to be null. There may be null characters - * inside the buffer boundary indicated by @in_size as well though! - * - * @non_blocking indicates whether O_NONBLOCK is set on the controller - * file descriptor. This is not expected to be relevant to most write - * command callback implementations, however it should be respected if - * relevant. In other words, if this is true do not block indefinitely, - * instead return EAGAIN or EWOULDBLOCK. If this is false never return - * EAGAIN or EWOULDBLOCK. - * - * Return -errno to indicate a failure. After a failure the next and - * final callback invocation for the command will be cmd_completer(). - * - * Return 0 to indicate success _and_ that another header field is - * expected next. The next header field will be fully buffered before - * being sent to this callback, just as the current one was. - * - * Return a positive value to indicate success _and_ that data is - * expected next. The exact positive value sets the chunk size used to - * buffer the data and pass it to data_receiver. All invocations of - * data_receiver are guaranteed to receive data in a _multiple_ of the - * chunk size, except the final invocation, because - * i2cp_ctrlr_end_char could be received on a non-chunk-size boundary. - * The return value should be less than I2CP_CTRLR_CMD_LIMIT, as that - * minus one is the maximum that will ever be buffered at once, and thus - * the maximum that will ever be sent to a single invocation of - * data_receiver. - * - * If the command is expected to end after a header field without any - * data, it is encouraged to return 1 here and have data_receiver - * indicate a failure if it is called. That avoids having the - * unexpected input buffered unnecessarily. - * - * This callback MUST NOT be NULL. - */ - int (*header_receiver)(void *data, char *in, size_t in_size, - bool non_blocking); - /* - * This is called to process write command data, when requested by the - * header_receiver() return value. - * - * This may be invoked multiple times for each data field, with the data - * broken up into sequential non-overlapping chunks. - * - * @in and @in_size are data. The data will never contain - * i2cp_ctrlr_end_char. - * - * in[in_size] is guaranteed to be null. There may be null characters - * inside the buffer boundary indicated by @in_size as well though! - * - * @in_size is guaranteed to be a multiple of the chunk size as - * specified by the last return value from header_receiver(), unless - * either the chunk size is >= I2CP_CTRLR_CMD_LIMIT, or - * i2cp_ctrlr_end_char was reached on a non-chunk-sized boundary. - * - * @in_size is guaranteed to be greater than zero, and less than - * I2CP_CTRLR_CMD_LIMIT. - * - * @non_blocking indicates whether O_NONBLOCK is set on the controller - * file descriptor. This is not expected to be relevant to most write - * command callback implementations, however it should be respected if - * relevant. In other words, if this is true do not block indefinitely, - * instead return EAGAIN or EWOULDBLOCK. If this is false never return - * EAGAIN or EWOULDBLOCK. - * - * This should return -errno upon failure, 0 upon success. All - * non-negative return values are currently treated as success but - * positive values are reserved for potential future use. After a - * failure the next and final callback invocation for the command will - * be cmd_completer(). - * - * If header_receiver() never returns a positive number, this callback - * should be NULL. Otherwise, this callback MUST NOT be NULL. - */ - int (*data_receiver)(void *data, char *in, size_t in_size, - bool non_blocking); - /* - * This is called to complete processing of a command, after it has been - * received in its entirety. - * - * If @receive_status is positive, it is an error code from the invoking - * routines themselves, e.g. if the controller process wrote a header - * field >= I2CP_CTRLR_CMD_LIMIT. - * - * If @receive_status is zero, it means all invocations of - * header_receiver and data_receiver returned successful values and the - * entire write command was received successfully. - * - * If @receive_status is negative, it is the value returned by the last - * header_receiver or data_receiver invocation. - * - * @non_blocking indicates whether O_NONBLOCK is set on the controller - * file descriptor. This is not expected to be relevant to most write - * command callback implementations, however it should be respected if - * relevant. In other words, if this is true do not block indefinitely, - * instead return EAGAIN or EWOULDBLOCK. If this is false never return - * EAGAIN or EWOULDBLOCK. - * - * This is called exactly once for each write command. This is true - * regardless of the value of @non_blocking and regardless of the return - * value of this function, so it is imperative that this function - * perform any necessary cleanup tasks related to @data, even if - * non_blocking=true and blocking is required! - * - * Thus, even with non_blocking=true, it would only ever make sense to - * return -EAGAIN from this function if the struct i2cp_cmd - * implementation is able to perform the would-be blocked cmd_completer - * operation later, e.g. upon invocation of a callback for the next - * write command, or by way of a background thread. - * - * This should return -errno upon failure, 0 upon success. All - * non-negative return values are currently treated as success but - * positive values are reserved for potential future use. - * - * An error should be returned only to indicate a new error that - * happened during the execution of this callback. Any error from - * @receive_status should *not* be copied to the return value of this - * callback. - * - * This callback may be NULL. - */ - int (*cmd_completer)(void *data, struct i2cp_controller *pdata, - int receive_status, bool non_blocking); -}; - -/* - * These are indexes of i2cp_cmds[]. Every element in that array should have a - * corresponding value in this enum, and the enum value should be used in the - * i2cp_cmds[] initializer. - * - * Command names are matched in this order, so sort by expected frequency. - */ -enum { - I2CP_CMD_MXFER_REPLY_IDX = 0, - I2CP_CMD_ADAP_START_IDX, - I2CP_CMD_ADAP_SHUTDOWN_IDX, - I2CP_CMD_GET_NUMBER_IDX, - I2CP_CMD_GET_PSEUDO_ID_IDX, - I2CP_CMD_SET_NAME_SUFFIX_IDX, - I2CP_CMD_SET_TIMEOUT_IDX, - /* Keep this at the end! This must equal ARRAY_SIZE(i2cp_cmds). */ - I2CP_NUM_WRITE_CMDS, -}; - -/* - * All values must be >= 0. This should not contain any error values. - * - * The state for a new controller must have a zero value, so that - * zero-initialized memory results in the correct default value. - */ -enum i2cp_ctrlr_state { - /* - * i2c_add_adapter() has not been called yet, or has only returned - * failure. - */ - I2CP_CTRLR_STATE_NEW = 0, - /* - * i2c_add_adapter() has return success, and the controller has not - * requested shutdown yet. - */ - I2CP_CTRLR_STATE_RUNNING, - /* - * i2c_add_adapter() has returned success, and the controller has - * requested shutdown. - * - * Note that it is perfectly acceptable for a pseudo controller fd to be - * closed and released without shutdown having been requested - * beforehand. Thus, this state is purely optional in the lifetime of a - * controller. - */ - I2CP_CTRLR_STATE_SHUTDN_REQ, -}; - -/* - * Avoid allocating this struct on the stack, it contains a large buffer as a - * direct member. - * - * To avoid deadlocks, never attempt to hold more than one of the locks in this - * structure at once, with the following exceptions: - * - It is permissible to acquire read_rsp_queue_lock while holding cmd_lock. - * - It is permissible to acquire read_rsp_queue_lock while holding rsp_lock. - */ -struct i2cp_controller { - unsigned int index; - /* - * Never modify the ID after initialization. - * - * This should be an unsigned integer type large enough to hold - * I2CP_ADAPTERS_MAX. - */ - unsigned int id; - /* - * Only i2cp_cdev_open() and i2cp_cdev_release() may access this field. - * Other functions called by them, or called by the I2C subsystem, may - * of course take a reference to this same struct i2c_adapter. However - * no other functions besides the aforementioned two may access the - * i2c_adapter field of struct i2cp_controller. - */ - struct i2c_adapter i2c_adapter; - - struct mutex startstop_lock; - enum i2cp_ctrlr_state startstop_state; - - wait_queue_head_t poll_wait_queue; - - /* This must be held while read or writing cmd_* fields. */ - struct mutex cmd_lock; - /* - * This becomes the @receive_status arg to struct i2cp_cmd.cmd_completer - * callback. - * - * A negative value is an error number from - * struct i2cp_cmd.header_receiver or struct i2cp_cmd.data_receiver. - * - * A zero value means no error has occurred so far in processing the - * current write reply command. - * - * A positive value is an error number from a non-command-specific part - * of write command processing, e.g. from the - * struct file_operations.write callback itself, or function further up - * its call stack that is not specific to any particular write command. - */ - int cmd_receive_status; - /* - * Index of i2cp_cmds[] and .cmd_data[] plus one, i.e. value of 1 means - * 0 index. Value of 0 (zero) means the controller is waiting for a new - * command. - */ - int cmd_idx_plus_one; - int cmd_data_increment; - size_t cmd_size; - /* Add one for trailing null character. */ - char cmd_buf[I2CP_CTRLR_CMD_LIMIT + 1]; - void *cmd_data[I2CP_NUM_WRITE_CMDS]; - - struct completion read_rsp_queued; - /* This must be held while read or writing read_rsp_queue_* fields. */ - struct mutex read_rsp_queue_lock; - /* - * This is a FIFO queue of struct i2cp_rsp.queue . - * - * This MUST be strictly used as FIFO. Only consume or pop the first - * item. Only append to the end. Users of this queue assume this FIFO - * behavior is strictly followed, and their uses of read_rsp_queue_lock - * would not be safe otherwise. - */ - struct list_head read_rsp_queue_head; - unsigned int read_rsp_queue_length; - - /* This must be held while reading or writing rsp_* fields. */ - struct mutex rsp_lock; - bool rsp_invalidated; - /* - * Holds formatted string from most recently popped item of - * read_rsp_queue_head if it was not wholly consumed by the last - * controller read. - */ - char *rsp_buf_start; - char *rsp_buf_pos; - ssize_t rsp_buf_remaining; -}; - -struct i2cp_cmd_mxfer_reply { - /* - * This lock MUST be held while reading or modifying any part of this - * struct i2cp_cmd_mxfer_reply, unless you can guarantee that nothing - * else can access this struct concurrently, such as during - * initialization. - * - * The struct i2cp_cmd_mxfer_reply_data.reply_queue_lock of the - * struct i2cp_cmd_mxfer_reply_data.reply_queue_head list which contains - * this struct i2cp_cmd_mxfer_reply.reply_queue_item MUST be held when - * attempting to acquire this lock. - * - * It is NOT required to keep - * struct i2cp_cmd_mxfer_reply_data.reply_queue_lock held after - * acquisition of this lock (unless also manipulating - * struct i2cp_cmd_mxfer_reply_data.reply_queue_* of course). - */ - struct mutex lock; - - /* - * Never modify the ID after initialization. - * - * This should be an unsigned integer type large enough to hold - * I2CP_CTRLR_RSP_QUEUE_LIMIT. If changing this type, audit for printf - * format strings that need updating! - */ - unsigned int id; - /* Number of I2C messages successfully processed, or negative error. */ - int ret; - /* Same type as struct i2c_algorithm.master_xfer @num arg. */ - int num_msgs; - /* Same type as struct i2c_algorithm.master_xfer @msgs arg. */ - struct i2c_msg *msgs; - /* Same length (not size) as *msgs array. */ - bool *completed; - /* Number of completed[] array entries with true value. */ - int num_completed_true; - - /* - * This is for use in struct i2cp_cmd_mxfer_reply_data.reply_queue_head - * FIFO queue. - * - * Any time this is deleted from its containing - * struct i2cp_cmd_mxfer_reply_data.reply_queue_head list, either - * list_del_init() MUST be used (not list_del()), OR this whole - * struct i2cp_cmd_mxfer_reply MUST be freed. - * - * That way, if this struct is not immediately freed, the code which - * eventually frees it can test whether it still needs to be deleted - * from struct i2cp_cmd_mxfer_reply_data.reply_queue_head by using - * list_empty() on reply_queue_item. (Calling list_del() on an - * already-deleted list item is unsafe.) - */ - struct list_head reply_queue_item; - struct completion data_filled; -}; - -/* - * The state for receiving the first field must have a zero value, so that - * zero-initialized memory results in the correct default value. - */ -enum i2cp_cmd_mxfer_reply_state { - I2CP_CMD_MXFER_REPLY_STATE_CMD_NEXT = 0, - I2CP_CMD_MXFER_REPLY_STATE_ID_NEXT, - I2CP_CMD_MXFER_REPLY_STATE_INDEX_NEXT, - I2CP_CMD_MXFER_REPLY_STATE_ADDR_NEXT, - I2CP_CMD_MXFER_REPLY_STATE_FLAGS_NEXT, - I2CP_CMD_MXFER_REPLY_STATE_ERRNO_NEXT, - I2CP_CMD_MXFER_REPLY_STATE_DATA_NEXT, - /* - * This is used to tell subsequent callback invocations that the write - * command currently being received is invalid, when the receiver wants - * to quietly discard the write command instead of loudly returning an - * error. - */ - I2CP_CMD_MXFER_REPLY_STATE_INVALID, -}; - -struct i2cp_cmd_mxfer_reply_data { - /* This must be held while read or writing reply_queue_* fields. */ - struct mutex reply_queue_lock; - /* - * This is used to make a strong attempt at avoiding ID reuse, - * especially for overlapping master_xfer() calls. - * - * This can wrap by design, and thus makes no perfect guarantees over - * the lifetime of an I2C pseudo adapter. - * - * No code should assume uniqueness, not even for master_xfer() calls of - * overlapping lifetimes. When the controller writes a master_xfer() - * reply command, assume that it is for the oldest outstanding instance - * of the ID number specified. - */ - /* Same type as struct i2cp_cmd_mxfer_reply.id field. */ - unsigned int next_mxfer_id; - /* - * This is a FIFO queue of struct i2cp_cmd_mxfer_reply.reply_queue_item. - * - * This MUST be strictly used as FIFO. Only consume or pop the first - * item. Only append to the end. Users of this queue assume this FIFO - * behavior is strictly followed, and their uses of reply_queue_lock may - * not be safe otherwise. - */ - struct list_head reply_queue_head; - unsigned int reply_queue_length; - struct i2cp_cmd_mxfer_reply *reply_queue_current_item; - - enum i2cp_cmd_mxfer_reply_state state; - - /* Same type as struct i2cp_cmd_mxfer_reply.id field. */ - unsigned int current_id; - /* Same type as struct i2c_msg.addr field. */ - u16 current_addr; - /* Same type as struct i2c_msg.flags field. */ - u16 current_flags; - /* Same type as struct i2c_algorithm.master_xfer @num arg. */ - int current_msg_idx; - /* Same type as struct i2c_msg.len field. */ - u16 current_buf_idx; -}; - -struct i2cp_cmd_set_name_suffix_data { - char name_suffix[sizeof_field(struct i2c_adapter, name)]; - size_t name_suffix_len; -}; - -struct i2cp_cmd_set_timeout_data { - int field_pos; - unsigned int timeout_ms; -}; - -struct i2cp_rsp { - /* - * This callback is invoked to format its associated data for passing to - * the userspace controller process when it read()s the I2C pseudo - * controller character device. - * - * @data will be the data pointer from this struct instance. - * - * @out is an output argument. Upon positive return value, *out must be - * set to a buffer which the caller will take ownership of, and which - * can be freed with kfree(). - * - * Upon positive return value, @data must NOT be freed. - * - * The formatter will be called repeatedly for the same data until it - * returns non-positive. - * - * Upon non-positive return value, *out should not be modified. - * - * Upon non-positive return value, the formatter should have freed data - * with kfree(). Implicitly this means any allocations owned by *data - * should have been freed by the formatter as well. - * - * A negative return value indicates an error occurred and the data - * cannot be formatted successfully. The error code may or may not - * eventually be propagated back to the I2C pseudo adapter controller. - * - * A positive return value is the number of characters/bytes to use from - * the *out buffer, always starting from index 0. It should NOT include - * a trailing NULL character unless that character should be propagated - * to the I2C pseudo adapter controller! It therefore does NOT need to - * be the full size of the allocated *out buffer, instead it can be - * less. (The size is not needed by kfree().) - * - * The formatter owns the memory pointed to by data. The invoking code - * will never mutate or free data. Thus, upon non-positive return value - * from the formatter, the formatter must have already performed any - * reference counting decrement or memory freeing necessary to ensure - * data does not live beyond its final use. - * - * There will never be more than one formatter callback in flight at - * once for a given I2C pseudo controller. This is true even in the - * face of concurrent reads by the controller. - * - * The formatter must NOT use i2cp_ctrlr_end_char in anywhere in *out - * (within the size range indicated by the return value; past that does - * not matter). The i2cp_ctrlr_end_char will be added automatically by - * the caller after a zero return value (successful completion) from the - * formatter. - * - * The formatter must never create or return a buffer larger than - * I2CP_MAX_MSG_BUF_SIZE. The formatter is encouraged to avoid that by - * generating and returning the output in chunks, taking advantage of - * the guarantee that it will be called repeatedly until exhaustion - * (zero return value) or failure (negative return value). If the - * formatter expects its formatted output or natural subsets of it to - * always fit within I2CP_MAX_MSG_BUF_SIZE, and it is called with input - * data not meeting that expectation, the formatter should return - * -ERANGE to indicate this condition. - */ - ssize_t (*formatter)(void *data, char **out); - void *data; - - struct list_head queue; -}; - -struct i2cp_rsp_buffer { - char *buf; - ssize_t size; -}; - -struct i2cp_rsp_master_xfer { - /* Never modify the ID after initialization. */ - /* Same type as struct i2cp_cmd_mxfer_reply.id field. */ - unsigned int id; - - /* These types match those of struct i2c_algorithm.master_xfer args. */ - struct i2c_msg *msgs; - int num; - - /* - * Always initialize fields below here to zero. They are for internal - * use by i2cp_rsp_master_xfer_formatter(). - */ - int num_msgs_done; /* type of @num field */ - size_t buf_start_plus_one; -}; - -/* vanprintf - See anprintf() documentation. */ -static ssize_t vanprintf(char **out, ssize_t max_size, gfp_t gfp, - const char *fmt, va_list ap) -{ - int ret; - ssize_t buf_size; - char *buf = NULL; - va_list args1; - - va_copy(args1, ap); - ret = vsnprintf(NULL, 0, fmt, ap); - if (ret < 0) - goto fail_before_args1; - if (max_size >= 0 && ret > max_size) { - ret = -ERANGE; - goto fail_before_args1; - } - - buf_size = ret + 1; - buf = kmalloc(buf_size, gfp); - if (buf == NULL) { - ret = -ENOMEM; - goto fail_before_args1; - } - - ret = vsnprintf(buf, buf_size, fmt, args1); - va_end(args1); - if (ret < 0) - goto fail_after_args1; - if (ret + 1 != buf_size) { - ret = -ENOTRECOVERABLE; - goto fail_after_args1; - } - - *out = buf; - return ret; - - fail_before_args1: - va_end(args1); - fail_after_args1: - kfree(buf); - if (ret >= 0) - ret = -ENOTRECOVERABLE; - return ret; -} - -/* - * anprintf - Format a string and place it into a newly allocated buffer. - * @out: Address of the pointer to place the buffer address into. Will only be - * written to with a successful positive return value. - * @max_size: If non-negative, the maximum buffer size that this function will - * attempt to allocate. If the formatted string including trailing null - * character would not fit, no buffer will be allocated, and an error will - * be returned. (Thus max_size of 0 will always result in an error.) - * @gfp: GFP flags for kmalloc(). - * @fmt: The format string to use. - * @...: Arguments for the format string. - * - * Return value meanings: - * - * >=0: A buffer of this size was allocated and its address written to *out. - * The caller now owns the buffer and is responsible for freeing it with - * kfree(). The final character in the buffer, not counted in this - * return value, is the trailing null. This is the same return value - * meaning as snprintf(3). - * - * <0: An error occurred. Negate the return value for the error number. - * @out will not have been written to. Errors that might come from - * snprintf(3) may come from this function as well. Additionally, the - * following errors may occur from this function: - * - * ERANGE: A buffer larger than @max_size would be needed to fit the - * formatted string including its trailing null character. - * - * ENOMEM: Allocation of the output buffer failed. - * - * ENOTRECOVERABLE: An unexpected condition occurred. This may indicate - * a bug. - */ -static ssize_t anprintf(char **out, ssize_t max_size, gfp_t gfp, - const char *fmt, ...) -{ - ssize_t ret; - va_list args; - - va_start(args, fmt); - ret = vanprintf(out, max_size, gfp, fmt, args); - va_end(args); - return ret; -} - -static ssize_t i2cp_rsp_buffer_formatter(void *data, char **out) -{ - struct i2cp_rsp_buffer *rsp_buf; - - rsp_buf = data; - if (rsp_buf->buf) { - if (rsp_buf->size > 0) { - *out = rsp_buf->buf; - rsp_buf->buf = NULL; - return rsp_buf->size; - } - kfree(rsp_buf->buf); - } - kfree(rsp_buf); - return 0; -} - -static ssize_t i2cp_rsp_master_xfer_formatter(void *data, char **out) -{ - ssize_t ret; - size_t i, buf_size, byte_start, byte_limit; - char *buf_start, *buf_pos; - struct i2cp_rsp_master_xfer *mxfer_rsp; - struct i2c_msg *i2c_msg; - - mxfer_rsp = data; - - /* - * This condition is set by a previous call to this function with the - * same data, when it returned an error but was not consuming the final - * i2c_msg. - */ - if (!mxfer_rsp->msgs) { - ++mxfer_rsp->num_msgs_done; - ret = 0; - goto maybe_free; - } - - i2c_msg = &mxfer_rsp->msgs[mxfer_rsp->num_msgs_done]; - - /* - * If this is a read, or if this is a write and we've finished writing - * the data buffer, we are done with this i2c_msg. - */ - if (mxfer_rsp->buf_start_plus_one >= 1 && - (i2c_msg->flags & I2C_M_RD || - mxfer_rsp->buf_start_plus_one >= (size_t)i2c_msg->len + 1)) { - ++mxfer_rsp->num_msgs_done; - mxfer_rsp->buf_start_plus_one = 0; - ret = 0; - goto maybe_free; - } - - if (mxfer_rsp->buf_start_plus_one <= 0) { - /* - * The length is not strictly necessary with the explicit - * end-of-message marker (i2cp_ctrlr_end_char), however it - * serves as a useful validity check for controllers to verify - * that no bytes were lost in kernel->userspace transmission. - */ - ret = anprintf(&buf_start, I2CP_MAX_MSG_BUF_SIZE, GFP_KERNEL, - "%*s%c%u%c%d%c0x%04X%c0x%04X%c%u", - (int)strlen(I2CP_MXFER_REQ_CMD), I2CP_MXFER_REQ_CMD, - i2cp_ctrlr_header_sep_char, mxfer_rsp->id, - i2cp_ctrlr_header_sep_char, mxfer_rsp->num_msgs_done, - i2cp_ctrlr_header_sep_char, i2c_msg->addr, - i2cp_ctrlr_header_sep_char, i2c_msg->flags, - i2cp_ctrlr_header_sep_char, i2c_msg->len); - if (ret > 0) { - *out = buf_start; - mxfer_rsp->buf_start_plus_one = 1; - /* - * If we have a zero return value, it means the output buffer - * was allocated as size one, containing only a terminating null - * character. This would be a bug given the requested format - * string above. Also, formatter functions must not mutate *out - * when returning zero. So if this matches, free the useless - * buffer and return an error. - */ - } else if (ret == 0) { - ret = -EINVAL; - kfree(buf_start); - } - goto maybe_free; - } - - byte_start = mxfer_rsp->buf_start_plus_one - 1; - byte_limit = min_t(size_t, i2c_msg->len - byte_start, - I2CP_MAX_MSG_BUF_SIZE / 3); - /* 3 chars per byte == 2 chars for hex + 1 char for separator */ - buf_size = byte_limit * 3; - - buf_start = kzalloc(buf_size, GFP_KERNEL); - if (!buf_start) { - ret = -ENOMEM; - goto maybe_free; - } - - for (buf_pos = buf_start, i = 0; i < byte_limit; ++i) { - *buf_pos++ = (i || byte_start) ? - i2cp_ctrlr_data_sep_char : i2cp_ctrlr_header_sep_char; - buf_pos = hex_byte_pack_upper( - buf_pos, i2c_msg->buf[byte_start + i]); - } - *out = buf_start; - ret = buf_size; - mxfer_rsp->buf_start_plus_one += i; - - maybe_free: - if (ret <= 0) { - if (mxfer_rsp->num_msgs_done >= mxfer_rsp->num) { - kfree(mxfer_rsp->msgs); - kfree(mxfer_rsp); - /* - * If we are returning an error but have not consumed all of - * mxfer_rsp yet, we must not attempt to output any more I2C - * messages from the same mxfer_rsp. Setting mxfer_rsp->msgs to - * NULL tells the remaining invocations with this mxfer_rsp to - * output nothing. - * - * There can be more invocations with the same mxfer_rsp even - * after returning an error here because - * i2cp_adapter_master_xfer() reuses a single - * struct i2cp_rsp_master_xfer (mxfer_rsp) across multiple - * struct i2cp_rsp (rsp_wrappers), one for each struct i2c_msg - * within the mxfer_rsp. - */ - } else if (ret < 0) { - kfree(mxfer_rsp->msgs); - mxfer_rsp->msgs = NULL; - } - } - return ret; -} - -static ssize_t i2cp_id_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - int ret; - struct i2c_adapter *adap; - struct i2cp_controller *pdata; - - adap = container_of(dev, struct i2c_adapter, dev); - pdata = container_of(adap, struct i2cp_controller, i2c_adapter); - ret = snprintf(buf, PAGE_SIZE, "%u\n", pdata->id); - if (ret >= PAGE_SIZE) - return -ERANGE; - return ret; -} - -static const struct device_attribute i2cp_id_dev_attr = { - .attr = { - .name = "i2c-pseudo-id", - .mode = 0444, - }, - .show = i2cp_id_show, -}; - -static enum i2cp_ctrlr_state i2cp_adap_get_state(struct i2cp_controller *pdata) -{ - enum i2cp_ctrlr_state ret; - - mutex_lock(&pdata->startstop_lock); - ret = pdata->startstop_state; - mutex_unlock(&pdata->startstop_lock); - return ret; -} - -static int i2cp_cmd_mxfer_reply_data_creator(void **data) -{ - struct i2cp_cmd_mxfer_reply_data *cmd_data; - - cmd_data = kzalloc(sizeof(*cmd_data), GFP_KERNEL); - if (!cmd_data) - return -ENOMEM; - mutex_init(&cmd_data->reply_queue_lock); - INIT_LIST_HEAD(&cmd_data->reply_queue_head); - *data = cmd_data; - return 0; -} - -/* - * Notify pending I2C requests of the shutdown. There is no possibility of - * further I2C replies at this point. This stops the I2C requests from waiting - * for the adapter timeout, which could have been set arbitrarily long by the - * userspace controller. - */ -static void i2cp_cmd_mxfer_reply_data_shutdown(void *data) -{ - struct list_head *list_ptr; - struct i2cp_cmd_mxfer_reply_data *cmd_data; - struct i2cp_cmd_mxfer_reply *mxfer_reply; - - cmd_data = data; - mutex_lock(&cmd_data->reply_queue_lock); - list_for_each(list_ptr, &cmd_data->reply_queue_head) { - mxfer_reply = list_entry(list_ptr, struct i2cp_cmd_mxfer_reply, - reply_queue_item); - mutex_lock(&mxfer_reply->lock); - complete_all(&mxfer_reply->data_filled); - mutex_unlock(&mxfer_reply->lock); - } - mutex_unlock(&cmd_data->reply_queue_lock); -} - -static void i2cp_cmd_mxfer_reply_data_destroyer(void *data) -{ - /* - * We do not have to worry about racing with in-flight I2C messages - * because data_destroyer callbacks are guaranteed to never be called - * while the I2C adapter device is active. - */ - kfree(data); -} - -static inline bool i2cp_mxfer_reply_is_current( - struct i2cp_cmd_mxfer_reply_data *cmd_data, - struct i2cp_cmd_mxfer_reply *mxfer_reply) -{ - int i; - - i = cmd_data->current_msg_idx; - return cmd_data->current_id == mxfer_reply->id && - i >= 0 && i < mxfer_reply->num_msgs && - cmd_data->current_addr == mxfer_reply->msgs[i].addr && - cmd_data->current_flags == mxfer_reply->msgs[i].flags; -} - -/* cmd_data->reply_queue_lock must be held. */ -static inline struct i2cp_cmd_mxfer_reply *i2cp_mxfer_reply_find_current( - struct i2cp_cmd_mxfer_reply_data *cmd_data) -{ - struct list_head *list_ptr; - struct i2cp_cmd_mxfer_reply *mxfer_reply; - - list_for_each(list_ptr, &cmd_data->reply_queue_head) { - mxfer_reply = list_entry(list_ptr, struct i2cp_cmd_mxfer_reply, - reply_queue_item); - if (i2cp_mxfer_reply_is_current(cmd_data, mxfer_reply)) - return mxfer_reply; - } - return NULL; -} - -/* cmd_data->reply_queue_lock must NOT already be held. */ -static inline void i2cp_mxfer_reply_update_current( - struct i2cp_cmd_mxfer_reply_data *cmd_data) -{ - mutex_lock(&cmd_data->reply_queue_lock); - cmd_data->reply_queue_current_item = i2cp_mxfer_reply_find_current( - cmd_data); - mutex_unlock(&cmd_data->reply_queue_lock); -} - -static int i2cp_cmd_mxfer_reply_header_receiver(void *data, char *in, - size_t in_size, bool non_blocking) -{ - int ret, reply_errno = 0; - struct i2cp_cmd_mxfer_reply_data *cmd_data; - - cmd_data = data; - - switch (cmd_data->state) { - case I2CP_CMD_MXFER_REPLY_STATE_CMD_NEXT: - /* Expect the msg/reply ID header field next. */ - cmd_data->state = I2CP_CMD_MXFER_REPLY_STATE_ID_NEXT; - return 0; - case I2CP_CMD_MXFER_REPLY_STATE_ID_NEXT: - case I2CP_CMD_MXFER_REPLY_STATE_INDEX_NEXT: - case I2CP_CMD_MXFER_REPLY_STATE_ADDR_NEXT: - case I2CP_CMD_MXFER_REPLY_STATE_FLAGS_NEXT: - case I2CP_CMD_MXFER_REPLY_STATE_ERRNO_NEXT: - break; - default: - /* Reaching here is a bug. */ - /* - * Testing this before checking for null characters ensures the - * correct error is indicated. - */ - return -EINVAL; - } - - /* - * The command name is logically outside the control of this function, - * and may contain null characters, even if that would be nonsensical. - * Thus it is handled above, followed by this check, and below here - * the rest of the header fields are handled. Some of them use - * functions that could mishandle input which contains nulls. An actual - * error would be okay, however if the input were consumed incorrectly - * without an error, that could lead to subtle bugs. - */ - if (memchr(in, '\0', in_size)) - return -EPROTO; - - switch (cmd_data->state) { - case I2CP_CMD_MXFER_REPLY_STATE_ID_NEXT: - ret = kstrtouint(in, 0, &cmd_data->current_id); - if (ret < 0) - return ret; - cmd_data->state = I2CP_CMD_MXFER_REPLY_STATE_INDEX_NEXT; - return 0; - case I2CP_CMD_MXFER_REPLY_STATE_INDEX_NEXT: - ret = kstrtoint(in, 0, &cmd_data->current_msg_idx); - if (ret < 0) - return ret; - cmd_data->state = I2CP_CMD_MXFER_REPLY_STATE_ADDR_NEXT; - return 0; - case I2CP_CMD_MXFER_REPLY_STATE_ADDR_NEXT: - ret = kstrtou16(in, 0, &cmd_data->current_addr); - if (ret < 0) - return ret; - cmd_data->state = I2CP_CMD_MXFER_REPLY_STATE_FLAGS_NEXT; - return 0; - case I2CP_CMD_MXFER_REPLY_STATE_FLAGS_NEXT: - ret = kstrtou16(in, 0, &cmd_data->current_flags); - if (ret < 0) - return ret; - cmd_data->state = I2CP_CMD_MXFER_REPLY_STATE_ERRNO_NEXT; - return 0; - case I2CP_CMD_MXFER_REPLY_STATE_ERRNO_NEXT: - ret = kstrtoint(in, 0, &reply_errno); - if (ret < 0) - return ret; - break; - default: - /* Reaching here is a bug. */ - return -EINVAL; - } - - /* - * Only I2CP_CMD_MXFER_REPLY_STATE_ERRNO_NEXT can reach this point. - * Now that we've received all of the headers, find the matching - * mxfer_reply. - */ - i2cp_mxfer_reply_update_current(cmd_data); - - if (reply_errno || !cmd_data->reply_queue_current_item) { - /* - * reply_errno: - * Drop the specific errno for now. The Linux I2C API - * does not provide a way to return an errno for a - * specific message within a master_xfer() call. The - * cmd_completer callback will indicate this - * controller-reported failure by not incrementing - * mxfer_reply->ret for this I2C msg reply. - * - * cmd_data->reply_queue_current_item == NULL: - * No matching mxfer_reply was found. Discard any - * further input in this command. The cmd_completer - * callback will indicate this failure to the - * controller. - */ - cmd_data->state = I2CP_CMD_MXFER_REPLY_STATE_INVALID; - /* - * Ask for data bytes in multiples of 1, i.e. no - * boundary requirements, because the we're just going - * to discard it. The next field could even be a header - * instead of data, but it doesn't matter, we're going - * to continue discarding the write input until the end - * of this write command. - */ - return 1; - } - - cmd_data->state = I2CP_CMD_MXFER_REPLY_STATE_DATA_NEXT; - /* - * Ask for data bytes in multiples of 3. Expected format is - * hexadecimal NN:NN:... e.g. "3C:05:F1:01" is a possible 4 byte - * data value. - */ - return 3; -} - -static int i2cp_cmd_mxfer_reply_data_receiver(void *data, char *in, - size_t in_size, bool non_blocking) -{ - int ret; - char u8_hex[3] = {0}; - struct i2cp_cmd_mxfer_reply_data *cmd_data; - struct i2cp_cmd_mxfer_reply *mxfer_reply; - struct i2c_msg *i2c_msg; - - cmd_data = data; - - if (cmd_data->state == I2CP_CMD_MXFER_REPLY_STATE_INVALID) - return 0; - if (cmd_data->state != I2CP_CMD_MXFER_REPLY_STATE_DATA_NEXT) - /* Reaching here is a bug. */ - return -EINVAL; - - mutex_lock(&cmd_data->reply_queue_lock); - mxfer_reply = cmd_data->reply_queue_current_item; - if (!mxfer_reply) { - /* Reaching here is a bug. */ - mutex_unlock(&cmd_data->reply_queue_lock); - return -EINVAL; - } - mutex_lock(&mxfer_reply->lock); - mutex_unlock(&cmd_data->reply_queue_lock); - - if (cmd_data->current_msg_idx < 0 || - cmd_data->current_msg_idx >= mxfer_reply->num_msgs) { - /* Reaching here is a bug. */ - ret = -EINVAL; - goto unlock; - } - - i2c_msg = &mxfer_reply->msgs[cmd_data->current_msg_idx]; - - if (!(i2c_msg->flags & I2C_M_RD)) { - /* The controller responded to a write with data. */ - ret = -EIO; - goto unlock; - } - - if (i2c_msg->flags & I2C_M_RECV_LEN) { - /* - * When I2C_M_RECV_LEN is set, struct i2c_algorithm.master_xfer - * is expected to increment struct i2c_msg.len by the actual - * amount of bytes read. - * - * Given the above, an initial struct i2c_msg.len value of 0 - * would be reasonable, since it will be incremented for each - * byte read. - * - * An initial value of 1 representing the expected size byte - * also makes sense, and appears to be common practice. - * - * We consider a larger initial value to indicate a bug in the - * I2C/SMBus client, because it's difficult to reconcile such a - * value with the documented requirement that struct i2c_msg.len - * be "incremented by the number of block data bytes received." - * Besides returning an error, our only options would be to - * ignore and blow away a value that was potentially meaningful - * to the client (e.g. if it indicates the maximum buffer size), - * assume the value is the buffer size or expected read size - * (which would conflict with the documentation), or just - * blindly increment it, leaving it at a value greater than the - * actual number of bytes we wrote to the buffer, and likely - * indicating a size larger than the actual buffer allocation. - */ - if (cmd_data->current_buf_idx == 0) { - if (i2c_msg->len > 1) { - ret = -EPROTO; - goto unlock; - } - /* - * Subtract the read size byte because the in_size - * increment in the loop below will re-add it. - */ - i2c_msg->len = 0; - } - } - - while (in_size > 0 && cmd_data->current_buf_idx < i2c_msg->len) { - if (in_size < 2 || - (in_size > 2 && in[2] != i2cp_ctrlr_data_sep_char) || - memchr(in, '\0', 2)) { - /* - * Reaching here is a bug in the userspace I2C pseudo - * adapter controller. (Or possibly a bug in this - * module itself, of course.) - */ - ret = -EIO; - goto unlock; - } - /* - * When using I2C_M_RECV_LEN, the buffer is required to be able - * to hold: - * - * I2C_SMBUS_BLOCK_MAX - * +1 byte for the read size (first byte) - * +1 byte for the optional PEC byte (last byte if present). - * - * If reading the next byte would exceed that, return EPROTO - * error per Documentation/i2c/fault-codes . - */ - if (i2c_msg->flags & I2C_M_RECV_LEN && - i2c_msg->len >= I2C_SMBUS_BLOCK_MAX + 2) { - ret = -EPROTO; - goto unlock; - } - /* Use u8_hex to get a terminating null byte for kstrtou8(). */ - memcpy(u8_hex, in, 2); - /* - * TODO: Do we need to do anything different based on the - * I2C_M_DMA_SAFE bit? Do we ever need to use copy_to_user()? - */ - ret = kstrtou8(u8_hex, 16, - &i2c_msg->buf[cmd_data->current_buf_idx]); - if (ret < 0) - goto unlock; - if (i2c_msg->flags & I2C_M_RECV_LEN) - ++i2c_msg->len; - ++cmd_data->current_buf_idx; - in += min_t(size_t, 3, in_size); - in_size -= min_t(size_t, 3, in_size); - } - - /* Quietly ignore any bytes beyond the buffer size. */ - ret = 0; - - unlock: - mutex_unlock(&mxfer_reply->lock); - return ret; -} - -static int i2cp_cmd_mxfer_reply_cmd_completer(void *data, - struct i2cp_controller *pdata, int receive_status, bool non_blocking) -{ - int ret; - struct i2cp_cmd_mxfer_reply_data *cmd_data; - struct i2cp_cmd_mxfer_reply *mxfer_reply; - struct i2c_msg *i2c_msg; - - cmd_data = data; - mutex_lock(&cmd_data->reply_queue_lock); - - mxfer_reply = cmd_data->reply_queue_current_item; - if (!mxfer_reply) { - mutex_unlock(&cmd_data->reply_queue_lock); - ret = -EIO; - goto reset_cmd_data; - } - - mutex_lock(&mxfer_reply->lock); - - if (mxfer_reply->completed[cmd_data->current_msg_idx]) { - /* We already received a reply for this msg. */ - mutex_unlock(&cmd_data->reply_queue_lock); - mutex_unlock(&mxfer_reply->lock); - ret = -EIO; - goto reset_cmd_data; - } - - mxfer_reply->completed[cmd_data->current_msg_idx] = true; - if (++mxfer_reply->num_completed_true >= mxfer_reply->num_msgs) { - list_del_init(&mxfer_reply->reply_queue_item); - --cmd_data->reply_queue_length; - cmd_data->reply_queue_current_item = NULL; - complete_all(&mxfer_reply->data_filled); - } - - mutex_unlock(&cmd_data->reply_queue_lock); - i2c_msg = &mxfer_reply->msgs[cmd_data->current_msg_idx]; - - if (!receive_status && - cmd_data->state == I2CP_CMD_MXFER_REPLY_STATE_DATA_NEXT && - (!(i2c_msg->flags & I2C_M_RD) || - cmd_data->current_buf_idx >= i2c_msg->len)) - ++mxfer_reply->ret; - - mutex_unlock(&mxfer_reply->lock); - ret = 0; - - reset_cmd_data: - cmd_data->state = I2CP_CMD_MXFER_REPLY_STATE_CMD_NEXT; - cmd_data->current_id = 0; - cmd_data->current_addr = 0; - cmd_data->current_flags = 0; - cmd_data->current_msg_idx = 0; - cmd_data->current_buf_idx = 0; - return ret; -} - -static int i2cp_cmd_adap_start_header_receiver(void *data, char *in, - size_t in_size, bool non_blocking) -{ - /* - * No more header fields or data are expected. This directs any further - * input in this command to the data_receiver, which for this write - * command will unconditionally indicate a controller error. - */ - return 1; -} - -static int i2cp_cmd_adap_start_data_receiver(void *data, char *in, - size_t in_size, bool non_blocking) -{ - /* - * Reaching here means the controller wrote extra data in the command - * line after the initial command name. That is unexpected and - * indicates a controller bug. - */ - return -EPROTO; -} - -static int i2cp_cmd_adap_start_cmd_completer(void *data, - struct i2cp_controller *pdata, int receive_status, bool non_blocking) -{ - int ret; - - /* Refuse to start if there were errors processing this command. */ - if (receive_status) - return 0; - - /* - * Acquire pdata->startstop_lock manually instead of using - * i2cp_adap_get_state() in order to keep the lock while calling - * i2c_add_adapter(). - */ - mutex_lock(&pdata->startstop_lock); - - if (pdata->startstop_state != I2CP_CTRLR_STATE_NEW) { - ret = -EISCONN; - goto unlock; - } - - /* Add the I2C adapter. */ - ret = i2c_add_adapter(&pdata->i2c_adapter); - if (ret < 0) - goto unlock; - - pdata->startstop_state = I2CP_CTRLR_STATE_RUNNING; - - /* Add the I2C pseudo controller ID sysfs file. */ - ret = device_create_file(&pdata->i2c_adapter.dev, &i2cp_id_dev_attr); - if (ret < 0) - goto unlock; - - ret = 0; - - unlock: - mutex_unlock(&pdata->startstop_lock); - return ret; -} - -static int i2cp_cmd_adap_shutdown_header_receiver(void *data, char *in, - size_t in_size, bool non_blocking) -{ - /* - * No more header fields or data are expected. This directs any further - * input in this command to the data_receiver, which for this write - * command will unconditionally indicate a controller error. - */ - return 1; -} - -static int i2cp_cmd_adap_shutdown_data_receiver(void *data, char *in, - size_t in_size, bool non_blocking) -{ - /* - * Reaching here means the controller wrote extra data in the command - * line after the initial command name. That is unexpected and - * indicates a controller bug. - */ - return -EPROTO; -} - -static int i2cp_cmd_adap_shutdown_cmd_completer(void *data, - struct i2cp_controller *pdata, int receive_status, bool non_blocking) -{ - /* Refuse to shutdown if there were errors processing this command. */ - if (receive_status) - return 0; - - mutex_lock(&pdata->startstop_lock); - pdata->startstop_state = I2CP_CTRLR_STATE_SHUTDN_REQ; - mutex_unlock(&pdata->startstop_lock); - - /* Wake up blocked controller readers. */ - complete_all(&pdata->read_rsp_queued); - /* Wake up blocked controller pollers. */ - wake_up_interruptible_all(&pdata->poll_wait_queue); - return 0; -} - -static int i2cp_cmd_get_number_header_receiver(void *data, char *in, - size_t in_size, bool non_blocking) -{ - /* - * No more header fields or data are expected. This directs any further - * input in this command to the data_receiver, which for this write - * command will unconditionally indicate a controller error. - */ - return 1; -} - -static int i2cp_cmd_get_number_data_receiver(void *data, char *in, - size_t in_size, bool non_blocking) -{ - /* - * Reaching here means the controller wrote extra data in the command - * line after the initial command name. That is unexpected and - * indicates a controller bug. - */ - return -EPROTO; -} - -static int i2cp_cmd_get_number_cmd_completer(void *data, - struct i2cp_controller *pdata, int receive_status, bool non_blocking) -{ - ssize_t ret; - int i2c_adap_nr; - struct i2cp_rsp_buffer *rsp_buf; - struct i2cp_rsp *rsp_wrapper; - - /* Abort if there were errors processing this command. */ - if (receive_status) - return 0; - - /* - * Check the pseudo controller startstop_state. If it's running, get - * the I2C adapter number. - * - * Acquire pdata->startstop_lock manually instead of using - * i2cp_adap_get_state() in order to keep the lock while retrieving the - * I2C adapter number. - */ - mutex_lock(&pdata->startstop_lock); - if (pdata->startstop_state != I2CP_CTRLR_STATE_RUNNING) { - mutex_unlock(&pdata->startstop_lock); - return -ENOTCONN; - } - i2c_adap_nr = pdata->i2c_adapter.nr; - mutex_unlock(&pdata->startstop_lock); - - rsp_wrapper = kzalloc(sizeof(*rsp_wrapper), GFP_KERNEL); - if (!rsp_wrapper) - return -ENOMEM; - - rsp_buf = kzalloc(sizeof(*rsp_buf), GFP_KERNEL); - if (!rsp_buf) { - ret = -ENOMEM; - goto fail_after_rsp_wrapper_alloc; - } - - ret = anprintf(&rsp_buf->buf, I2CP_MAX_MSG_BUF_SIZE, GFP_KERNEL, - "%*s%c%d", - (int)strlen(I2CP_NUMBER_REPLY_CMD), I2CP_NUMBER_REPLY_CMD, - i2cp_ctrlr_header_sep_char, i2c_adap_nr); - if (ret < 0) { - goto fail_after_rsp_buf_alloc; - } else if (ret == 0) { - ret = -EINVAL; - goto fail_after_buf_alloc; - } - rsp_buf->size = ret; - - rsp_wrapper->data = rsp_buf; - rsp_wrapper->formatter = i2cp_rsp_buffer_formatter; - - mutex_lock(&pdata->read_rsp_queue_lock); - if (pdata->read_rsp_queue_length >= I2CP_CTRLR_RSP_QUEUE_LIMIT) { - ret = -ENOBUFS; - mutex_unlock(&pdata->read_rsp_queue_lock); - goto fail_after_buf_alloc; - } - - list_add_tail(&rsp_wrapper->queue, &pdata->read_rsp_queue_head); - ++pdata->read_rsp_queue_length; - complete(&pdata->read_rsp_queued); - - mutex_unlock(&pdata->read_rsp_queue_lock); - return 0; - - fail_after_buf_alloc: - kfree(rsp_buf->buf); - fail_after_rsp_buf_alloc: - kfree(rsp_buf); - fail_after_rsp_wrapper_alloc: - kfree(rsp_wrapper); - return ret; -} - -static int i2cp_cmd_get_pseudo_id_header_receiver(void *data, char *in, - size_t in_size, bool non_blocking) -{ - /* - * No more header fields or data are expected. This directs any further - * input in this command to the data_receiver, which for this write - * command will unconditionally indicate a controller error. - */ - return 1; -} - -static int i2cp_cmd_get_pseudo_id_data_receiver(void *data, char *in, - size_t in_size, bool non_blocking) -{ - /* - * Reaching here means the controller wrote extra data in the command - * line after the initial command name. That is unexpected and - * indicates a controller bug. - */ - return -EPROTO; -} - -static int i2cp_cmd_get_pseudo_id_cmd_completer(void *data, - struct i2cp_controller *pdata, int receive_status, bool non_blocking) -{ - ssize_t ret; - struct i2cp_rsp_buffer *rsp_buf; - struct i2cp_rsp *rsp_wrapper; - - /* Abort if there were errors processing this command. */ - if (receive_status) - return 0; - - rsp_wrapper = kzalloc(sizeof(*rsp_wrapper), GFP_KERNEL); - if (!rsp_wrapper) - return -ENOMEM; - - rsp_buf = kzalloc(sizeof(*rsp_buf), GFP_KERNEL); - if (!rsp_buf) { - ret = -ENOMEM; - goto fail_after_rsp_wrapper_alloc; - } - - ret = anprintf(&rsp_buf->buf, I2CP_MAX_MSG_BUF_SIZE, GFP_KERNEL, - "%*s%c%u", - (int)strlen(I2CP_PSEUDO_ID_REPLY_CMD), I2CP_PSEUDO_ID_REPLY_CMD, - i2cp_ctrlr_header_sep_char, pdata->id); - if (ret < 0) { - goto fail_after_rsp_buf_alloc; - } else if (ret == 0) { - ret = -EINVAL; - goto fail_after_buf_alloc; - } - rsp_buf->size = ret; - - rsp_wrapper->data = rsp_buf; - rsp_wrapper->formatter = i2cp_rsp_buffer_formatter; - - mutex_lock(&pdata->read_rsp_queue_lock); - if (pdata->read_rsp_queue_length >= I2CP_CTRLR_RSP_QUEUE_LIMIT) { - ret = -ENOBUFS; - mutex_unlock(&pdata->read_rsp_queue_lock); - goto fail_after_buf_alloc; - } - - list_add_tail(&rsp_wrapper->queue, &pdata->read_rsp_queue_head); - ++pdata->read_rsp_queue_length; - complete(&pdata->read_rsp_queued); - - mutex_unlock(&pdata->read_rsp_queue_lock); - return 0; - - fail_after_buf_alloc: - kfree(rsp_buf->buf); - fail_after_rsp_buf_alloc: - kfree(rsp_buf); - fail_after_rsp_wrapper_alloc: - kfree(rsp_wrapper); - return ret; -} - -static int i2cp_cmd_set_name_suffix_data_creator(void **data) -{ - struct i2cp_cmd_set_name_suffix_data *cmd_data; - - cmd_data = kzalloc(sizeof(*cmd_data), GFP_KERNEL); - if (!cmd_data) - return -ENOMEM; - *data = cmd_data; - return 0; -} - -static void i2cp_cmd_set_name_suffix_data_destroyer(void *data) -{ - kfree(data); -} - -static int i2cp_cmd_set_name_suffix_header_receiver(void *data, char *in, - size_t in_size, bool non_blocking) -{ - return 1; -} - -static int i2cp_cmd_set_name_suffix_data_receiver(void *data, char *in, - size_t in_size, bool non_blocking) -{ - size_t remaining; - struct i2cp_cmd_set_name_suffix_data *cmd_data; - - cmd_data = data; - remaining = sizeof(cmd_data->name_suffix) - cmd_data->name_suffix_len; - /* Quietly truncate the suffix if necessary. */ - /* The suffix may need to be further truncated later. */ - if (in_size > remaining) - in_size = remaining; - memcpy(&cmd_data->name_suffix[cmd_data->name_suffix_len], in, in_size); - cmd_data->name_suffix_len += in_size; - return 0; -} - -static int i2cp_cmd_set_name_suffix_cmd_completer(void *data, - struct i2cp_controller *pdata, int receive_status, bool non_blocking) -{ - int ret; - struct i2cp_cmd_set_name_suffix_data *cmd_data; - - /* Abort if there were errors processing this command. */ - if (receive_status) - return 0; - - /* - * Acquire pdata->startstop_lock manually instead of using - * i2cp_adap_get_state() in order to keep the lock while - * setting the I2C adapter name. - */ - mutex_lock(&pdata->startstop_lock); - - if (pdata->startstop_state != I2CP_CTRLR_STATE_NEW) { - ret = -EISCONN; - goto unlock; - } - - cmd_data = data; - ret = snprintf(pdata->i2c_adapter.name, sizeof(pdata->i2c_adapter.name), - "I2C pseudo ID %u %*s", pdata->id, - (int)cmd_data->name_suffix_len, cmd_data->name_suffix); - if (ret < 0) - goto unlock; - - ret = 0; - - unlock: - mutex_unlock(&pdata->startstop_lock); - return ret; -} - -static int i2cp_cmd_set_timeout_data_creator(void **data) -{ - struct i2cp_cmd_set_timeout_data *cmd_data; - - cmd_data = kzalloc(sizeof(*cmd_data), GFP_KERNEL); - if (!cmd_data) - return -ENOMEM; - *data = cmd_data; - return 0; -} - -static void i2cp_cmd_set_timeout_data_destroyer(void *data) -{ - kfree(data); -} - -static int i2cp_cmd_set_timeout_header_receiver(void *data, char *in, - size_t in_size, bool non_blocking) -{ - int ret; - struct i2cp_cmd_set_timeout_data *cmd_data; - - cmd_data = data; - switch (cmd_data->field_pos++) { - case 0: - return 0; - case 1: - ret = kstrtouint(in, 0, &cmd_data->timeout_ms); - if (ret < 0) - return ret; - return 1; - } - /* Reaching here is a bug. */ - return -EINVAL; -} - -static int i2cp_cmd_set_timeout_data_receiver(void *data, char *in, - size_t in_size, bool non_blocking) -{ - /* - * Reaching here means the controller wrote extra data in the command - * line. That is unexpected and indicates a controller bug. - */ - return -EPROTO; -} - -static int i2cp_cmd_set_timeout_cmd_completer(void *data, - struct i2cp_controller *pdata, int receive_status, bool non_blocking) -{ - int ret; - struct i2cp_cmd_set_timeout_data *cmd_data; - - /* Abort if there were errors processing this command. */ - if (receive_status) - return 0; - - /* - * Acquire pdata->startstop_lock manually instead of using - * i2cp_adap_get_state() in order to keep the lock while setting the - * I2C adapter name. - */ - mutex_lock(&pdata->startstop_lock); - - if (pdata->startstop_state != I2CP_CTRLR_STATE_NEW) { - ret = -EISCONN; - goto unlock; - } - - cmd_data = data; - if (cmd_data->timeout_ms < I2CP_TIMEOUT_MS_MIN || - cmd_data->timeout_ms > I2CP_TIMEOUT_MS_MAX) { - ret = -ERANGE; - goto unlock; - } - - pdata->i2c_adapter.timeout = msecs_to_jiffies(cmd_data->timeout_ms); - ret = 0; - - unlock: - mutex_unlock(&pdata->startstop_lock); - return ret; -} - -/* Command names are matched in this order, so sort by expected frequency. */ -/* All elements should be initialized in their I2CP_CMD_*_IDX position. */ -static const struct i2cp_cmd i2cp_cmds[] = { - [I2CP_CMD_MXFER_REPLY_IDX] = { - .cmd_string = I2CP_MXFER_REPLY_CMD, - .cmd_size = CONST_STRLEN(I2CP_MXFER_REPLY_CMD), - .data_creator = i2cp_cmd_mxfer_reply_data_creator, - .data_shutdown = i2cp_cmd_mxfer_reply_data_shutdown, - .data_destroyer = i2cp_cmd_mxfer_reply_data_destroyer, - .header_receiver = i2cp_cmd_mxfer_reply_header_receiver, - .data_receiver = i2cp_cmd_mxfer_reply_data_receiver, - .cmd_completer = i2cp_cmd_mxfer_reply_cmd_completer, - }, - [I2CP_CMD_ADAP_START_IDX] = { - .cmd_string = I2CP_ADAP_START_CMD, - .cmd_size = CONST_STRLEN(I2CP_ADAP_START_CMD), - .header_receiver = i2cp_cmd_adap_start_header_receiver, - .data_receiver = i2cp_cmd_adap_start_data_receiver, - .cmd_completer = i2cp_cmd_adap_start_cmd_completer, - }, - [I2CP_CMD_ADAP_SHUTDOWN_IDX] = { - .cmd_string = I2CP_ADAP_SHUTDOWN_CMD, - .cmd_size = CONST_STRLEN(I2CP_ADAP_SHUTDOWN_CMD), - .header_receiver = i2cp_cmd_adap_shutdown_header_receiver, - .data_receiver = i2cp_cmd_adap_shutdown_data_receiver, - .cmd_completer = i2cp_cmd_adap_shutdown_cmd_completer, - }, - [I2CP_CMD_GET_NUMBER_IDX] = { - .cmd_string = I2CP_GET_NUMBER_CMD, - .cmd_size = CONST_STRLEN(I2CP_GET_NUMBER_CMD), - .header_receiver = i2cp_cmd_get_number_header_receiver, - .data_receiver = i2cp_cmd_get_number_data_receiver, - .cmd_completer = i2cp_cmd_get_number_cmd_completer, - }, - [I2CP_CMD_GET_PSEUDO_ID_IDX] = { - .cmd_string = I2CP_GET_PSEUDO_ID_CMD, - .cmd_size = CONST_STRLEN(I2CP_GET_PSEUDO_ID_CMD), - .header_receiver = i2cp_cmd_get_pseudo_id_header_receiver, - .data_receiver = i2cp_cmd_get_pseudo_id_data_receiver, - .cmd_completer = i2cp_cmd_get_pseudo_id_cmd_completer, - }, - [I2CP_CMD_SET_NAME_SUFFIX_IDX] = { - .cmd_string = I2CP_SET_NAME_SUFFIX_CMD, - .cmd_size = CONST_STRLEN(I2CP_SET_NAME_SUFFIX_CMD), - .data_creator = i2cp_cmd_set_name_suffix_data_creator, - .data_destroyer = i2cp_cmd_set_name_suffix_data_destroyer, - .header_receiver = i2cp_cmd_set_name_suffix_header_receiver, - .data_receiver = i2cp_cmd_set_name_suffix_data_receiver, - .cmd_completer = i2cp_cmd_set_name_suffix_cmd_completer, - }, - [I2CP_CMD_SET_TIMEOUT_IDX] = { - .cmd_string = I2CP_SET_TIMEOUT_CMD, - .cmd_size = CONST_STRLEN(I2CP_SET_TIMEOUT_CMD), - .data_creator = i2cp_cmd_set_timeout_data_creator, - .data_destroyer = i2cp_cmd_set_timeout_data_destroyer, - .header_receiver = i2cp_cmd_set_timeout_header_receiver, - .data_receiver = i2cp_cmd_set_timeout_data_receiver, - .cmd_completer = i2cp_cmd_set_timeout_cmd_completer, - }, -}; - -/* Returns whether or not there is response queue data to read. */ -/* Must be called with pdata->rsp_lock held. */ -static inline bool i2cp_poll_in(struct i2cp_controller *pdata) -{ - return pdata->rsp_invalidated || pdata->rsp_buf_remaining != 0 || - !list_empty(&pdata->read_rsp_queue_head); -} - -static inline int i2cp_fill_rsp_buf(struct i2cp_rsp *rsp_wrapper, - struct i2cp_rsp_buffer *rsp_buf, char *contents, size_t size) -{ - rsp_buf->buf = kmemdup(contents, size, GFP_KERNEL); - if (!rsp_buf->buf) - return -ENOMEM; - rsp_buf->size = size; - rsp_wrapper->data = rsp_buf; - rsp_wrapper->formatter = i2cp_rsp_buffer_formatter; - return 0; -} - -#define I2CP_FILL_RSP_BUF_WITH_LITERAL(rsp_wrapper, rsp_buf, str_literal)\ - i2cp_fill_rsp_buf(\ - rsp_wrapper, rsp_buf, str_literal, strlen(str_literal)) - -static int i2cp_adapter_master_xfer(struct i2c_adapter *adap, - struct i2c_msg *msgs, int num) -{ - int i, ret = 0; - long wait_ret; - size_t wrappers_length, wrapper_idx = 0, rsp_bufs_idx = 0; - struct i2cp_controller *pdata; - struct i2cp_rsp **rsp_wrappers; - struct i2cp_rsp_buffer *rsp_bufs[2] = {0}; - struct i2cp_rsp_master_xfer *mxfer_rsp; - struct i2cp_cmd_mxfer_reply_data *cmd_data; - struct i2cp_cmd_mxfer_reply *mxfer_reply; - - if (num <= 0) { - if (num < 0) - return -EINVAL; - return ret; - } - - pdata = adap->algo_data; - cmd_data = pdata->cmd_data[I2CP_CMD_MXFER_REPLY_IDX]; - - switch (i2cp_adap_get_state(pdata)) { - case I2CP_CTRLR_STATE_RUNNING: - break; - case I2CP_CTRLR_STATE_SHUTDN_REQ: - return ret; - default: - /* Reaching here is a bug, even with a valid enum value. */ - return -EINVAL; - } - - wrappers_length = (size_t)num + ARRAY_SIZE(rsp_bufs); - rsp_wrappers = kcalloc(wrappers_length, sizeof(*rsp_wrappers), - GFP_KERNEL); - if (!rsp_wrappers) - return -ENOMEM; - - mxfer_reply = kzalloc(sizeof(*mxfer_reply), GFP_KERNEL); - if (!mxfer_reply) { - ret = -ENOMEM; - goto return_after_rsp_wrappers_ptrs_alloc; - } - - mxfer_reply->num_msgs = num; - init_completion(&mxfer_reply->data_filled); - mutex_init(&mxfer_reply->lock); - - mxfer_reply->msgs = kcalloc(num, sizeof(*mxfer_reply->msgs), - GFP_KERNEL); - if (!mxfer_reply->msgs) { - ret = -ENOMEM; - goto return_after_mxfer_reply_alloc; - } - - mxfer_reply->completed = kcalloc(num, sizeof(*mxfer_reply->completed), - GFP_KERNEL); - if (!mxfer_reply->completed) { - ret = -ENOMEM; - goto return_after_reply_msgs_alloc; - } - - for (i = 0; i < num; ++i) { - mxfer_reply->msgs[i].addr = msgs[i].addr; - mxfer_reply->msgs[i].flags = msgs[i].flags; - mxfer_reply->msgs[i].len = msgs[i].len; - if (msgs[i].flags & I2C_M_RD) - /* Copy the address, not the data. */ - mxfer_reply->msgs[i].buf = msgs[i].buf; - } - - for (i = 0; i < ARRAY_SIZE(rsp_bufs); ++i) { - rsp_bufs[i] = kzalloc(sizeof(*rsp_bufs[i]), GFP_KERNEL); - if (!rsp_bufs[i]) { - ret = -ENOMEM; - goto return_after_reply_completed_alloc; - } - } - - mxfer_rsp = kzalloc(sizeof(*mxfer_rsp), GFP_KERNEL); - if (!mxfer_rsp) { - ret = -ENOMEM; - goto fail_after_individual_rsp_bufs_alloc; - } - - mxfer_rsp->id = cmd_data->next_mxfer_id++; - mxfer_rsp->num = num; - - mxfer_rsp->msgs = kcalloc(num, sizeof(*mxfer_rsp->msgs), GFP_KERNEL); - if (!mxfer_rsp->msgs) { - ret = -ENOMEM; - goto fail_after_mxfer_rsp_alloc; - } - - for (i = 0; i < num; ++i) { - mxfer_rsp->msgs[i].addr = msgs[i].addr; - mxfer_rsp->msgs[i].flags = msgs[i].flags; - mxfer_rsp->msgs[i].len = msgs[i].len; - if (msgs[i].flags & I2C_M_RD) - continue; - /* Copy the data, not the address. */ - mxfer_rsp->msgs[i].buf = kmemdup(msgs[i].buf, msgs[i].len, - GFP_KERNEL); - if (!mxfer_rsp->msgs[i].buf) { - ret = -ENOMEM; - goto fail_after_rsp_msgs_alloc; - } - } - - for (i = 0; i < wrappers_length; ++i) { - rsp_wrappers[i] = kzalloc(sizeof(*rsp_wrappers[i]), GFP_KERNEL); - if (!rsp_wrappers[i]) { - ret = -ENOMEM; - goto fail_after_individual_rsp_wrappers_alloc; - } - } - - ret = I2CP_FILL_RSP_BUF_WITH_LITERAL(rsp_wrappers[wrapper_idx++], - rsp_bufs[rsp_bufs_idx++], I2CP_BEGIN_MXFER_REQ_CMD); - if (ret < 0) - goto fail_after_individual_rsp_wrappers_alloc; - - for (i = 0; i < num; ++i) { - rsp_wrappers[wrapper_idx]->data = mxfer_rsp; - rsp_wrappers[wrapper_idx++]->formatter = - i2cp_rsp_master_xfer_formatter; - } - - ret = I2CP_FILL_RSP_BUF_WITH_LITERAL(rsp_wrappers[wrapper_idx++], - rsp_bufs[rsp_bufs_idx++], I2CP_COMMIT_MXFER_REQ_CMD); - if (ret < 0) - goto fail_after_individual_rsp_wrappers_alloc; - - BUILD_BUG_ON(rsp_bufs_idx != ARRAY_SIZE(rsp_bufs)); - - mutex_lock(&pdata->read_rsp_queue_lock); - if (pdata->read_rsp_queue_length >= I2CP_CTRLR_RSP_QUEUE_LIMIT) { - ret = -ENOBUFS; - goto fail_with_read_rsp_queue_lock; - } - - mutex_lock(&cmd_data->reply_queue_lock); - if (cmd_data->reply_queue_length >= I2CP_CTRLR_RSP_QUEUE_LIMIT) { - ret = -ENOBUFS; - goto fail_with_reply_queue_lock; - } - - mxfer_reply->id = mxfer_rsp->id; - list_add_tail(&mxfer_reply->reply_queue_item, - &cmd_data->reply_queue_head); - ++cmd_data->reply_queue_length; - - for (i = 0; i < wrappers_length; ++i) { - list_add_tail(&rsp_wrappers[i]->queue, - &pdata->read_rsp_queue_head); - complete(&pdata->read_rsp_queued); - } - pdata->read_rsp_queue_length += wrappers_length; - - mutex_unlock(&cmd_data->reply_queue_lock); - mutex_unlock(&pdata->read_rsp_queue_lock); - - /* Wake up the userspace controller if it was polling. */ - wake_up_interruptible(&pdata->poll_wait_queue); - /* Wait for a response from the userspace controller. */ - wait_ret = wait_for_completion_killable_timeout( - &mxfer_reply->data_filled, adap->timeout); - - mutex_lock(&cmd_data->reply_queue_lock); - /* - * Ensure mxfer_reply is not in use before dequeuing and freeing it. - * This depends on the requirement that mxfer_reply->lock only be - * acquired while holding cmd_data->reply_queue_lock. - */ - mutex_lock(&mxfer_reply->lock); - - if (wait_ret == -ERESTARTSYS) - ret = -EINTR; - else if (wait_ret < 0) - ret = wait_ret; - else - ret = mxfer_reply->ret; - - /* - * This depends on other functions that might delete - * mxfer_reply->reply_queue_item from cmd_data->reply_queue_head using - * list_del_init(), never list_del(). - */ - if (!list_empty(&mxfer_reply->reply_queue_item)) { - list_del(&mxfer_reply->reply_queue_item); - --cmd_data->reply_queue_length; - if (mxfer_reply == cmd_data->reply_queue_current_item) - cmd_data->reply_queue_current_item = NULL; - } - - mutex_unlock(&mxfer_reply->lock); - mutex_unlock(&cmd_data->reply_queue_lock); - goto return_after_reply_msgs_alloc; - - fail_with_reply_queue_lock: - mutex_unlock(&cmd_data->reply_queue_lock); - fail_with_read_rsp_queue_lock: - mutex_unlock(&pdata->read_rsp_queue_lock); - fail_after_individual_rsp_wrappers_alloc: - for (i = 0; i < wrappers_length; ++i) - kfree(rsp_wrappers[i]); - fail_after_rsp_msgs_alloc: - for (i = 0; i < num; ++i) - kfree(mxfer_rsp->msgs[i].buf); - kfree(mxfer_rsp->msgs); - fail_after_mxfer_rsp_alloc: - kfree(mxfer_rsp); - fail_after_individual_rsp_bufs_alloc: - for (i = 0; i < ARRAY_SIZE(rsp_bufs); ++i) { - kfree(rsp_bufs[i]->buf); - kfree(rsp_bufs[i]); - } - return_after_reply_completed_alloc: - kfree(mxfer_reply->completed); - return_after_reply_msgs_alloc: - kfree(mxfer_reply->msgs); - return_after_mxfer_reply_alloc: - kfree(mxfer_reply); - return_after_rsp_wrappers_ptrs_alloc: - kfree(rsp_wrappers); - return ret; -} - -/* - * If more functionality than this needs to be supported, add a write command - * for the controller to specify its additional functionality prior to - * ADAPTER_START. Basic I2C functionality should remain implied and required. - * - * These functionalities in particular could be worth supporting: - * I2C_FUNC_10BIT_ADDR - * I2C_FUNC_NOSTART - * I2C_FUNC_PROTOCOL_MANGLING - */ -static u32 i2cp_adapter_functionality(struct i2c_adapter *adap) -{ - return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; -} - -static const struct i2c_algorithm i2cp_algorithm = { - .master_xfer = i2cp_adapter_master_xfer, - .functionality = i2cp_adapter_functionality, -}; - -/* this_pseudo->counters.lock must _not_ be held when calling this. */ -static void i2cp_remove_from_counters(struct i2cp_controller *pdata, - struct i2cp_device *this_pseudo) -{ - - mutex_lock(&this_pseudo->counters.lock); - this_pseudo->counters.all_controllers[pdata->index] = NULL; - --this_pseudo->counters.count; - mutex_unlock(&this_pseudo->counters.lock); -} - -static int i2cp_cdev_open(struct inode *inodep, struct file *filep) -{ - int ret = 0; - unsigned int i, num_cmd_data_created = 0; - unsigned int ctrlr_id; - struct i2cp_controller *pdata; - struct i2cp_device *this_pseudo; - - /* Is there any way to find this through @inodep? */ - this_pseudo = i2cp_device; - - /* - * HAVE_STREAM_OPEN value meanings: - * -1 : stream_open() is not available - * 0 : unknown if stream_open() is or is not available - * 1 : stream_open() is available - */ -#if HAVE_STREAM_OPEN >= 0 - /* I2C pseudo adapter controllers are non-seekable pure I/O streams. */ - stream_open(inodep, filep); -#else - /* I2C pseudo adapter controllers are not seekable. */ - nonseekable_open(inodep, filep); -#endif - /* Refuse fsnotify events. Modeled after /dev/ptmx implementation. */ - filep->f_mode |= FMODE_NONOTIFY; - - /* Allocate the I2C adapter. */ - pdata = kzalloc(sizeof(*pdata), GFP_KERNEL); - if (!pdata) - return -ENOMEM; - - INIT_LIST_HEAD(&pdata->read_rsp_queue_head); - init_waitqueue_head(&pdata->poll_wait_queue); - init_completion(&pdata->read_rsp_queued); - mutex_init(&pdata->startstop_lock); - mutex_init(&pdata->cmd_lock); - mutex_init(&pdata->rsp_lock); - mutex_init(&pdata->read_rsp_queue_lock); - - for (i = 0; i < ARRAY_SIZE(i2cp_cmds); ++i) { - if (!i2cp_cmds[i].data_creator) - continue; - ret = i2cp_cmds[i].data_creator(&pdata->cmd_data[i]); - if (ret < 0) - break; - } - num_cmd_data_created = i; - if (ret < 0) - goto fail_after_cmd_data_created; - - mutex_lock(&this_pseudo->counters.lock); - - for (i = 0; i < i2cp_limit; ++i) - if (!this_pseudo->counters.all_controllers[i]) - break; - if (i >= i2cp_limit) { - mutex_unlock(&this_pseudo->counters.lock); - ret = -ENOSPC; - goto fail_after_cmd_data_created; - } - pdata->index = i; - - for (ctrlr_id = this_pseudo->counters.next_ctrlr_id;;) { - /* Determine whether ctrlr_id is already in use. */ - for (i = 0; i < i2cp_limit; ++i) { - if (this_pseudo->counters.all_controllers[i] && - (this_pseudo->counters.all_controllers[i]->id == - ctrlr_id)) - break; - } - /* If ctrlr_id is available, use it. */ - if (i >= i2cp_limit) { - pdata->id = ctrlr_id; - this_pseudo->counters.next_ctrlr_id = ctrlr_id + 1; - ++this_pseudo->counters.count; - this_pseudo->counters.all_controllers[pdata->index] = - pdata; - break; - } - /* Increment ctrlr_id, and check for wrapping. */ - if (++ctrlr_id == this_pseudo->counters.next_ctrlr_id) { - mutex_unlock(&this_pseudo->counters.lock); - ret = -ENOSPC; - goto fail_after_cmd_data_created; - } - } - - mutex_unlock(&this_pseudo->counters.lock); - - /* Initialize the I2C adapter. */ - pdata->i2c_adapter.owner = THIS_MODULE; - pdata->i2c_adapter.class = I2C_CLASS_HWMON | I2C_CLASS_SPD; - pdata->i2c_adapter.algo = &i2cp_algorithm; - pdata->i2c_adapter.algo_data = pdata; - pdata->i2c_adapter.timeout = msecs_to_jiffies(i2cp_default_timeout_ms); - pdata->i2c_adapter.dev.parent = &this_pseudo->device; - ret = snprintf(pdata->i2c_adapter.name, sizeof(pdata->i2c_adapter.name), - "I2C pseudo ID %u", pdata->id); - if (ret < 0) - goto fail_after_counters_update; - - /* Return success. */ - filep->private_data = pdata; - return 0; - - fail_after_counters_update: - i2cp_remove_from_counters(pdata, this_pseudo); - fail_after_cmd_data_created: - for (i = 0; i < num_cmd_data_created; ++i) - if (i2cp_cmds[i].data_destroyer) - i2cp_cmds[i].data_destroyer(pdata->cmd_data[i]); - kfree(pdata); - return ret; -} - -static int i2cp_cdev_release(struct inode *inodep, struct file *filep) -{ - int i; - bool adapter_was_added = false; - struct i2cp_controller *pdata; - struct i2cp_device *this_pseudo; - - pdata = filep->private_data; - this_pseudo = container_of(pdata->i2c_adapter.dev.parent, - struct i2cp_device, device); - - /* - * The select(2) man page makes it clear that the behavior of pending - * select()/poll()/epoll_wait() on a fd that gets closed while waiting - * is undefined and should never be relied on. However since we are - * about to free pdata and therefore free pdata->poll_wait_queue, safest - * to wake up anyone waiting on it in an attempt to not leave them in a - * completely undefined state. - */ - wake_up_interruptible_all(&pdata->poll_wait_queue); - /* - * Linux guarantees there are no outstanding reads or writes when a - * struct file is released, so no further synchronization with the other - * struct file_operations callbacks should be needed. - */ - filep->private_data = NULL; - - mutex_lock(&pdata->startstop_lock); - if (pdata->startstop_state != I2CP_CTRLR_STATE_NEW) { - /* - * Defer deleting the adapter until after releasing - * pdata->startstop_state. This avoids deadlocking with any - * overlapping i2cp_adapter_master_xfer() calls, which also - * acquire the lock in order to check the state. - */ - adapter_was_added = true; - /* - * Instruct any overlapping i2cp_adapter_master_xfer() calls to - * return immediately. - */ - pdata->startstop_state = I2CP_CTRLR_STATE_SHUTDN_REQ; - } - mutex_unlock(&pdata->startstop_lock); - - /* - * Wake up blocked I2C requests. This is an optimization so that they - * don't need to wait for the I2C adapter timeout, since there is no - * possibility of any further I2C replies. - */ - for (i = 0; i < ARRAY_SIZE(i2cp_cmds); ++i) - if (i2cp_cmds[i].data_shutdown) - i2cp_cmds[i].data_shutdown(pdata->cmd_data[i]); - - if (adapter_was_added) - i2c_del_adapter(&pdata->i2c_adapter); - - for (i = 0; i < ARRAY_SIZE(i2cp_cmds); ++i) { - if (i2cp_cmds[i].data_destroyer) - i2cp_cmds[i].data_destroyer(pdata->cmd_data[i]); - pdata->cmd_data[i] = NULL; - } - - i2cp_remove_from_counters(pdata, this_pseudo); - kfree(pdata); - return 0; -} - -/* The caller must hold pdata->rsp_lock. */ -/* Return value is whether or not to continue in calling loop. */ -static bool i2cp_cdev_read_iteration(char __user **buf, size_t *count, - ssize_t *ret, bool non_blocking, struct i2cp_controller *pdata) -{ - long wait_ret; - ssize_t copy_size; - unsigned long copy_ret; - struct i2cp_rsp *rsp_wrapper = NULL; - - /* - * If a previous read response buffer has been exhausted, free - * it. - * - * This is done at the beginning of the while(count>0) loop - * because...? - */ - if (pdata->rsp_buf_start && !pdata->rsp_buf_remaining) { - kfree(pdata->rsp_buf_start); - pdata->rsp_buf_start = NULL; - pdata->rsp_buf_pos = NULL; - } - - /* - * If we have no formatter callback output queued (neither - * successful output nor error), go through the FIFO queue of - * read responses until a formatter returns non-zero (successful - * output or failure). - */ - while (pdata->rsp_buf_remaining == 0) { - /* - * If pdata->rsp_invalidated is true, it means the - * previous read() returned an error. Now that the - * error has already been propagated to userspace, we - * can write the end character for the invalidated read - * response. - */ - if (pdata->rsp_invalidated) { - pdata->rsp_invalidated = false; - goto write_end_char; - } - - /* If we have already read some bytes successfully, even - * if less than requested, we should return as much as - * we can without blocking further. Same if we have an - * error to return. - */ - if (non_blocking || *ret != 0) { - if (!try_wait_for_completion(&pdata->read_rsp_queued)) { - if (*ret == 0) - *ret = -EAGAIN; - /* - * If we are out of read responses, - * return whatever we have written to - * the userspace buffer so far, even if - * it's nothing. - */ - return false; - } - } else { - wait_ret = wait_for_completion_killable( - &pdata->read_rsp_queued); - if (wait_ret == -ERESTARTSYS) { - if (*ret == 0) - *ret = -EINTR; - return false; - } else if (wait_ret < 0) { - if (*ret == 0) - *ret = wait_ret; - return false; - } - } - - mutex_lock(&pdata->read_rsp_queue_lock); - if (!list_empty(&pdata->read_rsp_queue_head)) - rsp_wrapper = list_first_entry( - &pdata->read_rsp_queue_head, - struct i2cp_rsp, queue); - /* - * Avoid holding pdata->read_rsp_queue_lock while - * executing a formatter, allocating memory, or doing - * anything else that might block or take non-trivial - * time. This avoids blocking the enqueuing of new read - * responses for any significant time, even during large - * controller reads. - */ - mutex_unlock(&pdata->read_rsp_queue_lock); - - if (!rsp_wrapper) { - /* This should only happen if shutdown was requested. */ - if (i2cp_adap_get_state(pdata) != - I2CP_CTRLR_STATE_SHUTDN_REQ) - *ret = -EINVAL; - return false; - } - - pdata->rsp_buf_remaining = rsp_wrapper->formatter( - rsp_wrapper->data, &pdata->rsp_buf_start); - - if (pdata->rsp_buf_remaining > 0) { - pdata->rsp_buf_pos = pdata->rsp_buf_start; - /* - * We consumed a completion for this rsp_wrapper - * but we are leaving it in - * pdata->read_rsp_queue_head. Re-add a - * completion for it. - * - * Since overlapping reads are effectively - * serialized via use of pdata->rsp_lock, we - * could take shortcuts in how - * pdata->read_rsp_queued is used to avoid the - * need for re-incrementing it here. However by - * maintaining the invariant of consuming a - * completion each time an item from - * pdata->read_rsp_queue_head is consumed - * (whether or not it ends up being removed from - * the queue in that iteration), the completion - * logic is simpler to follow, and more easily - * lends itself to a future refactor of this - * read operation to not hold pdata->rsp_lock - * continuously. - */ - complete(&pdata->read_rsp_queued); - break; - } - - /* - * The formatter should not mutate pdata->rsp_buf_start - * if it returned non-positive. Just in case, we handle - * such a bug gracefully here. - */ - kfree(pdata->rsp_buf_start); - pdata->rsp_buf_start = NULL; - - mutex_lock(&pdata->read_rsp_queue_lock); - list_del(&rsp_wrapper->queue); - --pdata->read_rsp_queue_length; - mutex_unlock(&pdata->read_rsp_queue_lock); - - kfree(rsp_wrapper); - rsp_wrapper = NULL; - - /* Check if the formatter callback returned an error. - * - * If we have _not_ written any bytes to the userspace - * buffer yet, return now with the error code from the - * formatter. - * - * If we _have_ written bytes already, return now with - * the number of bytes written, and leave the error code - * from the formatter in pdata->rsp_buf_remaining so it - * can be returned on the next read, before any bytes - * are written. - * - * In either case, we deliberately return the error - * before writing the end character for the invalidated - * read response, so that the userspace controller knows - * to discard the response. - */ - if (pdata->rsp_buf_remaining < 0) { - if (*ret == 0) { - *ret = pdata->rsp_buf_remaining; - pdata->rsp_buf_remaining = 0; - } - pdata->rsp_invalidated = true; - return false; - } - - write_end_char: - copy_size = sizeof(i2cp_ctrlr_end_char); - /* - * This assertion is just in case someone changes - * i2cp_ctrlr_end_char to a string. Such a change would require - * handling it like a read response buffer, including ensuring - * that we not write more than *count. So long as it's a single - * character, we can avoid an extra check of *count in this code - * block, we already know it's greater than zero. - */ - BUILD_BUG_ON(copy_size != 1); - copy_ret = copy_to_user(*buf, &i2cp_ctrlr_end_char, - copy_size); - copy_size -= copy_ret; - /* - * After writing to the userspace buffer, we need to - * update various counters including the return value, - * then continue from the start of the outer while loop - * because it's possible *count has reached zero. - * - * Those exact same steps must be done after copying - * from a read response buffer to the userspace buffer, - * so jump to that code instead of duplicating it. - */ - goto after_copy_to_user; - } - - copy_size = max_t(ssize_t, 0, - min_t(ssize_t, *count, pdata->rsp_buf_remaining)); - copy_ret = copy_to_user(*buf, pdata->rsp_buf_pos, copy_size); - copy_size -= copy_ret; - pdata->rsp_buf_remaining -= copy_size; - - if (pdata->rsp_buf_remaining > 0) { - pdata->rsp_buf_pos += copy_size; - } else { - kfree(pdata->rsp_buf_start); - pdata->rsp_buf_start = NULL; - pdata->rsp_buf_pos = NULL; - } - - /* - * When jumping here, the following variables should be set: - * copy_ret: Return value from copy_to_user() (bytes not copied). - * copy_size: The number of bytes successfully copied by copy_to_user(). In - * other words, this should be the size arg to copy_to_user() minus its - * return value (bytes not copied). - */ - after_copy_to_user: - *ret += copy_size; - *count -= copy_size; - *buf += copy_size; - - return !copy_ret; -} - -static ssize_t i2cp_cdev_read(struct file *filep, char __user *buf, - size_t count, loff_t *f_ps) -{ - ssize_t ret = 0; - bool non_blocking; - struct i2cp_controller *pdata; - - /* - * Just in case this could change out from under us, best to keep a - * consistent view for the duration of this syscall. - */ - non_blocking = !!(filep->f_flags & O_NONBLOCK); - pdata = filep->private_data; - - if (count > (size_t)I2CP_RW_SIZE_LIMIT) - count = I2CP_RW_SIZE_LIMIT; - - /* - * Since read() calls are effectively serialized by way of - * pdata->rsp_lock, we MUST NOT block on obtaining that lock if in - * non-blocking mode, because it might be held by a blocking read(). - */ - if (!non_blocking) - mutex_lock(&pdata->rsp_lock); - else if (!mutex_trylock(&pdata->rsp_lock)) - return -EAGAIN; - - /* - * Check if a formatter callback returned an error that hasn't yet been - * returned to the controller. Do this before the while(count>0) loop - * because read(2) with zero count is allowed to report errors. - */ - if (pdata->rsp_buf_remaining < 0) { - BUILD_BUG_ON(ret != 0); - ret = pdata->rsp_buf_remaining; - pdata->rsp_buf_remaining = 0; - goto unlock; - } - - while (count > 0 && i2cp_cdev_read_iteration( - &buf, &count, &ret, non_blocking, pdata)) - ; - - unlock: - mutex_unlock(&pdata->rsp_lock); - return ret; -} - -/* Must be called with pdata->cmd_lock held. */ -/* Must never consume past first i2cp_ctrlr_end_char in @start. */ -static ssize_t i2cp_receive_ctrlr_cmd_header( - struct i2cp_controller *pdata, char *start, size_t remaining, - bool non_blocking) -{ - int found_deliminator_char = 0; - int i, cmd_idx; - ssize_t copy_size, ret = 0, stop, buf_remaining; - - buf_remaining = I2CP_CTRLR_CMD_LIMIT - pdata->cmd_size; - stop = min_t(ssize_t, remaining, buf_remaining + 1); - - for (i = 0; i < stop; ++i) - if (start[i] == i2cp_ctrlr_end_char || - start[i] == i2cp_ctrlr_header_sep_char) { - found_deliminator_char = 1; - break; - } - - if (i <= buf_remaining) { - copy_size = i; - } else { - copy_size = buf_remaining; - if (!pdata->cmd_receive_status) - /* - * Exceeded max size of I2C pseudo controller command - * buffer. The command currently being written will be - * ignored. - * - * Positive error number is deliberate here. - */ - pdata->cmd_receive_status = ENOBUFS; - } - - memcpy(&pdata->cmd_buf[pdata->cmd_size], start, copy_size); - pdata->cmd_size += copy_size; - - if (!found_deliminator_char || pdata->cmd_size <= 0) - return copy_size + found_deliminator_char; - - /* This may be negative. */ - cmd_idx = pdata->cmd_idx_plus_one - 1; - - if (cmd_idx < 0) { - for (i = 0; i < ARRAY_SIZE(i2cp_cmds); ++i) - if (i2cp_cmds[i].cmd_size == pdata->cmd_size && - !memcmp(i2cp_cmds[i].cmd_string, pdata->cmd_buf, - pdata->cmd_size)) - break; - if (i >= ARRAY_SIZE(i2cp_cmds)) { - /* unrecognized command */ - ret = -EIO; - goto clear_buffer; - } - cmd_idx = i; - pdata->cmd_idx_plus_one = cmd_idx + 1; - } - - /* - * If we have write bytes queued and we encountered i2cp_ctrlr_end_char - * or i2cp_ctrlr_header_sep_char, invoke the header_receiver callback. - */ - if (!pdata->cmd_receive_status) { - ret = i2cp_cmds[cmd_idx].header_receiver( - pdata->cmd_data[cmd_idx], pdata->cmd_buf, - pdata->cmd_size, non_blocking); - if (ret > 0) { - if (ret > I2CP_CTRLR_CMD_LIMIT) { - ret = -EINVAL; - goto clear_buffer; - } - pdata->cmd_data_increment = ret; - } else if (ret < 0) { - pdata->cmd_receive_status = ret; - } - } - - clear_buffer: - pdata->cmd_size = 0; - /* - * Ensure a trailing null character for the next header_receiver() or - * data_receiver() invocation. - */ - memset(pdata->cmd_buf, 0, sizeof(pdata->cmd_buf)); - - if (ret < 0) { - if (pdata->cmd_idx_plus_one >= 1 && !pdata->cmd_receive_status) - /* Negate to get a positive error number. */ - pdata->cmd_receive_status = -ret; - return ret; - } - return copy_size + found_deliminator_char; -} - -/* Must be called with pdata->cmd_lock held. */ -/* Must never consume past first i2cp_ctrlr_end_char in @start. */ -static ssize_t i2cp_receive_ctrlr_cmd_data(struct i2cp_controller *pdata, - char *start, size_t remaining, bool non_blocking) -{ - ssize_t i, ret, size_holder; - int cmd_idx; - - /* If cmd_idx ends up negative here, it is a bug. */ - cmd_idx = pdata->cmd_idx_plus_one - 1; - if (cmd_idx < 0) - return -EINVAL; - - size_holder = min_t(size_t, - (I2CP_CTRLR_CMD_LIMIT - - (I2CP_CTRLR_CMD_LIMIT % pdata->cmd_data_increment)) - - pdata->cmd_size, - (((pdata->cmd_size + remaining) / - pdata->cmd_data_increment) * - pdata->cmd_data_increment) - pdata->cmd_size); - - /* Size of current buffer plus all remaining write bytes. */ - size_holder = pdata->cmd_size + remaining; - /* - * Avoid rounding down to zero. If there are insufficient write - * bytes remaining to grow the buffer to 1x of the requested - * data byte increment, we'll copy what is available to the - * buffer, and just leave it queued without any further command - * handler invocations in this write() (unless i2cp_ctrlr_end_char is - * found, in which case we will always invoke the data_receiver for any - * remaining data bytes, and will always invoke the cmd_completer). - */ - if (size_holder > pdata->cmd_data_increment) - /* - * Round down to the nearest multiple of the requested - * data byte increment. - */ - size_holder -= size_holder % pdata->cmd_data_increment; - /* - * Take the smaller of: - * - * [A] 2nd min_t() arg: The number of bytes that we would want the - * buffer to end up with if it had unlimited space (computed - * above). - * - * [B] 3rd min_t() arg: The number of bytes that we would want the - * buffer to end up with if there were unlimited write bytes - * remaining (computed in-line below). - */ - size_holder = min_t(ssize_t, size_holder, (I2CP_CTRLR_CMD_LIMIT - ( - I2CP_CTRLR_CMD_LIMIT % pdata->cmd_data_increment))); - /* - * Subtract the existing buffer size to get the number of bytes we - * actually want to copy from the remaining write bytes in this loop - * iteration, assuming no i2cp_ctrlr_end_char. - */ - size_holder -= pdata->cmd_size; - - /* - * Look for i2cp_ctrlr_end_char. If we find it, we will copy up to but - * *not* including its position. - */ - for (i = 0; i < size_holder; ++i) - if (start[i] == i2cp_ctrlr_end_char) - break; - - /* Copy from the remaining write bytes to the command buffer. */ - memcpy(&pdata->cmd_buf[pdata->cmd_size], start, i); - pdata->cmd_size += i; - - /* - * If we have write bytes queued and *either* we encountered - * i2cp_ctrlr_end_char *or* we have a multiple of - * pdata->cmd_data_increment, invoke the data_receiver callback. - */ - if (pdata->cmd_size > 0 && - (i < size_holder || - pdata->cmd_size % pdata->cmd_data_increment == 0)) { - if (!pdata->cmd_receive_status) { - ret = i2cp_cmds[cmd_idx].data_receiver( - pdata->cmd_data[cmd_idx], pdata->cmd_buf, - pdata->cmd_size, non_blocking); - if (ret < 0) - pdata->cmd_receive_status = ret; - } - pdata->cmd_size = 0; - /* - * Ensure a trailing null character for the next - * header_receiver() or data_receiver() invocation. - */ - memset(pdata->cmd_buf, 0, sizeof(pdata->cmd_buf)); - } - - /* If i2cp_ctrlr_end_char was found, skip past it. */ - if (i < size_holder) - ++i; - return i; -} - -/* Must be called with pdata->cmd_lock held. */ -static int i2cp_receive_ctrlr_cmd_complete(struct i2cp_controller *pdata, - bool non_blocking) -{ - int ret = 0, cmd_idx; - - /* This may be negative. */ - cmd_idx = pdata->cmd_idx_plus_one - 1; - - if (cmd_idx >= 0 && i2cp_cmds[cmd_idx].cmd_completer) { - ret = i2cp_cmds[cmd_idx].cmd_completer(pdata->cmd_data[cmd_idx], - pdata, pdata->cmd_receive_status, non_blocking); - if (ret > 0) - ret = 0; - } - - pdata->cmd_idx_plus_one = 0; - pdata->cmd_receive_status = 0; - pdata->cmd_data_increment = 0; - - pdata->cmd_size = 0; - /* - * Ensure a trailing null character for the next header_receiver() or - * data_receiver() invocation. - */ - memset(pdata->cmd_buf, 0, sizeof(pdata->cmd_buf)); - - return ret; -} - -static ssize_t i2cp_cdev_write(struct file *filep, const char __user *buf, - size_t count, loff_t *f_ps) -{ - ssize_t ret = 0; - bool non_blocking; - size_t remaining; - char *kbuf, *start; - struct i2cp_controller *pdata; - - /* - * Just in case this could change out from under us, best to keep a - * consistent view for the duration of this syscall. - * - * Write command implementations, i.e. struct i2cp_cmd implementations, - * do NOT have to support blocking writes. For example, if a write of - * an I2C message reply is received for a message that the pseudo - * adapter never requested or expected, it makes more sense to indicate - * an error than to block until possibly receiving a master_xfer request - * for that I2C message, even if blocking is permitted. - * - * Furthermore, controller writes MUST NEVER block indefinitely, even - * when non_blocking is false. E.g. while non_blocking may be used to - * select between mutex_trylock and mutex_lock*, even in the - * latter case the lock should never be blocked on I/O, on userspace, or - * on anything else outside the control of this driver. It IS - * permissable for the lock to be blocked on processing of previous or - * concurrent write input, so long as that processing does not violate - * these rules. - */ - non_blocking = !!(filep->f_flags & O_NONBLOCK); - pdata = filep->private_data; - - if (count > (size_t)I2CP_RW_SIZE_LIMIT) - count = I2CP_RW_SIZE_LIMIT; - - kbuf = kzalloc(count, GFP_KERNEL); - if (!kbuf) { - ret = -ENOMEM; - goto free_kbuf; - } - if (copy_from_user(kbuf, buf, count)) { - ret = -EFAULT; - goto free_kbuf; - } - - start = kbuf; - remaining = count; - - /* - * Since write() calls are effectively serialized by way of - * pdata->cmd_lock, we MUST NOT block on obtaining that lock if in - * non-blocking mode, because it might be held by a blocking write(). - */ - if (!non_blocking) { - mutex_lock(&pdata->cmd_lock); - } else if (!mutex_trylock(&pdata->cmd_lock)) { - ret = -EAGAIN; - goto free_kbuf; - } - - while (remaining) { - if (pdata->cmd_data_increment <= 0) - ret = i2cp_receive_ctrlr_cmd_header( - pdata, start, remaining, non_blocking); - else - ret = i2cp_receive_ctrlr_cmd_data( - pdata, start, remaining, non_blocking); - if (ret < 0) - break; - if (ret == 0 || ret > remaining) { - ret = -EINVAL; - break; - } - - remaining -= ret; - start += ret; - - if (ret > 0 && start[-1] == i2cp_ctrlr_end_char) { - ret = i2cp_receive_ctrlr_cmd_complete( - pdata, non_blocking); - if (ret < 0) - break; - } - } - - mutex_unlock(&pdata->cmd_lock); - wake_up_interruptible_sync(&pdata->poll_wait_queue); - - if (ret >= 0) - /* If successful the whole write is always consumed. */ - ret = count; - - free_kbuf: - kfree(kbuf); - return ret; -} - -/* - * The select/poll/epoll implementation in this module is designed around these - * controller behavior assumptions: - * - * - If any reader of a given controller makes use of polling, all will. - * - * - Upon notification of available data to read, a reader will fully consume it - * in a read() loop until receiving EAGAIN, EWOULDBLOCK, or EOF. - * - * - Only one reader need be woken upon newly available data, however it is okay - * if more than one are sometimes woken. - * - * - If more than one reader is woken, or otherwise acts in parallel, it is the - * responsibility of the readers to either ensure that only one at a time - * consumes all input until EAGAIN/EWOULDBLOCK, or that they properly - * recombine any data that was split among them. - * - * - All of the above applies to writers as well. - * - * Notes: - * - * - If a reader does not read all available data until EAGAIN/EWOULDBLOCK after - * being woken from poll, there may be no wake event for the remaining - * available data, causing it to remain unread until further data becomes - * available and triggers another wake event. The same applies to writers - - * they are only guaranteed to be woken /once/ per blocked->unblocked - * transition, so after being woken they should continue writing until either - * the controller is out of data or EAGAIN/EWOULDBLOCK is encountered. - * - * - It is strongly suggested that controller implementations have only one - * reader (thread) and one writer (thread), which may or may not be the same - * thread. After all only one message can be active on an I2C bus at a time, - * and this driver implementation reflects that. Avoiding multiple readers - * and multiple writers greatly simplifies controller implementation, and - * there is likely nothing to be gained from performing any of their work in - * parallel. - * - * - Implementation detail: Reads are effectively serialized by a per controller - * read lock. From the perspective of other readers, the controller device - * will appear blocked, with appropriate behavior based on the O_NONBLOCK bit. - * THIS IS SUBJECT TO CHANGE! - * - * - Implementation detail: Writes are effectively serialized by a per - * controller write lock. From the perspective of other writers, the - * controller device will appear blocked, with appropriate behavior based on - * the O_NONBLOCK bit. THIS IS SUBJECT TO CHANGE! - * - * - Implementation detail: In the initial implementation, the only scenario - * where a controller will appear blocked for writes is if another write is in - * progress. Thus, a single writer should never see the device blocked. THIS - * IS SUBJECT TO CHANGE! When using O_NONBLOCK, a controller should correctly - * handle EAGAIN/EWOULDBLOCK even if it has only one writer. - */ -static __poll_t i2cp_cdev_poll(struct file *filep, poll_table *ptp) -{ - __poll_t poll_ret = 0; - struct i2cp_controller *pdata; - - pdata = filep->private_data; - - poll_wait(filep, &pdata->poll_wait_queue, ptp); - - if (mutex_trylock(&pdata->rsp_lock)) { - if (i2cp_poll_in(pdata)) - poll_ret |= POLLIN | POLLRDNORM; - mutex_unlock(&pdata->rsp_lock); - } - - if (!mutex_is_locked(&pdata->cmd_lock)) - poll_ret |= POLLOUT | POLLWRNORM; - - if (i2cp_adap_get_state(pdata) == I2CP_CTRLR_STATE_SHUTDN_REQ) - poll_ret |= POLLHUP; - - return poll_ret; -} - -static const struct file_operations i2cp_fileops = { - .owner = THIS_MODULE, - .open = i2cp_cdev_open, - .release = i2cp_cdev_release, - .read = i2cp_cdev_read, - .write = i2cp_cdev_write, - .poll = i2cp_cdev_poll, - .llseek = no_llseek, -}; - -static ssize_t i2cp_limit_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - int ret; - - ret = snprintf(buf, PAGE_SIZE, "%u\n", i2cp_limit); - if (ret >= PAGE_SIZE) - return -ERANGE; - return ret; -} - -static struct device_attribute i2cp_limit_dev_attr = { - .attr = { - .name = "limit", - .mode = 0444, - }, - .show = i2cp_limit_show, -}; - -static ssize_t i2cp_count_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - int count, ret; - struct i2cp_device *this_pseudo; - - this_pseudo = container_of(dev, struct i2cp_device, device); - - mutex_lock(&this_pseudo->counters.lock); - count = this_pseudo->counters.count; - mutex_unlock(&this_pseudo->counters.lock); - - ret = snprintf(buf, PAGE_SIZE, "%u\n", count); - if (ret >= PAGE_SIZE) - return -ERANGE; - return ret; -} - -static struct device_attribute i2cp_count_dev_attr = { - .attr = { - .name = "count", - .mode = 0444, - }, - .show = i2cp_count_show, -}; - -static struct attribute *i2cp_device_sysfs_attrs[] = { - &i2cp_limit_dev_attr.attr, - &i2cp_count_dev_attr.attr, - NULL, -}; - -static const struct attribute_group i2cp_device_sysfs_group = { - .attrs = i2cp_device_sysfs_attrs, -}; - -static const struct attribute_group *i2cp_device_sysfs_groups[] = { - &i2cp_device_sysfs_group, - NULL, -}; - -static void i2c_p_device_release(struct device *dev) -{ - struct i2cp_device *this_pseudo; - - this_pseudo = container_of(dev, struct i2cp_device, device); - kfree(this_pseudo->counters.all_controllers); - kfree(this_pseudo); -} - -static inline void i2c_p_class_destroy(void) -{ - struct class *class; - - class = i2cp_class; - i2cp_class = NULL; - class_destroy(class); -} - -static int __init i2cp_init(void) -{ - int ret = -1; - - if (i2cp_limit < I2CP_ADAPTERS_MIN || i2cp_limit > I2CP_ADAPTERS_MAX) { - pr_err("%s: i2cp_limit=%u, must be in range [" - STR(I2CP_ADAPTERS_MIN) ", " STR(I2CP_ADAPTERS_MAX) - "]\n", __func__, i2cp_limit); - return -EINVAL; - } - - i2cp_class = class_create(THIS_MODULE, I2CP_CLASS_NAME); - if (IS_ERR(i2cp_class)) - return PTR_ERR(i2cp_class); - - i2cp_class->dev_groups = i2cp_device_sysfs_groups; - - ret = alloc_chrdev_region(&i2cp_dev_num, I2CP_CDEV_BASEMINOR, - I2CP_CDEV_COUNT, I2CP_CHRDEV_NAME); - if (ret < 0) - goto fail_after_class_create; - - i2cp_device = kzalloc(sizeof(*i2cp_device), GFP_KERNEL); - if (!i2cp_device) { - ret = -ENOMEM; - goto fail_after_chrdev_register; - } - - i2cp_device->device.devt = i2cp_dev_num; - i2cp_device->device.class = i2cp_class; - i2cp_device->device.release = i2c_p_device_release; - device_initialize(&i2cp_device->device); - - ret = dev_set_name(&i2cp_device->device, "%s", I2CP_DEVICE_NAME); - if (ret < 0) - goto fail_after_device_init; - - mutex_init(&i2cp_device->counters.lock); - i2cp_device->counters.all_controllers = kcalloc(i2cp_limit, - sizeof(*i2cp_device->counters.all_controllers), GFP_KERNEL); - if (!i2cp_device->counters.all_controllers) { - ret = -ENOMEM; - goto fail_after_device_init; - } - - cdev_init(&i2cp_device->cdev, &i2cp_fileops); - i2cp_device->cdev.owner = THIS_MODULE; - - ret = cdev_device_add(&i2cp_device->cdev, &i2cp_device->device); - if (ret < 0) - goto fail_after_device_init; - - return 0; - - fail_after_device_init: - put_device(&i2cp_device->device); - fail_after_chrdev_register: - unregister_chrdev_region(i2cp_dev_num, I2CP_CDEV_COUNT); - fail_after_class_create: - i2c_p_class_destroy(); - return ret; -} - -static void __exit i2cp_exit(void) -{ - cdev_device_del(&i2cp_device->cdev, &i2cp_device->device); - put_device(&i2cp_device->device); - unregister_chrdev_region(i2cp_dev_num, I2CP_CDEV_COUNT); - i2c_p_class_destroy(); -} - -MODULE_AUTHOR("Matthew Blecker <matthewb@ihavethememo.net"); -MODULE_DESCRIPTION("Driver for userspace I2C adapter implementations."); -MODULE_LICENSE("GPL"); - -module_init(i2cp_init); -module_exit(i2cp_exit); diff --git a/extra/i2c_pseudo/install b/extra/i2c_pseudo/install deleted file mode 100755 index e66dcbd719..0000000000 --- a/extra/i2c_pseudo/install +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/sh -# -# This attempts to build and install the i2c-pseudo Linux kernel module. -# Installs a udev rule making i2c-pseudo devices read-write by users in plugdev. - -set -e -cd "$(dirname "$0")" - -make clean -make -ret=0 -sudo make modules_install || ret="$?" - -# Only install udev rule if plugdev group exists. -if getent group plugdev > /dev/null; then - sudo cp -iv 50-i2c-pseudo.rules /etc/udev/rules.d/ \ - || echo 1>&2 "NOTICE: Failed to copy udev rules file." -fi - -if [ "$ret" -eq 0 ]; then - make clean - sudo depmod -a - sudo modprobe i2c-pseudo - echo "SUCCESS: installed and loaded i2c-pseudo module" -else - echo 1>&2 "WARNING: make modules_install failed with exit status $ret." - echo 1>&2 "The module has not been installed for future reuse." - echo 1>&2 "Will still attempt to load the module, using insmod instead of "\ -"modprobe." - ret=0 - sudo insmod i2c-pseudo.ko || ret="$?" - if [ "$ret" -ne 0 ]; then - make clean - exit "$ret" - fi - echo "PARTIAL SUCCESS: loaded i2c-pseudo module (not installed)" - make clean -fi diff --git a/extra/lightbar/.gitignore b/extra/lightbar/.gitignore deleted file mode 100644 index 964154302a..0000000000 --- a/extra/lightbar/.gitignore +++ /dev/null @@ -1 +0,0 @@ -lightbar diff --git a/extra/lightbar/Makefile b/extra/lightbar/Makefile deleted file mode 100644 index ce84428869..0000000000 --- a/extra/lightbar/Makefile +++ /dev/null @@ -1,28 +0,0 @@ -# Copyright 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. - -PROG= lightbar -HEADERS= simulation.h -SRCS= main.c windows.c input.c ../../common/lightbar.c - -# comment this out if you don't have libreadline installed -HAS_GNU_READLINE=1 - -INCLUDE= -I. -I../../include -CFLAGS= -g -Wall -Werror -pthread ${INCLUDE} -DLIGHTBAR_SIMULATION -LDFLAGS = -lX11 -lxcb -lrt - -ifneq ($(HAS_GNU_READLINE),) -CFLAGS += -DHAS_GNU_READLINE -LDFLAGS += -lreadline -endif - -all: ${PROG} - -${PROG} : ${SRCS} ${HEADERS} Makefile - gcc ${CFLAGS} ${SRCS} ${LDFLAGS} -o ${PROG} - -.PHONY: clean -clean: - rm -f ${PROG} diff --git a/extra/lightbar/README b/extra/lightbar/README deleted file mode 100644 index 1862f922e4..0000000000 --- a/extra/lightbar/README +++ /dev/null @@ -1,39 +0,0 @@ -Lightbar simulator ------------------------------------------------------------------------------- - -Build with "make lightbar". The executable is "./lightbar". - -You may need to install libxcb1-dev or similar. - -This provides a simulation environment for the lightbar task, compiling -common/lightbar.c from the EC source, but faking the rest of the EC. - -The EC console is on stdin/stdout, delivering all input to the lightbar's -console command handler (so it prefixes any input with "lightbar"). The -lightbar itself is displayed in an X window. You can click in that window to -emulate changes to the battery level, AC connection, and brightness, all of -which are normally outside the lightbar task's direct control. - -The initial sequence is "S5". Try issuing the command "seq s3s0" to see -something more familiar. - - -Note: the Pixel lightbar circuitry has three modes of operation: - -Unpowered - - When the host CPU is off (S5/G3), all power to the lightbar and its - controller circuitry is lost. - -On - - When the host CPU is on (S0) or suspended (S3), the lightbar is powered - again. After every power loss, it will need to be reinitialized by calling - lb_init() before it can be used. - -Standby - - The lightbar controller ICs can turn off all the LED outputs to conserve - power. This is the initial state when power is applied. You can turn the - LEDs off manually by calling lb_off(). When suspended, the controller will - respond to commands, but the LEDs aren't lit. Turn them on with lb_on(). diff --git a/extra/lightbar/input.c b/extra/lightbar/input.c deleted file mode 100644 index e6c5485e39..0000000000 --- a/extra/lightbar/input.c +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 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. - */ -#include <stdint.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <time.h> - -#include "simulation.h" - -#ifdef HAS_GNU_READLINE -#include <readline/readline.h> -#include <readline/history.h> - -char *get_input(const char *prompt) -{ - static char *line; - - if (line) { - free(line); - line = 0; - } - - line = readline(prompt); - - if (line && *line) - add_history(line); - - return line; -} - -#else /* no readline */ - -char *get_input(const char *prompt) -{ - static char mybuf[80]; - char *got; - printf("%s", prompt); - got = fgets(mybuf, sizeof(mybuf), stdin); - return got; -} - -#endif /* HAS_GNU_READLINE */ - -void *entry_input(void *ptr) -{ - char *got, buf[80]; - char *str, *word, *saveptr; - int argc; - char *argv[40]; - int ret; - - do { - got = get_input("lightbar% "); - if (got) { - strcpy(buf, got); - argc = 0; - argv[argc++] = "lightbar"; - word = str = buf; - while (word && argc < ARRAY_SIZE(argv)) { - word = strtok_r(str, " \t\r\n", &saveptr); - if (word) - argv[argc++] = word; - str = 0; - } - argv[argc] = 0; - ret = fake_consolecmd_lightbar(argc, argv); - if (ret) - printf("ERROR %d\n", ret); - } - - } while (got); - - exit(0); - - return 0; -} diff --git a/extra/lightbar/main.c b/extra/lightbar/main.c deleted file mode 100644 index 5acf3d427a..0000000000 --- a/extra/lightbar/main.c +++ /dev/null @@ -1,329 +0,0 @@ -/* - * Copyright 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. - */ -#include <assert.h> -#include <errno.h> -#include <inttypes.h> -#include <pthread.h> -#include <stdint.h> -#include <string.h> -#include <stdio.h> -#include <stdlib.h> -#include <stdarg.h> -#include <time.h> - -#include "simulation.h" - -static void *(*thread_fns[])(void *) = { - entry_windows, - entry_lightbar, - entry_input, -}; - -int main(int argc, char *argv[]) -{ - int i; - pthread_t thread[ARRAY_SIZE(thread_fns)]; - - printf("\nLook at the README file.\n"); - printf("Click in the window.\n"); - printf("Type \"help\" for commands.\n\n"); - fflush(stdout); - - init_windows(); - - for (i = 0; i < ARRAY_SIZE(thread_fns); i++) - assert(0 == pthread_create(&thread[i], NULL, thread_fns[i], 0)); - - for (i = 0; i < ARRAY_SIZE(thread_fns); i++) - pthread_join(thread[i], NULL); - - return 0; -} - -void *entry_lightbar(void *ptr) -{ - lightbar_task(); - return 0; -} - -/****************************************************************************/ -/* Fake functions. We only have to implement enough for lightbar.c */ - -/* timespec uses nanoseconds */ -#define TS_USEC 1000L -#define TS_MSEC 1000000L -#define TS_SEC 1000000000L - -static void timespec_incr(struct timespec *v, time_t secs, long nsecs) -{ - v->tv_sec += secs; - /* The nanosecond sum won't overflow, but might have a carry. */ - v->tv_nsec += nsecs; - v->tv_sec += v->tv_nsec / TS_SEC; - v->tv_nsec %= TS_SEC; -} - - -static pthread_mutex_t task_mutex = PTHREAD_MUTEX_INITIALIZER; -static pthread_cond_t task_cond = PTHREAD_COND_INITIALIZER; -static uint32_t task_event; - -uint32_t task_wait_event(int timeout_us) -{ - struct timespec t; - uint32_t event; - - pthread_mutex_lock(&task_mutex); - - if (timeout_us > 0) { - clock_gettime(CLOCK_REALTIME, &t); - timespec_incr(&t, timeout_us / SECOND, timeout_us * TS_USEC); - - if (ETIMEDOUT == pthread_cond_timedwait(&task_cond, - &task_mutex, &t)) - task_event |= TASK_EVENT_TIMER; - } else { - pthread_cond_wait(&task_cond, &task_mutex); - } - - pthread_mutex_unlock(&task_mutex); - event = task_event; - task_event = 0; - return event; -} - -uint32_t task_set_event(task_id_t tskid, /* always LIGHTBAR */ - uint32_t event) -{ - pthread_mutex_lock(&task_mutex); - task_event = event; - pthread_cond_signal(&task_cond); - pthread_mutex_unlock(&task_mutex); - return 0; -} - - - -/* Stubbed functions */ - -void cprintf(int zero, const char *fmt, ...) -{ - va_list ap; - char *s; - char *newfmt = strdup(fmt); - - for (s = newfmt; *s; s++) - if (*s == '%' && s[1] == 'T') - *s = 'T'; - - va_start(ap, fmt); - vprintf(newfmt, ap); - va_end(ap); - - free(newfmt); -} - -void cprints(int zero, const char *fmt, ...) -{ - va_list ap; - - printf("[TT "); - va_start(ap, fmt); - vprintf(fmt, ap); - va_end(ap); - printf("]\n"); -} - -timestamp_t get_time(void) -{ - static struct timespec t_start; - struct timespec t; - timestamp_t ret; - - if (!t_start.tv_sec) - clock_gettime(CLOCK_REALTIME, &t_start); - clock_gettime(CLOCK_REALTIME, &t); - ret.val = (t.tv_sec - t_start.tv_sec) * SECOND + - (t.tv_nsec - t_start.tv_nsec) / TS_USEC; - return ret; -} - -/* We could implement these if we wanted to test their usage. */ -int system_add_jump_tag(uint16_t tag, int version, int size, const void *data) -{ - return 0; -} - -uint8_t *system_get_jump_tag(uint16_t tag, int *version, int *size) -{ - return 0; -} - -/* Copied from util/ectool.c */ -int lb_read_params_from_file(const char *filename, - struct lightbar_params_v1 *p) -{ - FILE *fp; - char buf[80]; - int val[4]; - int r = 1; - int line = 0; - int want, got; - int i; - - fp = fopen(filename, "rb"); - if (!fp) { - fprintf(stderr, "Can't open %s: %s\n", - filename, strerror(errno)); - return 1; - } - - /* We must read the correct number of params from each line */ -#define READ(N) do { \ - line++; \ - want = (N); \ - got = -1; \ - if (!fgets(buf, sizeof(buf), fp)) \ - goto done; \ - got = sscanf(buf, "%i %i %i %i", \ - &val[0], &val[1], &val[2], &val[3]); \ - if (want != got) \ - goto done; \ - } while (0) - - - /* Do it */ - READ(1); p->google_ramp_up = val[0]; - READ(1); p->google_ramp_down = val[0]; - READ(1); p->s3s0_ramp_up = val[0]; - READ(1); p->s0_tick_delay[0] = val[0]; - READ(1); p->s0_tick_delay[1] = val[0]; - READ(1); p->s0a_tick_delay[0] = val[0]; - READ(1); p->s0a_tick_delay[1] = val[0]; - READ(1); p->s0s3_ramp_down = val[0]; - READ(1); p->s3_sleep_for = val[0]; - READ(1); p->s3_ramp_up = val[0]; - READ(1); p->s3_ramp_down = val[0]; - READ(1); p->tap_tick_delay = val[0]; - READ(1); p->tap_gate_delay = val[0]; - READ(1); p->tap_display_time = val[0]; - - READ(1); p->tap_pct_red = val[0]; - READ(1); p->tap_pct_green = val[0]; - READ(1); p->tap_seg_min_on = val[0]; - READ(1); p->tap_seg_max_on = val[0]; - READ(1); p->tap_seg_osc = val[0]; - READ(3); - p->tap_idx[0] = val[0]; - p->tap_idx[1] = val[1]; - p->tap_idx[2] = val[2]; - - READ(2); - p->osc_min[0] = val[0]; - p->osc_min[1] = val[1]; - READ(2); - p->osc_max[0] = val[0]; - p->osc_max[1] = val[1]; - READ(2); - p->w_ofs[0] = val[0]; - p->w_ofs[1] = val[1]; - - READ(2); - p->bright_bl_off_fixed[0] = val[0]; - p->bright_bl_off_fixed[1] = val[1]; - - READ(2); - p->bright_bl_on_min[0] = val[0]; - p->bright_bl_on_min[1] = val[1]; - - READ(2); - p->bright_bl_on_max[0] = val[0]; - p->bright_bl_on_max[1] = val[1]; - - READ(3); - p->battery_threshold[0] = val[0]; - p->battery_threshold[1] = val[1]; - p->battery_threshold[2] = val[2]; - - READ(4); - p->s0_idx[0][0] = val[0]; - p->s0_idx[0][1] = val[1]; - p->s0_idx[0][2] = val[2]; - p->s0_idx[0][3] = val[3]; - - READ(4); - p->s0_idx[1][0] = val[0]; - p->s0_idx[1][1] = val[1]; - p->s0_idx[1][2] = val[2]; - p->s0_idx[1][3] = val[3]; - - READ(4); - p->s3_idx[0][0] = val[0]; - p->s3_idx[0][1] = val[1]; - p->s3_idx[0][2] = val[2]; - p->s3_idx[0][3] = val[3]; - - READ(4); - p->s3_idx[1][0] = val[0]; - p->s3_idx[1][1] = val[1]; - p->s3_idx[1][2] = val[2]; - p->s3_idx[1][3] = val[3]; - - for (i = 0; i < ARRAY_SIZE(p->color); i++) { - READ(3); - p->color[i].r = val[0]; - p->color[i].g = val[1]; - p->color[i].b = val[2]; - } - -#undef READ - - /* Yay */ - r = 0; -done: - if (r) - fprintf(stderr, "problem with line %d: wanted %d, got %d\n", - line, want, got); - fclose(fp); - return r; -} - -int lb_load_program(const char *filename, struct lightbar_program *prog) -{ - FILE *fp; - size_t got; - int rc; - - fp = fopen(filename, "rb"); - if (!fp) { - fprintf(stderr, "Can't open %s: %s\n", - filename, strerror(errno)); - return 1; - } - - rc = fseek(fp, 0, SEEK_END); - if (rc) { - fprintf(stderr, "Couldn't find end of file %s", - filename); - fclose(fp); - return 1; - } - rc = (int) ftell(fp); - if (rc > EC_LB_PROG_LEN) { - fprintf(stderr, "File %s is too long, aborting\n", filename); - fclose(fp); - return 1; - } - rewind(fp); - - memset(prog->data, 0, EC_LB_PROG_LEN); - got = fread(prog->data, 1, EC_LB_PROG_LEN, fp); - if (rc != got) - fprintf(stderr, "Warning: did not read entire file\n"); - prog->size = got; - fclose(fp); - return 0; -} diff --git a/extra/lightbar/programs/bad-decode-32.bin b/extra/lightbar/programs/bad-decode-32.bin deleted file mode 100644 index 1d5d0c6c75..0000000000 --- a/extra/lightbar/programs/bad-decode-32.bin +++ /dev/null @@ -1 +0,0 @@ -UUU
\ No newline at end of file diff --git a/extra/lightbar/programs/bad-decode-8.bin b/extra/lightbar/programs/bad-decode-8.bin Binary files differdeleted file mode 100644 index 8352675d67..0000000000 --- a/extra/lightbar/programs/bad-decode-8.bin +++ /dev/null diff --git a/extra/lightbar/programs/bad-jump.bin b/extra/lightbar/programs/bad-jump.bin deleted file mode 100644 index b2c29a0bbf..0000000000 --- a/extra/lightbar/programs/bad-jump.bin +++ /dev/null @@ -1 +0,0 @@ -?
\ No newline at end of file diff --git a/extra/lightbar/programs/bad-opcode.bin b/extra/lightbar/programs/bad-opcode.bin deleted file mode 100644 index 6b10f95843..0000000000 --- a/extra/lightbar/programs/bad-opcode.bin +++ /dev/null @@ -1 +0,0 @@ -Ã
\ No newline at end of file diff --git a/extra/lightbar/programs/green-pulse.bin b/extra/lightbar/programs/green-pulse.bin Binary files differdeleted file mode 100644 index 0fdab712e9..0000000000 --- a/extra/lightbar/programs/green-pulse.bin +++ /dev/null diff --git a/extra/lightbar/programs/green-pulse.lbs b/extra/lightbar/programs/green-pulse.lbs deleted file mode 100644 index bccf3e5c9a..0000000000 --- a/extra/lightbar/programs/green-pulse.lbs +++ /dev/null @@ -1,8 +0,0 @@ - set.1 {0,1,2,3}.end.g 0xff - delay.r 7813 - delay.w 2000000 -L0001: on - cycle.1 - off - wait - jump L0001 diff --git a/extra/lightbar/programs/infinite-jump.bin b/extra/lightbar/programs/infinite-jump.bin Binary files differdeleted file mode 100644 index 5407bf3ddf..0000000000 --- a/extra/lightbar/programs/infinite-jump.bin +++ /dev/null diff --git a/extra/lightbar/programs/infinite-jump.lbs b/extra/lightbar/programs/infinite-jump.lbs deleted file mode 100644 index 6174d7ffd4..0000000000 --- a/extra/lightbar/programs/infinite-jump.lbs +++ /dev/null @@ -1 +0,0 @@ -L0001: jump L0001 diff --git a/extra/lightbar/programs/konami.bin b/extra/lightbar/programs/konami.bin Binary files differdeleted file mode 100644 index f7abfdc4ee..0000000000 --- a/extra/lightbar/programs/konami.bin +++ /dev/null diff --git a/extra/lightbar/programs/konami.lbs b/extra/lightbar/programs/konami.lbs deleted file mode 100644 index c9fa8d697a..0000000000 --- a/extra/lightbar/programs/konami.lbs +++ /dev/null @@ -1,89 +0,0 @@ -# Konami code easter egg - delay.w 100000 - set.rgb {1,2}.end 0xff 0xff 0x00 - ramp.1 - wait - cycle.1 - wait - ramp.1 - wait - cycle.1 - wait - set.rgb {1,2}.end 0x00 0x00 0x00 - set.1 {0,3}.end.b 0xff - ramp.1 - wait - cycle.1 - wait - ramp.1 - wait - cycle.1 - wait - set.1 {0,3}.end.b 0x00 - set.1 {0,1}.end.r 0xff - ramp.1 - wait - cycle.1 - wait - set.1 {0,1}.end.r 0x00 - set.1 {2,3}.end.g 0xff - ramp.1 - wait - cycle.1 - wait - set.1 {2,3}.end.g 0x00 - set.1 {0,1}.end.r 0xff - ramp.1 - wait - cycle.1 - wait - set.1 {0,1}.end.r 0x00 - set.1 {2,3}.end.g 0xff - ramp.1 - wait - cycle.1 - wait - set.1 {2,3}.end.g 0x00 - set.rgb {0,2}.end 0x00 0xff 0xff - ramp.1 - wait - cycle.1 - wait - delay.w 50000 - wait - set.rgb {0,2}.end 0x00 0x00 0x00 - set.rgb {1,3}.end 0xff 0x00 0xff - ramp.1 - wait - wait - cycle.1 - wait - delay.w 100000 - wait - wait - set.rgb {0,1,2,3}.end 0xff 0xff 0xff - ramp.1 - wait - cycle.1 - wait - ramp.1 - wait - cycle.1 - wait - ramp.1 - wait - cycle.1 - wait - ramp.1 - wait - cycle.1 - wait - ramp.1 - wait - cycle.1 - wait - ramp.1 - wait - cycle.1 - wait - halt diff --git a/extra/lightbar/programs/rainbow-shift.bin b/extra/lightbar/programs/rainbow-shift.bin Binary files differdeleted file mode 100644 index a72c5b16d6..0000000000 --- a/extra/lightbar/programs/rainbow-shift.bin +++ /dev/null diff --git a/extra/lightbar/programs/rainbow-shift.lbs b/extra/lightbar/programs/rainbow-shift.lbs deleted file mode 100644 index e1cbcddc83..0000000000 --- a/extra/lightbar/programs/rainbow-shift.lbs +++ /dev/null @@ -1,8 +0,0 @@ -# The rainbow cycle program. - set.rgb {0,1,2,3}.end 0xff 0xff 0xff - set.rgb {0}.phase 0x00 0x55 0xaa - set.rgb {1}.phase 0x40 0x95 0xea - set.rgb {2}.phase 0x80 0xd5 0x2a - set.rgb {3}.phase 0xc0 0x15 0x6a - delay.r 7813 - cycle diff --git a/extra/lightbar/programs/red-green-blink.bin b/extra/lightbar/programs/red-green-blink.bin Binary files differdeleted file mode 100644 index 6bece444dd..0000000000 --- a/extra/lightbar/programs/red-green-blink.bin +++ /dev/null diff --git a/extra/lightbar/programs/red-green-blink.lbs b/extra/lightbar/programs/red-green-blink.lbs deleted file mode 100644 index d520b739bb..0000000000 --- a/extra/lightbar/programs/red-green-blink.lbs +++ /dev/null @@ -1,14 +0,0 @@ -# Blinks red and green with 1 second pauses. - set.rgb {0,1,2,3}.beg 0xff 0x00 0x00 - set.rgb {0,1,2,3}.end 0x00 0xff 0x00 - delay.w 250000 - delay.r 0 - cycle.1 - wait - ramp.1 - wait - cycle.1 - wait - ramp.1 - wait - halt diff --git a/extra/lightbar/programs/s0.bin b/extra/lightbar/programs/s0.bin Binary files differdeleted file mode 100644 index b20cecd8ee..0000000000 --- a/extra/lightbar/programs/s0.bin +++ /dev/null diff --git a/extra/lightbar/programs/s0.lbs b/extra/lightbar/programs/s0.lbs deleted file mode 100644 index 364c3d595c..0000000000 --- a/extra/lightbar/programs/s0.lbs +++ /dev/null @@ -1,22 +0,0 @@ -# S0 sequence: Google colors, unless battery is low. - set.rgb {0}.end 0x33 0x69 0xe8 - set.rgb {1}.end 0xd5 0x0f 0x25 - set.rgb {2}.end 0xee 0xb2 0x11 - set.rgb {3}.end 0x00 0x99 0x25 - delay.r 1250 - ramp.1 - set.1 {0,1,2,3}.beg.r 0xff - delay.r 2500 - delay.w 1000000 - wait - jump L0003 -L0001: swap - ramp.1 -L0002: wait -L0003: jbat L0004 L0002 - jump L0002 -L0004: swap - ramp.1 -L0005: wait - jbat L0005 L0001 - jump L0001 diff --git a/extra/lightbar/programs/s0s3.bin b/extra/lightbar/programs/s0s3.bin Binary files differdeleted file mode 100644 index d1cb8a4af1..0000000000 --- a/extra/lightbar/programs/s0s3.bin +++ /dev/null diff --git a/extra/lightbar/programs/s0s3.lbs b/extra/lightbar/programs/s0s3.lbs deleted file mode 100644 index ba141f338d..0000000000 --- a/extra/lightbar/programs/s0s3.lbs +++ /dev/null @@ -1,16 +0,0 @@ -# S0S3 sequence: Fade out, Google color ramp up/down. - get - delay.r 2000 - ramp.1 - swap - set.rgb {0}.end 0x33 0x69 0xe8 - set.rgb {1}.end 0xd5 0x0f 0x25 - set.rgb {2}.end 0xee 0xb2 0x11 - set.rgb {3}.end 0x00 0x99 0x25 - delay.r 1250 - ramp.1 - swap - delay.r 10000 - ramp.1 - off - halt diff --git a/extra/lightbar/programs/s3.bin b/extra/lightbar/programs/s3.bin Binary files differdeleted file mode 100644 index 7e487bb8c9..0000000000 --- a/extra/lightbar/programs/s3.bin +++ /dev/null diff --git a/extra/lightbar/programs/s3.lbs b/extra/lightbar/programs/s3.lbs deleted file mode 100644 index e8803a06bb..0000000000 --- a/extra/lightbar/programs/s3.lbs +++ /dev/null @@ -1,17 +0,0 @@ -# S3 sequence: Pulse red on low battery. - set.rgb {0,1,2,3}.end 0xff 0x00 0x00 - cycle.1 - delay.w 5000000 -L0001: off - wait - jcharge L0001 - jbat L0002 L0001 - jump L0001 -L0002: on - delay.r 1250 - ramp.1 - swap - delay.r 10000 - ramp.1 - swap - jump L0001 diff --git a/extra/lightbar/programs/s3s0.bin b/extra/lightbar/programs/s3s0.bin Binary files differdeleted file mode 100644 index b277752d25..0000000000 --- a/extra/lightbar/programs/s3s0.bin +++ /dev/null diff --git a/extra/lightbar/programs/s3s0.lbs b/extra/lightbar/programs/s3s0.lbs deleted file mode 100644 index 0cac96f208..0000000000 --- a/extra/lightbar/programs/s3s0.lbs +++ /dev/null @@ -1,11 +0,0 @@ -# S3S0 sequence: Google color ramp up/down. - set.rgb {0}.end 0x33 0x69 0xe8 - set.rgb {1}.end 0xd5 0x0f 0x25 - set.rgb {2}.end 0xee 0xb2 0x11 - set.rgb {3}.end 0x00 0x99 0x25 - delay.r 1250 - ramp.1 - swap - delay.r 10000 - ramp.1 - halt diff --git a/extra/lightbar/simulation.h b/extra/lightbar/simulation.h deleted file mode 100644 index 4df7b69411..0000000000 --- a/extra/lightbar/simulation.h +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright 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. - */ -#ifndef __EXTRA_SIMULATION_H -#define __EXTRA_SIMULATION_H - -#include <stdint.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <time.h> -#include <unistd.h> - -#include "lb_common.h" -#include "lightbar.h" - -/* Functions specific to our simulation environment */ -void *entry_windows(void *); -void *entry_input(void *); -void *entry_lightbar(void *); -void init_windows(void); -int lb_read_params_from_file(const char *filename, - struct lightbar_params_v1 *p); -int lb_load_program(const char *filename, struct lightbar_program *prog); -/* Interfaces to the EC code that we're encapsulating */ -void lightbar_task(void); -int fake_consolecmd_lightbar(int argc, char *argv[]); - -/* EC-specific configuration */ -#undef DEMO_MODE_DEFAULT -#define DEMO_MODE_DEFAULT 1 -#ifndef CONFIG_CONSOLE_CMDHELP -#define CONFIG_CONSOLE_CMDHELP -#endif -#ifndef CONFIG_LIGHTBAR_POWER_RAILS -#define CONFIG_LIGHTBAR_POWER_RAILS -#endif - - -/* Stuff that's too interleaved with the rest of the EC to just include */ - -/* Test an important condition at compile time, not run time */ -#define _BA1_(cond, line) \ - extern int __build_assertion_ ## line[1 - 2*!(cond)] \ - __attribute__ ((unused)) -#define _BA0_(c, x) _BA1_(c, x) -#define BUILD_ASSERT(cond) _BA0_(cond, __LINE__) - -#define BUILD_CHECK_INLINE(value, cond_true) ((value) / (!!(cond_true))) - -/* Number of elements in an array */ -#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) - -/* Non-standard standard library functions */ -void cprintf(int zero, const char *fmt, ...); -void cprints(int zero, const char *fmt, ...); -#define ccprintf(fmt...) cprintf(0, fmt) -#define strtoi strtol - -/* Task events */ -#define TASK_EVENT_CUSTOM_BIT(x) BUILD_CHECK_INLINE(BIT(x), BIT(x) & 0x0fffffff) -#define TASK_EVENT_I2C_IDLE 0x10000000 -#define TASK_EVENT_WAKE 0x20000000 -#define TASK_EVENT_MUTEX 0x40000000 -#define TASK_EVENT_TIMER 0x80000000 - -/* Time units in usecs */ -#define MSEC 1000 -#define SECOND 1000000 - -#define TASK_ID_LIGHTBAR 0 -#define CC_LIGHTBAR 0 - -/* Other definitions and structs */ -#define EC_SUCCESS 0 -#define EC_ERROR_INVAL 5 -#define EC_ERROR_PARAM1 11 -#define EC_ERROR_PARAM2 12 - -typedef int task_id_t; - -typedef union { - uint64_t val; - struct { - uint32_t lo; - uint32_t hi; - } le /* little endian words */; -} timestamp_t; - -struct host_cmd_handler_args { - const void *params; - void *response; - int response_size; -}; - -/* EC functions that we have to provide */ -uint32_t task_wait_event(int timeout_us); -uint32_t task_set_event(task_id_t tskid, uint32_t event); -timestamp_t get_time(void); -int system_add_jump_tag(uint16_t tag, int version, int size, const void *data); -uint8_t *system_get_jump_tag(uint16_t tag, int *version, int *size); - -/* Export unused static functions to avoid compiler warnings. */ -#define DECLARE_HOOK(X, fn, Y) \ - void fake_hook_##fn(void) { fn(); } - -#define DECLARE_HOST_COMMAND(X, fn, Y) \ - enum ec_status fake_hostcmd_##fn(struct host_cmd_handler_args *args) \ - { return fn(args); } - -#define DECLARE_CONSOLE_COMMAND(X, fn, Y...) \ - int fake_consolecmd_##X(int argc, char *argv[]) \ - { return fn(argc, argv); } - -#endif /* __EXTRA_SIMULATION_H */ diff --git a/extra/lightbar/windows.c b/extra/lightbar/windows.c deleted file mode 100644 index 115074363c..0000000000 --- a/extra/lightbar/windows.c +++ /dev/null @@ -1,293 +0,0 @@ -/* - * Copyright 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. - */ -#include <assert.h> -#include <pthread.h> -#include <stdint.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <time.h> -#include <xcb/xcb.h> - -#include "simulation.h" - -/*****************************************************************************/ -/* Window drawing stuff */ - -/* Dimensions - may change */ -static int win_w = 1024; -static int win_h = 32; - -static xcb_connection_t *c; -static xcb_screen_t *screen; -static xcb_drawable_t win; -static xcb_gcontext_t foreground; -static xcb_colormap_t colormap_id; - -static int fake_power; - -void init_windows(void) -{ - uint32_t mask = 0; - uint32_t values[2]; - - /* Open the connection to the X server */ - c = xcb_connect(NULL, NULL); - - /* Get the first screen */ - screen = xcb_setup_roots_iterator(xcb_get_setup(c)).data; - - /* Get a colormap */ - colormap_id = xcb_generate_id(c); - xcb_create_colormap(c, XCB_COLORMAP_ALLOC_NONE, - colormap_id, screen->root, screen->root_visual); - - /* Create foreground GC */ - foreground = xcb_generate_id(c); - mask = XCB_GC_FOREGROUND | XCB_GC_GRAPHICS_EXPOSURES; - values[0] = screen->white_pixel; - values[1] = 0; - xcb_create_gc(c, foreground, screen->root, mask, values); - - /* Create the window */ - win = xcb_generate_id(c); - mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK; - values[0] = screen->black_pixel; - values[1] = XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_BUTTON_PRESS; - xcb_create_window(c, /* Connection */ - XCB_COPY_FROM_PARENT, /* depth */ - win, /* window Id */ - screen->root, /* parent window */ - 0, 0, /* x, y */ - win_w, win_h, /* width, height */ - 10, /* border_width */ - XCB_WINDOW_CLASS_INPUT_OUTPUT, /* class */ - screen->root_visual, /* visual */ - mask, values); /* masks */ - - /* Map the window on the screen */ - xcb_map_window(c, win); - - /* We flush the request */ - xcb_flush(c); -} - -void cleanup(void) -{ - xcb_destroy_window(c, win); - xcb_free_gc(c, foreground); - xcb_free_colormap(c, colormap_id); - xcb_disconnect(c); -} - -/*****************************************************************************/ -/* Draw the lightbar elements */ - -/* xcb likes 16-bit colors */ -uint16_t leds[NUM_LEDS][3] = { - {0xffff, 0x0000, 0x0000}, - {0x0000, 0xffff, 0x0000}, - {0x0000, 0x0000, 0xffff}, - {0xffff, 0xffff, 0x0000}, -}; -pthread_mutex_t leds_mutex = PTHREAD_MUTEX_INITIALIZER; - -void change_gc_color(uint16_t red, uint16_t green, uint16_t blue) -{ - uint32_t mask = 0; - uint32_t values[2]; - xcb_alloc_color_reply_t *reply; - - reply = xcb_alloc_color_reply(c, - xcb_alloc_color(c, colormap_id, - red, green, blue), - NULL); - assert(reply); - - mask = XCB_GC_FOREGROUND; - values[0] = reply->pixel; - xcb_change_gc(c, foreground, mask, values); - free(reply); -} - -void update_window(void) -{ - xcb_segment_t segments[] = { - {0, 0, win_w, win_h}, - {0, win_h, win_w, 0}, - }; - xcb_rectangle_t rect; - int w = win_w / NUM_LEDS; - int i; - uint16_t copyleds[NUM_LEDS][3]; - - if (fake_power) { - pthread_mutex_lock(&leds_mutex); - memcpy(copyleds, leds, sizeof(leds)); - pthread_mutex_unlock(&leds_mutex); - - for (i = 0; i < NUM_LEDS; i++) { - rect.x = i * w; - rect.y = 0; - rect.width = w; - rect.height = win_h; - - change_gc_color(copyleds[i][0], - copyleds[i][1], - copyleds[i][2]); - - xcb_poly_fill_rectangle(c, win, foreground, 1, &rect); - } - } else { - rect.x = 0; - rect.y = 0; - rect.width = win_w; - rect.height = win_h; - - change_gc_color(0, 0, 0); - xcb_poly_fill_rectangle(c, win, foreground, 1, &rect); - - change_gc_color(0x8080, 0, 0); - - for (i = 0; i < NUM_LEDS; i++) { - segments[0].x1 = i * w; - segments[0].y1 = 0; - segments[0].x2 = segments[0].x1 + w; - segments[0].y2 = win_h; - segments[1].x1 = segments[0].x1; - segments[1].y1 = win_h; - segments[1].x2 = segments[0].x2; - segments[1].y2 = 0; - xcb_poly_segment(c, win, foreground, 2, segments); - } - } - - xcb_flush(c); -} - -void setrgb(int led, int red, int green, int blue) -{ - led %= NUM_LEDS; - - pthread_mutex_lock(&leds_mutex); - leds[led][0] = red << 8 | red; - leds[led][1] = green << 8 | green; - leds[led][2] = blue << 8 | blue; - pthread_mutex_unlock(&leds_mutex); - - update_window(); -} - -/*****************************************************************************/ -/* lb_common stubs */ - - - -/* Brightness serves no purpose here. It's automatic on the Chromebook. */ -static int brightness = 0xc0; -void lb_set_brightness(unsigned int newval) -{ - brightness = newval; -} -uint8_t lb_get_brightness(void) -{ - return brightness; -} - -void lb_set_rgb(unsigned int led, int red, int green, int blue) -{ - int i; - if (led >= NUM_LEDS) - for (i = 0; i < NUM_LEDS; i++) - setrgb(i, red, green, blue); - else - setrgb(led, red, green, blue); -} - -int lb_get_rgb(unsigned int led, uint8_t *red, uint8_t *green, uint8_t *blue) -{ - led %= NUM_LEDS; - pthread_mutex_lock(&leds_mutex); - *red = leds[led][0]; - *green = leds[led][1]; - *blue = leds[led][2]; - pthread_mutex_unlock(&leds_mutex); - return 0; -} - -void lb_init(void) -{ - if (fake_power) - lb_set_rgb(NUM_LEDS, 0, 0, 0); -}; -void lb_off(void) -{ - fake_power = 0; - update_window(); -}; -void lb_on(void) -{ - fake_power = 1; - update_window(); -}; -void lb_hc_cmd_dump(struct ec_response_lightbar *out) -{ - printf("lightbar is %s\n", fake_power ? "on" : "off"); - memset(out, fake_power, sizeof(*out)); -}; -void lb_hc_cmd_reg(const struct ec_params_lightbar *in) { }; - -int lb_power(int enabled) -{ - return fake_power; -} - - -/*****************************************************************************/ -/* Event handling stuff */ - -void *entry_windows(void *ptr) -{ - xcb_generic_event_t *e; - xcb_expose_event_t *ev; - xcb_button_press_event_t *bv; - int chg = 1; - - while ((e = xcb_wait_for_event(c))) { - - switch (e->response_type & ~0x80) { - case XCB_EXPOSE: - ev = (xcb_expose_event_t *)e; - if (win_w != ev->width || win_h != ev->height) { - win_w = ev->width; - win_h = ev->height; - } - update_window(); - break; - case XCB_BUTTON_PRESS: - bv = (xcb_button_press_event_t *)e; - switch (bv->detail) { - case 1: - demo_battery_level(-1); - break; - case 3: - demo_battery_level(+1); - break; - case 2: - chg = !chg; - demo_is_charging(chg); - break; - } - break; - } - - free(e); - } - - cleanup(); - exit(0); - return 0; -} diff --git a/extra/rma_reset/.gitignore b/extra/rma_reset/.gitignore deleted file mode 100644 index ae297b7dc0..0000000000 --- a/extra/rma_reset/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -base32.o -curve25519-generic.o -curve25519.o -rma_reset -sha256.o diff --git a/extra/rma_reset/Makefile b/extra/rma_reset/Makefile deleted file mode 100644 index 4a640c5b4c..0000000000 --- a/extra/rma_reset/Makefile +++ /dev/null @@ -1,55 +0,0 @@ -# Copyright 2017 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. - -CC ?= gcc -PROGRAM := rma_reset -SOURCE := $(PROGRAM).c -OBJS := curve25519.o curve25519-generic.o sha256.o base32.o -LIBS := -LFLAGS := -CFLAGS := -std=gnu99 \ - -Wall \ - -Werror \ - -Wpointer-arith \ - -Wcast-align \ - -Wcast-qual \ - -Wundef \ - -Wsign-compare \ - -Wredundant-decls \ - -Wmissing-declarations - -ifeq ($(DEBUG),1) -CFLAGS += -g -O0 -else -CFLAGS += -O3 -endif -# -# Add libusb-1.0 required flags -# -INCLUDE=-I. -I../../ -I../../fuzz -I../../test -I../../include -I../../chip/host -LIBS += -lcrypto -lssl -CFLAGS += ${INCLUDE} -STANDALONE_FLAGS=${INCLUDE} -ffreestanding -fno-builtin \ - -Ibuiltin/ -D"__keep= " - -$(PROGRAM): $(SOURCE) $(OBJS) Makefile - $(CC) $(CFLAGS) $(SOURCE) $(LFLAGS) $(LIBS) $(OBJS) -o $@ - -curve25519-generic.o: ../../common/curve25519-generic.c - $(CC) $(STANDALONE_FLAGS) -c -o curve25519-generic.o \ - ../../common/curve25519-generic.c - -curve25519.o: ../../common/curve25519.c - $(CC) $(STANDALONE_FLAGS) -c -o curve25519.o ../../common/curve25519.c - -sha256.o: ../../common/sha256.c - $(CC) $(STANDALONE_FLAGS) -c -o sha256.o ../../common/sha256.c - -base32.o: ../../common/base32.c - $(CC) $(STANDALONE_FLAGS) -c -o base32.o ../../common/base32.c - -.PHONY: clean - -clean: - rm -rf *.o $(PROGRAM) *~ diff --git a/extra/rma_reset/board.h b/extra/rma_reset/board.h deleted file mode 100644 index f969ad0c56..0000000000 --- a/extra/rma_reset/board.h +++ /dev/null @@ -1,11 +0,0 @@ -/* Copyright 2017 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. - */ - -#ifndef __CROS_EC_BOARD_H -#define __CROS_EC_BOARD_H - -#define CONFIG_RNG - -#endif /* __CROS_EC_BOARD_H */ diff --git a/extra/rma_reset/rma_reset.c b/extra/rma_reset/rma_reset.c deleted file mode 100644 index fe1eb5e909..0000000000 --- a/extra/rma_reset/rma_reset.c +++ /dev/null @@ -1,707 +0,0 @@ -/* Copyright 2017 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. - */ - -#include <ctype.h> -#include <endian.h> -#include <errno.h> -#include <fcntl.h> -#include <getopt.h> -#include <openssl/bn.h> -#include <openssl/ec.h> -#include <openssl/obj_mac.h> -#include <openssl/rand.h> -#include <stdint.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <time.h> -#include <unistd.h> - -#include "rma_auth.h" -#include "curve25519.h" -#include "sha256.h" -#include "base32.h" - -#define EC_COORDINATE_SZ 32 -#define EC_PRIV_KEY_SZ 32 -#define EC_P256_UNCOMPRESSED_PUB_KEY_SZ (EC_COORDINATE_SZ * 2 + 1) -#define EC_P256_COMPRESSED_PUB_KEY_SZ (EC_COORDINATE_SZ + 1) - -#define SERVER_ADDRESS \ - "https://www.google.com/chromeos/partner/console/cr50reset/request" - -/* Test server keys for x25519 and p256 curves. */ -static const uint8_t rma_test_server_x25519_public_key[] = { - 0x03, 0xae, 0x2d, 0x2c, 0x06, 0x23, 0xe0, 0x73, - 0x0d, 0xd3, 0xb7, 0x92, 0xac, 0x54, 0xc5, 0xfd, - 0x7e, 0x9c, 0xf0, 0xa8, 0xeb, 0x7e, 0x2a, 0xb5, - 0xdb, 0xf4, 0x79, 0x5f, 0x8a, 0x0f, 0x28, 0x3f -}; - -static const uint8_t rma_test_server_x25519_private_key[] = { - 0x47, 0x3b, 0xa5, 0xdb, 0xc4, 0xbb, 0xd6, 0x77, - 0x20, 0xbd, 0xd8, 0xbd, 0xc8, 0x7a, 0xbb, 0x07, - 0x03, 0x79, 0xba, 0x7b, 0x52, 0x8c, 0xec, 0xb3, - 0x4d, 0xaa, 0x69, 0xf5, 0x65, 0xb4, 0x31, 0xad -}; - -#define RMA_TEST_SERVER_X25519_KEY_ID 0x10 -#define RMA_PROD_SERVER_X25519_KEY_ID 0 - -/* - * P256 curve keys, generated using openssl as follows: - * - * openssl ecparam -name prime256v1 -genkey -out key.pem - * openssl ec -in key.pem -text -noout - */ -static const uint8_t rma_test_server_p256_private_key[] = { - 0x54, 0xb0, 0x82, 0x92, 0x54, 0x92, 0xfc, 0x4a, - 0xa7, 0x6b, 0xea, 0x8f, 0x30, 0xcc, 0xf7, 0x3d, - 0xa2, 0xf6, 0xa7, 0xad, 0xf0, 0xec, 0x7d, 0xe9, - 0x26, 0x75, 0xd1, 0xec, 0xde, 0x20, 0x8f, 0x81 -}; - -/* - * P256 public key in full form, x and y coordinates with a single byte - * prefix, 65 bytes total. - */ -static const uint8_t rma_test_server_p256_public_key[] = { - 0x04, 0xe7, 0xbe, 0x37, 0xaa, 0x68, 0xca, 0xcc, - 0x68, 0xf4, 0x8c, 0x56, 0x65, 0x5a, 0xcb, 0xf8, - 0xf4, 0x65, 0x3c, 0xd3, 0xc6, 0x1b, 0xae, 0xd6, - 0x51, 0x7a, 0xcc, 0x00, 0x8d, 0x59, 0x6d, 0x1b, - 0x0a, 0x66, 0xe8, 0x68, 0x5e, 0x6a, 0x82, 0x19, - 0x81, 0x76, 0x84, 0x92, 0x7f, 0x8d, 0xb2, 0xbe, - 0xf5, 0x39, 0x50, 0xd5, 0xfe, 0xee, 0x00, 0x67, - 0xcf, 0x40, 0x5f, 0x68, 0x12, 0x83, 0x4f, 0xa4, - 0x35 -}; - -#define RMA_TEST_SERVER_P256_KEY_ID 0x20 -#define RMA_PROD_SERVER_P256_KEY_ID 0x01 - -/* Default values which can change based on command line arguments. */ -static uint8_t server_key_id = RMA_TEST_SERVER_X25519_KEY_ID; -static uint8_t board_id[4] = {'Z', 'Z', 'C', 'R'}; -static uint8_t device_id[8] = {'T', 'H', 'X', 1, 1, 3, 8, 0xfe}; -static uint8_t hw_id[20] = "TESTSAMUS1234"; - -static char challenge[RMA_CHALLENGE_BUF_SIZE]; -static char authcode[RMA_AUTHCODE_BUF_SIZE]; - -static char *progname; -static char *short_opts = "a:b:c:d:hpk:tw:"; -static const struct option long_opts[] = { - /* name hasarg *flag val */ - {"auth_code", 1, NULL, 'a'}, - {"board_id", 1, NULL, 'b'}, - {"challenge", 1, NULL, 'c'}, - {"device_id", 1, NULL, 'd'}, - {"help", 0, NULL, 'h'}, - {"hw_id", 1, NULL, 'w'}, - {"key_id", 1, NULL, 'k'}, - {"p256", 0, NULL, 'p'}, - {"test", 0, NULL, 't'}, - {}, -}; - -void panic_assert_fail(const char *fname, int linenum); -void rand_bytes(void *buffer, size_t len); -int safe_memcmp(const void *s1, const void *s2, size_t size); - -void panic_assert_fail(const char *fname, int linenum) -{ - printf("\nASSERTION FAILURE at %s:%d\n", fname, linenum); -} - -int safe_memcmp(const void *s1, const void *s2, size_t size) -{ - const uint8_t *us1 = s1; - const uint8_t *us2 = s2; - int result = 0; - - if (size == 0) - return 0; - - while (size--) - result |= *us1++ ^ *us2++; - - return result != 0; -} - -void rand_bytes(void *buffer, size_t len) -{ - RAND_bytes(buffer, len); -} - -/* - * Generate a p256 key pair and calculate the shared secret based on our - * private key and the server public key. - * - * Return the X coordinate of the generated public key and the shared secret. - * - * @pub_key - the compressed public key without the prefix; by convention - * between RMA client and server the generated pubic key would - * always have prefix of 0x03, (the Y coordinate value is odd), so - * it is omitted from the key blob, which allows to keep the blob - * size at 32 bytes. - * @secret_seed - the product of multiplying of the server point by our - * private key, only the 32 bytes of X coordinate are returned. - */ -static void p256_key_and_secret_seed(uint8_t pub_key[32], - uint8_t secret_seed[32]) -{ - const EC_GROUP *group; - EC_KEY *key; - EC_POINT *pub; - EC_POINT *secret_point; - uint8_t buf[EC_P256_UNCOMPRESSED_PUB_KEY_SZ]; - - /* Prepare structures to operate on. */ - key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); - group = EC_KEY_get0_group(key); - pub = EC_POINT_new(group); - - /* - * We might have to try multiple times, until the Y coordinate is an - * odd value as required by convention. - */ - do { - EC_KEY_generate_key(key); - - /* Extract public key into an octal array. */ - EC_POINT_point2oct(group, EC_KEY_get0_public_key(key), - POINT_CONVERSION_UNCOMPRESSED, - buf, sizeof(buf), NULL); - - /* If Y coordinate is an odd value, we are done. */ - } while (!(buf[sizeof(buf) - 1] & 1)); - - /* Copy X coordinate out. */ - memcpy(pub_key, buf + 1, 32); - - /* - * We have our private key and the server's point coordinates (aka - * server public key). Let's multiply the coordinates by our private - * key to get the shared secret. - */ - - /* Load raw public key into the point structure. */ - EC_POINT_oct2point(group, pub, rma_test_server_p256_public_key, - sizeof(rma_test_server_p256_public_key), NULL); - - secret_point = EC_POINT_new(group); - - /* Multiply server public key by our private key. */ - EC_POINT_mul(group, secret_point, 0, pub, - EC_KEY_get0_private_key(key), 0); - - /* Pull the result back into the octal buffer. */ - EC_POINT_point2oct(group, secret_point, POINT_CONVERSION_UNCOMPRESSED, - buf, sizeof(buf), NULL); - - /* - * Copy X coordinate into the output to use as the shared secret - * seed. - */ - memcpy(secret_seed, buf + 1, 32); - - /* release resources */ - EC_KEY_free(key); - EC_POINT_free(pub); - EC_POINT_free(secret_point); -} - -/* - * When imitating server side, calculate the secret value given the client's - * compressed public key (X coordinate only with 0x03 prefix implied) and - * knowing our (server) private key. - * - * @secret - array to return the X coordinate of the calculated point. - * @raw_pub_key - X coordinate of the point calculated by the client, 0x03 - * prefix implied. - */ -static void p256_calculate_secret(uint8_t secret[32], - const uint8_t raw_pub_key[32]) -{ - uint8_t raw_pub_key_x[EC_P256_COMPRESSED_PUB_KEY_SZ]; - EC_KEY *key; - const uint8_t *kp = raw_pub_key_x; - EC_POINT *secret_point; - const EC_GROUP *group; - BIGNUM *priv; - uint8_t buf[EC_P256_UNCOMPRESSED_PUB_KEY_SZ]; - - /* Express server private key as a BN. */ - priv = BN_new(); - BN_bin2bn(rma_test_server_p256_private_key, EC_PRIV_KEY_SZ, priv); - - /* - * Populate a p256 key structure based on the compressed - * representation of the client's public key. - */ - raw_pub_key_x[0] = 3; /* Implied by convention. */ - memcpy(raw_pub_key_x + 1, raw_pub_key, sizeof(raw_pub_key_x) - 1); - key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); - group = EC_KEY_get0_group(key); - key = o2i_ECPublicKey(&key, &kp, sizeof(raw_pub_key_x)); - - /* This is where the multiplication result will go. */ - secret_point = EC_POINT_new(group); - - /* Multiply client's point by our private key. */ - EC_POINT_mul(group, secret_point, 0, - EC_KEY_get0_public_key(key), - priv, 0); - - /* Pull the result back into the octal buffer. */ - EC_POINT_point2oct(group, secret_point, POINT_CONVERSION_UNCOMPRESSED, - buf, sizeof(buf), NULL); - - /* Copy X coordinate into the output to use as the shared secret. */ - memcpy(secret, buf + 1, 32); -} - -static int rma_server_side(const char *generated_challenge) -{ - int key_id, version; - uint8_t secret[32]; - uint8_t hmac[32]; - struct rma_challenge c; - uint8_t *cptr = (uint8_t *)&c; - - /* Convert the challenge back into binary */ - if (base32_decode(cptr, 8 * sizeof(c), generated_challenge, 9) != - 8 * sizeof(c)) { - printf("Error decoding challenge\n"); - return -1; - } - - version = RMA_CHALLENGE_GET_VERSION(c.version_key_id); - key_id = RMA_CHALLENGE_GET_KEY_ID(c.version_key_id); - printf("Challenge: %s\n", generated_challenge); - printf("Version: %d\n", version); - printf("Server KeyID: %d\n", key_id); - - if (version != RMA_CHALLENGE_VERSION) - printf("Unsupported challenge version %d\n", version); - - /* Calculate the shared secret, use curve based on the key ID. */ - switch (key_id) { - case RMA_PROD_SERVER_X25519_KEY_ID: - printf("Unsupported Prod KeyID %d\n", key_id); - case RMA_TEST_SERVER_X25519_KEY_ID: - X25519(secret, rma_test_server_x25519_private_key, - c.device_pub_key); - break; - case RMA_PROD_SERVER_P256_KEY_ID: - printf("Unsupported Prod KeyID %d\n", key_id); - case RMA_TEST_SERVER_P256_KEY_ID: - p256_calculate_secret(secret, c.device_pub_key); - break; - default: - printf("Unknown KeyID %d\n", key_id); - return 1; - } - - /* - * Auth code is a truncated HMAC of the ephemeral public key, BoardID, - * and DeviceID. - */ - hmac_SHA256(hmac, secret, sizeof(secret), cptr + 1, sizeof(c) - 1); - if (base32_encode(authcode, RMA_AUTHCODE_BUF_SIZE, - hmac, RMA_AUTHCODE_CHARS * 5, 0)) { - printf("Error encoding auth code\n"); - return -1; - } - printf("Authcode: %s\n", authcode); - - return 0; -}; - -static int rma_create_test_challenge(int p256_mode) -{ - uint8_t temp[32]; /* Private key or HMAC */ - uint8_t secret_seed[32]; - struct rma_challenge c; - uint8_t *cptr = (uint8_t *)&c; - uint32_t bid; - - /* Clear the current challenge and authcode, if any */ - memset(challenge, 0, sizeof(challenge)); - memset(authcode, 0, sizeof(authcode)); - - memset(&c, 0, sizeof(c)); - c.version_key_id = RMA_CHALLENGE_VKID_BYTE( - RMA_CHALLENGE_VERSION, server_key_id); - - memcpy(&bid, board_id, sizeof(bid)); - bid = be32toh(bid); - memcpy(c.board_id, &bid, sizeof(c.board_id)); - - memcpy(c.device_id, device_id, sizeof(c.device_id)); - - if (p256_mode) { - p256_key_and_secret_seed(c.device_pub_key, secret_seed); - } else { - /* Calculate a new ephemeral key pair */ - X25519_keypair(c.device_pub_key, temp); - /* Calculate the shared secret seed. */ - X25519(secret_seed, temp, rma_test_server_x25519_public_key); - } - - /* Encode the challenge */ - if (base32_encode(challenge, sizeof(challenge), cptr, 8 * sizeof(c), 9)) - return 1; - - /* - * Auth code is a truncated HMAC of the ephemeral public key, BoardID, - * and DeviceID. Those are all in the right order in the challenge - * struct, after the version/key id byte. - */ - hmac_SHA256(temp, secret_seed, sizeof(secret_seed), - cptr + 1, sizeof(c) - 1); - if (base32_encode(authcode, sizeof(authcode), temp, - RMA_AUTHCODE_CHARS * 5, 0)) - return 1; - - return 0; -} - -int rma_try_authcode(const char *code) -{ - return safe_memcmp(authcode, code, RMA_AUTHCODE_CHARS); -} - -static void dump_key(const char *title, const uint8_t *key, size_t key_size) -{ - size_t i; - const int bytes_per_line = 8; - - printf("\n\n\%s\n", title); - for (i = 0; i < key_size; i++) - printf("%02x%c", key[i], ((i + 1) % bytes_per_line) ? ' ':'\n'); - - if (i % bytes_per_line) - printf("\n"); -} - -static void print_params(int p_flag) -{ - int i; - const uint8_t *priv_key; - const uint8_t *pub_key; - int key_id; - size_t pub_key_size; - - printf("\nBoard Id:\n"); - for (i = 0; i < 4; i++) - printf("%c ", board_id[i]); - - printf("\n\nDevice Id:\n"); - for (i = 0; i < 3; i++) - printf("%c ", device_id[i]); - for (i = 3; i < 8; i++) - printf("%02x ", device_id[i]); - - if (p_flag) { - priv_key = rma_test_server_p256_private_key; - pub_key = rma_test_server_p256_public_key; - pub_key_size = sizeof(rma_test_server_p256_public_key); - key_id = RMA_TEST_SERVER_P256_KEY_ID; - } else { - priv_key = rma_test_server_x25519_private_key; - pub_key = rma_test_server_x25519_public_key; - pub_key_size = sizeof(rma_test_server_x25519_public_key); - key_id = RMA_TEST_SERVER_X25519_KEY_ID; - } - - printf("\n\nServer Key Id:\n"); - printf("%02x", key_id); - - /* Both private keys are of the same size */ - dump_key("Server Private Key:", priv_key, EC_PRIV_KEY_SZ); - dump_key("Server Public Key:", pub_key, pub_key_size); - - printf("\nChallenge:\n"); - for (i = 0; i < RMA_CHALLENGE_CHARS; i++) { - printf("%c", challenge[i]); - if (((i + 1) % 5) == 0) - printf(" "); - if (((i + 1) % 40) == 0) - printf("\n"); - } - - printf("\nAuthorization Code:\n"); - for (i = 0; i < RMA_AUTHCODE_BUF_SIZE; i++) - printf("%c", authcode[i]); - - printf("\n\nChallenge String:\n"); - printf("%s?challenge=", SERVER_ADDRESS); - for (i = 0; i < RMA_CHALLENGE_CHARS; i++) - printf("%c", challenge[i]); - printf("&hwid=%s\n", hw_id); - - printf("\n"); -} - -static void usage(void) -{ - printf("\nUsage: %s [--p256] --key_id <arg> --board_id <arg> " - "--device_id <arg> --hw_id <arg> |\n" - " --auth_code <arg> |\n" - " --challenge <arg>\n" - "\n" - "This is used to generate the cr50 or server responses for rma " - "open.\n" - "The cr50 side can be used to generate a challenge response " - "and sends authoriztion code to reset device.\n" - "The server side can generate an authcode from cr50's " - "rma challenge.\n" - "\n" - " -c,--challenge The challenge generated by cr50\n" - " -k,--key_id Index of the server private key\n" - " -b,--board_id BoardID type field\n" - " -d,--device_id Device-unique identifier\n" - " -a,--auth_code Reset authorization code\n" - " -w,--hw_id Hardware id\n" - " -h,--help Show this message\n" - " -p,--p256 Use prime256v1 curve instead of x25519\n" - " -t,--test " - "Generate challenge using default test inputs\n" - "\n", progname); -} - -static int atoh(char *v) -{ - char hn; - char ln; - - hn = toupper(*v); - ln = toupper(*(v + 1)); - - hn -= (isdigit(hn) ? '0' : '7'); - ln -= (isdigit(ln) ? '0' : '7'); - - if ((hn < 0 || hn > 0xf) || (ln < 0 || ln > 0xf)) - return 0; - - return (hn << 4) | ln; -} - -static int set_server_key_id(char *id) -{ - /* verify length */ - if (strlen(id) != 2) - return 1; - - /* verify digits */ - if (!isxdigit(*id) || !isxdigit(*(id+1))) - return 1; - - server_key_id = atoh(id); - - return 0; -} - -static int set_board_id(char *id) -{ - int i; - - /* verify length */ - if (strlen(id) != 8) - return 1; - - /* verify digits */ - for (i = 0; i < 8; i++) - if (!isxdigit(*(id + i))) - return 1; - - for (i = 0; i < 4; i++) - board_id[i] = atoh((id + (i*2))); - - return 0; -} - -static int set_device_id(char *id) -{ - int i; - - /* verify length */ - if (strlen(id) != 16) - return 1; - - for (i = 0; i < 16; i++) - if (!isxdigit(*(id + i))) - return 1; - - for (i = 0; i < 8; i++) - device_id[i] = atoh((id + (i*2))); - - return 0; -} - -static int set_hw_id(char *id) -{ - int i; - int len; - - len = strlen(id); - if (len > 20) - len = 20; - - for (i = 0; i < 20; i++) - hw_id[i] = *(id + i); - - return 0; -} - -static int set_auth_code(char *code) -{ - int i; - - if (strlen(code) != 8) - return 1; - - for (i = 0; i < 8; i++) - authcode[i] = *(code + i); - authcode[i] = 0; - - return 0; -} - -int main(int argc, char **argv) -{ - int a_flag = 0; - int b_flag = 0; - int d_flag = 0; - int k_flag = 0; - int p_flag = 0; - int t_flag = 0; - int w_flag = 0; - int i; - - progname = strrchr(argv[0], '/'); - if (progname) - progname++; - else - progname = argv[0]; - - opterr = 0; - while ((i = getopt_long(argc, argv, short_opts, long_opts, 0)) != -1) { - switch (i) { - case 't': - t_flag = 1; - break; - case 'c': - return rma_server_side(optarg); - case 'k': - if (set_server_key_id(optarg)) { - printf("Malformed key id\n"); - return 1; - } - k_flag = 1; - break; - case 'b': - if (set_board_id(optarg)) { - printf("Malformed board id\n"); - return 1; - } - b_flag = 1; - break; - case 'd': - if (set_device_id(optarg)) { - printf("Malformed device id\n"); - return 1; - } - d_flag = 1; - break; - case 'a': - if (set_auth_code(optarg)) { - printf("Malformed authorization code\n"); - return 1; - } - a_flag = 1; - break; - case 'w': - if (set_hw_id(optarg)) { - printf("Malformed hardware id\n"); - return 1; - } - w_flag = 1; - break; - case 'h': - usage(); - return 0; - case 0: /* auto-handled option */ - break; - case '?': - if (optopt) - printf("Unrecognized option: -%c\n", optopt); - else - printf("Unrecognized option: %s\n", - argv[optind - 1]); - break; - case ':': - printf("Missing argument to %s\n", argv[optind - 1]); - break; - case 'p': - p_flag = 1; - server_key_id = RMA_TEST_SERVER_P256_KEY_ID; - break; - default: - printf("Internal error at %s:%d\n", __FILE__, __LINE__); - return 1; - } - } - - if (a_flag) { - FILE *acode; - char verify_authcode[RMA_AUTHCODE_BUF_SIZE]; - int rv; - - acode = fopen("/tmp/authcode", "r"); - if (acode == NULL) { - printf("Please generate challenge\n"); - return 1; - } - - rv = fread(verify_authcode, 1, RMA_AUTHCODE_BUF_SIZE, acode); - if (rv != RMA_AUTHCODE_BUF_SIZE) { - printf("Error reading saved authcode\n"); - return 1; - } - if (strcmp(verify_authcode, authcode) == 0) - printf("Code Accepted\n"); - else - printf("Invalid Code\n"); - - } else { - if (!t_flag) { /* Use default values */ - if (!k_flag || !b_flag || !d_flag || !w_flag) { - printf("server-side: Flag -c is mandatory\n"); - printf("cr50-side: Flags -k, -b, -d, and -w " - "are mandatory\n"); - return 1; - } - } - - rma_create_test_challenge(p_flag); - - { - FILE *acode; - - acode = fopen("/tmp/authcode", "w"); - if (acode < 0) - return 1; - fwrite(authcode, 1, RMA_AUTHCODE_BUF_SIZE, acode); - fclose(acode); - } - - print_params(p_flag); - } - - return 0; -} diff --git a/extra/sps_errs/.gitignore b/extra/sps_errs/.gitignore deleted file mode 100644 index ea17491321..0000000000 --- a/extra/sps_errs/.gitignore +++ /dev/null @@ -1 +0,0 @@ -prog diff --git a/extra/sps_errs/Makefile b/extra/sps_errs/Makefile deleted file mode 100644 index 12224ad803..0000000000 --- a/extra/sps_errs/Makefile +++ /dev/null @@ -1,37 +0,0 @@ -# Copyright 2015 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. - -# Use your own libmpsse if you want, but we're going to use the files -# that are part of the Chromium OS trunks_client program. -PLATFORM2 = ../../../../platform2 -MPSSE_DIR = $(PLATFORM2)/trunks/ftdi - -PROG = prog -SRCS = prog.c $(MPSSE_DIR)/mpsse.c $(MPSSE_DIR)/support.c - -CFLAGS = \ - -std=gnu99 \ - -g3 \ - -O3 \ - -Wall \ - -Werror \ - -Wpointer-arith \ - -Wcast-align \ - -Wcast-qual \ - -Wundef \ - -Wsign-compare \ - -Wredundant-decls \ - -Wmissing-declarations - -CFLAGS += -I../../include -I${MPSSE_DIR} -I${PLATFORM2} - -CFLAGS += $(shell pkg-config --cflags libusb-1.0 libftdi1) -LIBS += $(shell pkg-config --libs libusb-1.0 libftdi1) - -$(PROG): $(SRCS) Makefile - gcc $(CFLAGS) $(SRCS) $(LDFLAGS) $(LIBS) -o $@ - -.PHONY: clean -clean: - rm -rf $(PROG) diff --git a/extra/sps_errs/README b/extra/sps_errs/README deleted file mode 100644 index d1fbb6b43f..0000000000 --- a/extra/sps_errs/README +++ /dev/null @@ -1,28 +0,0 @@ -SETUP: - - Attach an EC to the build host using an FTDI USB-to-SPI adapter. - -BUILD: - - make - ./prog - - -USAGE: - - Usage: ./prog [-v] [-c BYTES] - - This sends a EC_CMD_HELLO host command. The -c option can - be used to truncate the exchange early, to see how the EC - deals with the interruption. - -NOTE: - - Ubuntu Trusty uses an ancient version of libftdi. - - If building outside of the Chromium chroot, you'll probably want to grab the - latest libftdi1-1.2.tar.bz2 from - - http://www.intra2net.com/en/developer/libftdi/ - - and install it into /usr instead. diff --git a/extra/sps_errs/prog.c b/extra/sps_errs/prog.c deleted file mode 100644 index b649199068..0000000000 --- a/extra/sps_errs/prog.c +++ /dev/null @@ -1,447 +0,0 @@ -/* Copyright 2015 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. - */ - -#include <signal.h> -#include <stdint.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> - -#include "mpsse.h" - -#include "ec_commands.h" - -static int opt_verbose; -static size_t stop_after = -1; - -/* Communication handle */ -static struct mpsse_context *mpsse; - -/* enum ec_status meaning */ -static const char *ec_strerr(enum ec_status r) -{ - static const char * const strs[] = { - "SUCCESS", - "INVALID_COMMAND", - "ERROR", - "INVALID_PARAM", - "ACCESS_DENIED", - "INVALID_RESPONSE", - "INVALID_VERSION", - "INVALID_CHECKSUM", - "IN_PROGRESS", - "UNAVAILABLE", - "TIMEOUT", - "OVERFLOW", - "INVALID_HEADER", - "REQUEST_TRUNCATED", - "RESPONSE_TOO_BIG", - "BUS_ERROR", - "BUSY", - }; - if (r >= EC_RES_SUCCESS && r <= EC_RES_BUSY) - return strs[r]; - - return "<undefined result>"; -}; - - -/**************************************************************************** - * Debugging output - */ - -#define LINELEN 16 - -static void showline(uint8_t *buf, int len) -{ - int i; - printf(" "); - for (i = 0; i < len; i++) - printf(" %02x", buf[i]); - for (i = len; i < LINELEN; i++) - printf(" "); - printf(" "); - for (i = 0; i < len; i++) - printf("%c", - (buf[i] >= ' ' && buf[i] <= '~') ? buf[i] : '.'); - printf("\n"); -} - -static void show(const char *fmt, uint8_t *buf, int len) -{ - int i, m, n; - - if (!opt_verbose) - return; - - printf(fmt, len); - - m = len / LINELEN; - n = len % LINELEN; - - for (i = 0; i < m; i++) - showline(buf + i * LINELEN, LINELEN); - if (n) - showline(buf + m * LINELEN, n); -} - -/**************************************************************************** - * Send command & receive result - */ - -/* - * With proto v3, the kernel driver asks the EC for the max param size - * (EC_CMD_GET_PROTOCOL_INFO) at probe time, because it can vary depending on - * the bus and/or the supported commands. - * - * FIXME: For now we'll just hard-code a size. - */ -static uint8_t txbuf[128]; - -/* - * Load the output buffer with a proto v3 request (header, then data, with - * checksum correct in header). - */ -static size_t prepare_request(int cmd, int version, - const uint8_t *data, size_t data_len) -{ - struct ec_host_request *request; - size_t i, total_len; - uint8_t csum = 0; - - total_len = sizeof(*request) + data_len; - if (total_len > sizeof(txbuf)) { - printf("Request too large (%zd > %zd)\n", - total_len, sizeof(txbuf)); - return -1; - } - - /* Header first */ - request = (struct ec_host_request *)txbuf; - request->struct_version = EC_HOST_REQUEST_VERSION; - request->checksum = 0; - request->command = cmd; - request->command_version = version; - request->reserved = 0; - request->data_len = data_len; - - /* Then data */ - memcpy(txbuf + sizeof(*request), data, data_len); - - /* Update checksum */ - for (i = 0; i < total_len; i++) - csum += txbuf[i]; - request->checksum = -csum; - - return total_len; -} - - -/* Timeout flag, so we don't wait forever */ -static int timedout; -static void alarm_handler(int sig) -{ - timedout = 1; -} - -/* - * Send command, wait for result. Return zero if communication succeeded; check - * response to see if the EC liked the command. - */ -static int send_cmd(int cmd, int version, - void *outbuf, - size_t outsize, - struct ec_host_response *hdr, - void *bodydest, - size_t bodylen) -{ - uint8_t *tptr, *hptr = 0, *bptr = 0; - size_t len, i; - uint8_t sum = 0; - int lastone = 0x1111; - int ret = 0; - size_t bytes_left = stop_after; - size_t bytes_sent = 0; - - - /* Load up the txbuf with the stuff to send */ - len = prepare_request(cmd, version, outbuf, outsize); - if (len < 0) - return -1; - - if (MPSSE_OK != Start(mpsse)) { - fprintf(stderr, "Start failed: %s\n", - ErrorString(mpsse)); - return -1; - } - - /* Send the command request */ - if (len > bytes_left) { - printf("len %zd => %zd\n", len, bytes_left); - len = bytes_left; - } - - show("Transfer(%d) =>\n", txbuf, len); - tptr = Transfer(mpsse, txbuf, len); - bytes_left -= len; - bytes_sent += len; - if (!tptr) { - fprintf(stderr, "Transfer failed: %s\n", - ErrorString(mpsse)); - goto out; - } - - show("Transfer(%d) <=\n", tptr, len); - - /* Make sure the EC was listening */ - for (i = 0; i < len; i++) { - switch (tptr[i]) { - case EC_SPI_PAST_END: - case EC_SPI_RX_BAD_DATA: - case EC_SPI_NOT_READY: - ret = tptr[i]; - /* FALLTHROUGH */ - default: - break; - } - if (ret) - break; - } - free(tptr); - if (ret) { - printf("HEY: EC no good (0x%02x)\n", ret); - goto out; - } - - if (!bytes_left) - goto out; - - /* Read until we see the response come along */ - - /* Give up eventually */ - timedout = 0; - if (SIG_ERR == signal(SIGALRM, alarm_handler)) { - perror("Problem with signal handler"); - goto out; - } - alarm(1); - - if (opt_verbose) - printf("Wait:"); - - /* Read a byte at a time until we see the start of the frame. - * This is slow, but still faster than the EC. */ - while (bytes_left) { - uint8_t *ptr = Read(mpsse, 1); - bytes_left--; - bytes_sent++; - if (!ptr) { - fprintf(stderr, "Read failed: %s\n", - ErrorString(mpsse)); - alarm(0); - goto out; - } - if (opt_verbose && lastone != *ptr) { - printf(" %02x", *ptr); - lastone = *ptr; - } - if (*ptr == EC_SPI_FRAME_START) { - free(ptr); - break; - } - free(ptr); - - if (timedout) { - fprintf(stderr, "timed out\n"); - goto out; - } - } - alarm(0); - - if (opt_verbose) - printf("\n"); - - if (!bytes_left) - goto out; - - /* Now read the response header */ - len = sizeof(*hdr); - if (len > bytes_left) { - printf("len %zd => %zd\n", len, bytes_left); - len = bytes_left; - } - - hptr = Read(mpsse, len); - bytes_left -= len; - bytes_sent += len; - if (!hptr) { - fprintf(stderr, "Read failed: %s\n", - ErrorString(mpsse)); - goto out; - } - show("Header(%d):\n", hptr, sizeof(*hdr)); - memcpy(hdr, hptr, sizeof(*hdr)); - - /* Check the header */ - if (hdr->struct_version != EC_HOST_RESPONSE_VERSION) { - printf("HEY: response version %d (should be %d)\n", - hdr->struct_version, - EC_HOST_RESPONSE_VERSION); - goto out; - } - - if (hdr->data_len > bodylen) { - printf("HEY: response data_len %d is > %zd\n", - hdr->data_len, - bodylen); - goto out; - } - - if (!bytes_left) - goto out; - - len = hdr->data_len; - if (len > bytes_left) { - printf("len %zd => %zd\n", len, bytes_left); - len = bytes_left; - } - - /* Read the data */ - if (len) { - bptr = Read(mpsse, len); - bytes_left -= len; - bytes_sent += len; - if (!bptr) { - fprintf(stderr, "Read failed: %s\n", - ErrorString(mpsse)); - goto out; - } - show("Body(%d):\n", bptr, hdr->data_len); - memcpy(bodydest, bptr, hdr->data_len); - } - - /* Verify the checksum */ - for (i = 0; i < sizeof(hdr); i++) - sum += hptr[i]; - for (i = 0; i < hdr->data_len; i++) - sum += bptr[i]; - if (sum) - printf("HEY: Checksum invalid\n"); - -out: - printf("sent %zd bytes\n", bytes_sent); - if (!bytes_left) - printf("hit byte limit\n"); - if (hptr) - free(hptr); - if (bptr) - free(bptr); - - if (MPSSE_OK != Stop(mpsse)) { - fprintf(stderr, "Stop failed: %s\n", - ErrorString(mpsse)); - return -1; - } - - return 0; -} - - -/****************************************************************************/ - -/** - * Try it. - * - * @return zero on success - */ -static int hello(void) -{ - struct ec_params_hello p; - struct ec_host_response resp; - struct ec_response_hello r; - uint32_t expected; - int retval; - - memset(&p, 0, sizeof(p)); - memset(&resp, 0, sizeof(resp)); - memset(&r, 0, sizeof(r)); - - p.in_data = 0xa5a5a5a5; - expected = p.in_data + 0x01020304; - - retval = send_cmd(EC_CMD_HELLO, 0, - &p, sizeof(p), - &resp, - &r, sizeof(r)); - - if (retval) { - printf("Transmission error\n"); - return -1; - } - - if (EC_RES_SUCCESS != resp.result) { - printf("EC result is %d: %s\n", - resp.result, ec_strerr(resp.result)); - return -1; - } - - printf("sent %08x, expected %08x, got %08x => %s\n", - p.in_data, expected, r.out_data, - expected == r.out_data ? "yay" : "boo"); - - return !(expected == r.out_data); -} - -static void usage(char *progname) -{ - printf("\nUsage: %s [-v] [-c BYTES]\n\n", progname); - printf("This sends a EC_CMD_HELLO host command. The -c option can\n"); - printf("be used to truncate the exchange early, to see how the EC\n"); - printf("deals with the interruption.\n\n"); -} - -int main(int argc, char *argv[]) -{ - int retval = 1; - int errorcnt = 0; - int i; - - while ((i = getopt(argc, argv, ":vc:")) != -1) { - switch (i) { - case 'c': - stop_after = atoi(optarg); - printf("stopping after %zd bytes\n", stop_after); - break; - case 'v': - opt_verbose++; - break; - case '?': - printf("unrecognized option: -%c\n", optopt); - errorcnt++; - break; - } - } - if (errorcnt) { - usage(argv[0]); - return 1; - } - - /* Find something to talk to */ - mpsse = MPSSE(SPI0, 2000000, 0); - if (!mpsse) { - printf("Can't find a device to open\n"); - return 1; - } - - if (0 != hello()) - goto out; - - retval = 0; -out: - Close(mpsse); - mpsse = 0; - return retval; -} diff --git a/extra/stack_analyzer/README.md b/extra/stack_analyzer/README.md deleted file mode 100644 index d1c77b57d2..0000000000 --- a/extra/stack_analyzer/README.md +++ /dev/null @@ -1,105 +0,0 @@ -# Stack Size Analysis Tool for EC Firmware - -This tool does static analysis on EC firmwares to get the maximum stack usage of -each function and task. The maximum stack usage of a function includes the stack -used by itself and the functions it calls. - -## Usage - -Make sure the firmware of your target board has been built. - -In `src/platform/ec`, run `make BOARD=${BOARD} SECTION=${SECTION} -ANNOTATION=${ANNOTATION} analyzestack` The `${SECTION}` can be `RO` or `RW`. The -`${ANNOTATION}` is a optional annotation file, see the example_annotation.yaml, -by default, board/$BOARD/analyzestack.yaml is used. - -## Output - -For each task, it will output the result like below, - -``` -Task: PD_C1, Max size: 1156 (932 + 224), Allocated size: 640 -Call Trace: - pd_task (160) [common/usb_pd_protocol.c:1644] 1008a6e8 - -> pd_task[common/usb_pd_protocol.c:1808] 1008ac8a - - handle_request[common/usb_pd_protocol.c:1191] - - handle_data_request[common/usb_pd_protocol.c:798] - -> pd_task[common/usb_pd_protocol.c:2672] 1008c222 - -> [annotation] - pd_send_request_msg.lto_priv.263 (56) [common/usb_pd_protocol.c:653] 1009a0b4 - -> pd_send_request_msg.lto_priv.263[common/usb_pd_protocol.c:712] 1009a22e0 -``` - -The `pd_task` uses 160 bytes on the stack and calls -`pd_send_request_msg.lto_priv.263`. - -The callsites to the next function will be shown like below, - -``` --> pd_task[common/usb_pd_protocol.c:1808] 1008ac8a - - handle_request[common/usb_pd_protocol.c:1191] - - handle_data_request[common/usb_pd_protocol.c:798] --> pd_task[common/usb_pd_protocol.c:2672] 1008c222 --> [annotation] -``` - -This means one callsite to the next function is at `usb_pd_protocol.c:798`, but -it is inlined to the current function and you can follow the trace: -`usb_pd_protocol.c:1808 -> usb_pd_protocol.c:1191 -> usb_pd_protocol.c:798` to -find the callsite. The second callsite is at `usb_pd_protocol.c:2672`. And the -third one is added by annotation. - -The unresolved indirect callsites have the similar format to the above. - -## Annotating Indirect Call - -To annotate an indirect call like this, - -``` -Unresolved indirect callsites: - pd_transmit - -> pd_transmit[common/usb_pd_protocol.c:407] 802c9c8 - - tcpm_transmit[driver/tcpm/tcpm.h:142] -``` - -It is an indirect call in the `tcpm_transmit`, which is inlined to the -`pd_transmit`. - -You can add a annotation like the below to eliminate it. - -``` -add: - tcpm_transmit[driver/tcpm/tcpm.h:142]: - - anx74xx_tcpm_transmit -``` - -The source `tcpm_transmit[driver/tcpm/tcpm.h:142]` must be a full signature -(function_name[path:line number]). So the resolver can know which indirect call -you want to annotate and eliminate (even if it is inlined). - -## Annotating arrays (hooks, console commands, host commands) - -When a callsite calls a number of functions based on values from an constant -array (in `.rodata` section), one can use the following syntax: - -``` - hook_task[common/hooks.c:197]: - - { name: __deferred_funcs, stride: 4, offset: 0 } - - { name: __hooks_second, stride: 8, offset: 0 } - - { name: __hooks_tick, stride: 8, offset: 0 } -``` - -Where `name` is the symbol name for the start of the array (the end of the array -is `<name>_end`), stride is the array element size, and offset is the offset of -the function pointer in the structure. For example, above, `__deferred_funcs` is -a simple array of function pointers, while `__hooks_tick` is an array of `struct -hook_data` (size 8, pointer at offset 0): - -``` -struct hook_data { - /* Hook processing routine. */ - void (*routine)(void); - /* Priority; low numbers = higher priority. */ - int priority; -}; -``` diff --git a/extra/stack_analyzer/example_annotation.yaml b/extra/stack_analyzer/example_annotation.yaml deleted file mode 100644 index 084fabc2d1..0000000000 --- a/extra/stack_analyzer/example_annotation.yaml +++ /dev/null @@ -1,44 +0,0 @@ -# Size of extra stack frame needed by exception context switch. -exception_frame_size: 64 - -# Add some missing calls. -add: - # console_task also calls command_display_accel_info and command_accel_init. - console_task: - - command_display_accel_info - - command_accel_init - - # Function name can be followed by [source code path] to indicate where is it - # declared (there may be several functions with the same name). - motion_lid_calc[common/motion_lid.c]: - - get_range[driver/accel_kionix.c] - - # The full signature (function name[path:line number]) can be used to - # eliminate the indirect call (see README.md). - tcpm_transmit[driver/tcpm/tcpm.h:142]: - - anx74xx_tcpm_transmit - -# Remove some call paths. -remove: -# Remove all callsites pointing to panic_assert_fail. -- panic_assert_fail -- panic -- [software_panic] -# Remove some invalid paths. -- [pd_send_request_msg, set_state, pd_power_supply_reset] -- [__tx_char, __tx_char] -- [set_state, set_state, set_state] - -# Remove two invalid paths with the common prefix. -- [pd_execute_hard_reset, set_state, [charge_manager_update_dualrole, pd_dfp_exit_mode]] -# It is equivalent to the following two lines, -# - [pd_execute_hard_reset, set_state, charge_manager_update_dualrole] -# - [pd_execute_hard_reset, set_state, pd_dfp_exit_mode] - -# Remove four invalid paths with the common segment. -- [[pd_send_request_msg, pd_request_vconn_swap], set_state, [usb_mux_set, pd_power_supply_reset]] -# It is equivalent to the following four lines, -# - [pd_send_request_msg, set_state, usb_mux_set] -# - [pd_send_request_msg, set_state, pd_power_supply_reset] -# - [pd_request_vconn_swap, set_state, usb_mux_set] -# - [pd_request_vconn_swap, set_state, pd_power_supply_reset] diff --git a/extra/stack_analyzer/run_tests.sh b/extra/stack_analyzer/run_tests.sh deleted file mode 100755 index 5662f60b8b..0000000000 --- a/extra/stack_analyzer/run_tests.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash -# -# Copyright 2017 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. - -# Discover all the unit tests in extra/stack_analyzer directory and run them. -python3 -m unittest discover -b -s extra/stack_analyzer -p "*_unittest.py" \ - && touch extra/stack_analyzer/.tests-passed diff --git a/extra/stack_analyzer/stack_analyzer.py b/extra/stack_analyzer/stack_analyzer.py deleted file mode 100755 index 77d16d5450..0000000000 --- a/extra/stack_analyzer/stack_analyzer.py +++ /dev/null @@ -1,1872 +0,0 @@ -#!/usr/bin/env python3 -# Copyright 2017 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. -# -# Ignore indention messages, since legacy scripts use 2 spaces instead of 4. -# pylint: disable=bad-indentation,docstring-section-indent -# pylint: disable=docstring-trailing-quotes - -"""Statically analyze stack usage of EC firmware. - - Example: - extra/stack_analyzer/stack_analyzer.py \ - --export_taskinfo ./build/elm/util/export_taskinfo.so \ - --section RW \ - ./build/elm/RW/ec.RW.elf - -""" - -from __future__ import print_function - -import argparse -import collections -import ctypes -import os -import re -import subprocess -import yaml - - -SECTION_RO = 'RO' -SECTION_RW = 'RW' -# Default size of extra stack frame needed by exception context switch. -# This value is for cortex-m with FPU enabled. -DEFAULT_EXCEPTION_FRAME_SIZE = 224 - - -class StackAnalyzerError(Exception): - """Exception class for stack analyzer utility.""" - - -class TaskInfo(ctypes.Structure): - """Taskinfo ctypes structure. - - The structure definition is corresponding to the "struct taskinfo" - in "util/export_taskinfo.so.c". - """ - _fields_ = [('name', ctypes.c_char_p), - ('routine', ctypes.c_char_p), - ('stack_size', ctypes.c_uint32)] - - -class Task(object): - """Task information. - - Attributes: - name: Task name. - routine_name: Routine function name. - stack_max_size: Max stack size. - routine_address: Resolved routine address. None if it hasn't been resolved. - """ - - def __init__(self, name, routine_name, stack_max_size, routine_address=None): - """Constructor. - - Args: - name: Task name. - routine_name: Routine function name. - stack_max_size: Max stack size. - routine_address: Resolved routine address. - """ - self.name = name - self.routine_name = routine_name - self.stack_max_size = stack_max_size - self.routine_address = routine_address - - def __eq__(self, other): - """Task equality. - - Args: - other: The compared object. - - Returns: - True if equal, False if not. - """ - if not isinstance(other, Task): - return False - - return (self.name == other.name and - self.routine_name == other.routine_name and - self.stack_max_size == other.stack_max_size and - self.routine_address == other.routine_address) - - -class Symbol(object): - """Symbol information. - - Attributes: - address: Symbol address. - symtype: Symbol type, 'O' (data, object) or 'F' (function). - size: Symbol size. - name: Symbol name. - """ - - def __init__(self, address, symtype, size, name): - """Constructor. - - Args: - address: Symbol address. - symtype: Symbol type. - size: Symbol size. - name: Symbol name. - """ - assert symtype in ['O', 'F'] - self.address = address - self.symtype = symtype - self.size = size - self.name = name - - def __eq__(self, other): - """Symbol equality. - - Args: - other: The compared object. - - Returns: - True if equal, False if not. - """ - if not isinstance(other, Symbol): - return False - - return (self.address == other.address and - self.symtype == other.symtype and - self.size == other.size and - self.name == other.name) - - -class Callsite(object): - """Function callsite. - - Attributes: - address: Address of callsite location. None if it is unknown. - target: Callee address. None if it is unknown. - is_tail: A bool indicates that it is a tailing call. - callee: Resolved callee function. None if it hasn't been resolved. - """ - - def __init__(self, address, target, is_tail, callee=None): - """Constructor. - - Args: - address: Address of callsite location. None if it is unknown. - target: Callee address. None if it is unknown. - is_tail: A bool indicates that it is a tailing call. (function jump to - another function without restoring the stack frame) - callee: Resolved callee function. - """ - # It makes no sense that both address and target are unknown. - assert not (address is None and target is None) - self.address = address - self.target = target - self.is_tail = is_tail - self.callee = callee - - def __eq__(self, other): - """Callsite equality. - - Args: - other: The compared object. - - Returns: - True if equal, False if not. - """ - if not isinstance(other, Callsite): - return False - - if not (self.address == other.address and - self.target == other.target and - self.is_tail == other.is_tail): - return False - - if self.callee is None: - return other.callee is None - elif other.callee is None: - return False - - # Assume the addresses of functions are unique. - return self.callee.address == other.callee.address - - -class Function(object): - """Function. - - Attributes: - address: Address of function. - name: Name of function from its symbol. - stack_frame: Size of stack frame. - callsites: Callsite list. - stack_max_usage: Max stack usage. None if it hasn't been analyzed. - stack_max_path: Max stack usage path. None if it hasn't been analyzed. - """ - - def __init__(self, address, name, stack_frame, callsites): - """Constructor. - - Args: - address: Address of function. - name: Name of function from its symbol. - stack_frame: Size of stack frame. - callsites: Callsite list. - """ - self.address = address - self.name = name - self.stack_frame = stack_frame - self.callsites = callsites - self.stack_max_usage = None - self.stack_max_path = None - - def __eq__(self, other): - """Function equality. - - Args: - other: The compared object. - - Returns: - True if equal, False if not. - """ - if not isinstance(other, Function): - return False - - if not (self.address == other.address and - self.name == other.name and - self.stack_frame == other.stack_frame and - self.callsites == other.callsites and - self.stack_max_usage == other.stack_max_usage): - return False - - if self.stack_max_path is None: - return other.stack_max_path is None - elif other.stack_max_path is None: - return False - - if len(self.stack_max_path) != len(other.stack_max_path): - return False - - for self_func, other_func in zip(self.stack_max_path, other.stack_max_path): - # Assume the addresses of functions are unique. - if self_func.address != other_func.address: - return False - - return True - - def __hash__(self): - return id(self) - -class AndesAnalyzer(object): - """Disassembly analyzer for Andes architecture. - - Public Methods: - AnalyzeFunction: Analyze stack frame and callsites of the function. - """ - - GENERAL_PURPOSE_REGISTER_SIZE = 4 - - # Possible condition code suffixes. - CONDITION_CODES = [ 'eq', 'eqz', 'gez', 'gtz', 'lez', 'ltz', 'ne', 'nez', - 'eqc', 'nec', 'nezs', 'nes', 'eqs'] - CONDITION_CODES_RE = '({})'.format('|'.join(CONDITION_CODES)) - - IMM_ADDRESS_RE = r'([0-9A-Fa-f]+)\s+<([^>]+)>' - # Branch instructions. - JUMP_OPCODE_RE = re.compile(r'^(b{0}|j|jr|jr.|jrnez)(\d?|\d\d)$' \ - .format(CONDITION_CODES_RE)) - # Call instructions. - CALL_OPCODE_RE = re.compile \ - (r'^(jal|jral|jral.|jralnez|beqzal|bltzal|bgezal)(\d)?$') - CALL_OPERAND_RE = re.compile(r'^{}$'.format(IMM_ADDRESS_RE)) - # Ignore lp register because it's for return. - INDIRECT_CALL_OPERAND_RE = re.compile \ - (r'^\$r\d{1,}$|\$fp$|\$gp$|\$ta$|\$sp$|\$pc$') - # TODO: Handle other kinds of store instructions. - PUSH_OPCODE_RE = re.compile(r'^push(\d{1,})$') - PUSH_OPERAND_RE = re.compile(r'^\$r\d{1,}, \#\d{1,} \! \{([^\]]+)\}') - SMW_OPCODE_RE = re.compile(r'^smw(\.\w\w|\.\w\w\w)$') - SMW_OPERAND_RE = re.compile(r'^(\$r\d{1,}|\$\wp), \[\$\wp\], ' - r'(\$r\d{1,}|\$\wp), \#\d\w\d \! \{([^\]]+)\}') - OPERANDGROUP_RE = re.compile(r'^\$r\d{1,}\~\$r\d{1,}') - - LWI_OPCODE_RE = re.compile(r'^lwi(\.\w\w)$') - LWI_PC_OPERAND_RE = re.compile(r'^\$pc, \[([^\]]+)\]') - # Example: "34280: 3f c8 0f ec addi.gp $fp, #0xfec" - # Assume there is always a "\t" after the hex data. - DISASM_REGEX_RE = re.compile(r'^(?P<address>[0-9A-Fa-f]+):\s+' - r'(?P<words>[0-9A-Fa-f ]+)' - r'\t\s*(?P<opcode>\S+)(\s+(?P<operand>[^;]*))?') - - def ParseInstruction(self, line, function_end): - """Parse the line of instruction. - - Args: - line: Text of disassembly. - function_end: End address of the current function. None if unknown. - - Returns: - (address, words, opcode, operand_text): The instruction address, words, - opcode, and the text of operands. - None if it isn't an instruction line. - """ - result = self.DISASM_REGEX_RE.match(line) - if result is None: - return None - - address = int(result.group('address'), 16) - # Check if it's out of bound. - if function_end is not None and address >= function_end: - return None - - opcode = result.group('opcode').strip() - operand_text = result.group('operand') - words = result.group('words') - if operand_text is None: - operand_text = '' - else: - operand_text = operand_text.strip() - - return (address, words, opcode, operand_text) - - def AnalyzeFunction(self, function_symbol, instructions): - - stack_frame = 0 - callsites = [] - for address, words, opcode, operand_text in instructions: - is_jump_opcode = self.JUMP_OPCODE_RE.match(opcode) is not None - is_call_opcode = self.CALL_OPCODE_RE.match(opcode) is not None - - if is_jump_opcode or is_call_opcode: - is_tail = is_jump_opcode - - result = self.CALL_OPERAND_RE.match(operand_text) - - if result is None: - if (self.INDIRECT_CALL_OPERAND_RE.match(operand_text) is not None): - # Found an indirect call. - callsites.append(Callsite(address, None, is_tail)) - - else: - target_address = int(result.group(1), 16) - # Filter out the in-function target (branches and in-function calls, - # which are actually branches). - if not (function_symbol.size > 0 and - function_symbol.address < target_address < - (function_symbol.address + function_symbol.size)): - # Maybe it is a callsite. - callsites.append(Callsite(address, target_address, is_tail)) - - elif self.LWI_OPCODE_RE.match(opcode) is not None: - result = self.LWI_PC_OPERAND_RE.match(operand_text) - if result is not None: - # Ignore "lwi $pc, [$sp], xx" because it's usually a return. - if result.group(1) != '$sp': - # Found an indirect call. - callsites.append(Callsite(address, None, True)) - - elif self.PUSH_OPCODE_RE.match(opcode) is not None: - # Example: fc 20 push25 $r8, #0 ! {$r6~$r8, $fp, $gp, $lp} - if self.PUSH_OPERAND_RE.match(operand_text) is not None: - # capture fc 20 - imm5u = int(words.split(' ')[1], 16) - # sp = sp - (imm5u << 3) - imm8u = (imm5u<<3) & 0xff - stack_frame += imm8u - - result = self.PUSH_OPERAND_RE.match(operand_text) - operandgroup_text = result.group(1) - # capture $rx~$ry - if self.OPERANDGROUP_RE.match(operandgroup_text) is not None: - # capture number & transfer string to integer - oprandgrouphead = operandgroup_text.split(',')[0] - rx=int(''.join(filter(str.isdigit, oprandgrouphead.split('~')[0]))) - ry=int(''.join(filter(str.isdigit, oprandgrouphead.split('~')[1]))) - - stack_frame += ((len(operandgroup_text.split(','))+ry-rx) * - self.GENERAL_PURPOSE_REGISTER_SIZE) - else: - stack_frame += (len(operandgroup_text.split(',')) * - self.GENERAL_PURPOSE_REGISTER_SIZE) - - elif self.SMW_OPCODE_RE.match(opcode) is not None: - # Example: smw.adm $r6, [$sp], $r10, #0x2 ! {$r6~$r10, $lp} - if self.SMW_OPERAND_RE.match(operand_text) is not None: - result = self.SMW_OPERAND_RE.match(operand_text) - operandgroup_text = result.group(3) - # capture $rx~$ry - if self.OPERANDGROUP_RE.match(operandgroup_text) is not None: - # capture number & transfer string to integer - oprandgrouphead = operandgroup_text.split(',')[0] - rx=int(''.join(filter(str.isdigit, oprandgrouphead.split('~')[0]))) - ry=int(''.join(filter(str.isdigit, oprandgrouphead.split('~')[1]))) - - stack_frame += ((len(operandgroup_text.split(','))+ry-rx) * - self.GENERAL_PURPOSE_REGISTER_SIZE) - else: - stack_frame += (len(operandgroup_text.split(',')) * - self.GENERAL_PURPOSE_REGISTER_SIZE) - - return (stack_frame, callsites) - -class ArmAnalyzer(object): - """Disassembly analyzer for ARM architecture. - - Public Methods: - AnalyzeFunction: Analyze stack frame and callsites of the function. - """ - - GENERAL_PURPOSE_REGISTER_SIZE = 4 - - # Possible condition code suffixes. - CONDITION_CODES = ['', 'eq', 'ne', 'cs', 'hs', 'cc', 'lo', 'mi', 'pl', 'vs', - 'vc', 'hi', 'ls', 'ge', 'lt', 'gt', 'le'] - CONDITION_CODES_RE = '({})'.format('|'.join(CONDITION_CODES)) - # Assume there is no function name containing ">". - IMM_ADDRESS_RE = r'([0-9A-Fa-f]+)\s+<([^>]+)>' - - # Fuzzy regular expressions for instruction and operand parsing. - # Branch instructions. - JUMP_OPCODE_RE = re.compile( - r'^(b{0}|bx{0})(\.\w)?$'.format(CONDITION_CODES_RE)) - # Call instructions. - CALL_OPCODE_RE = re.compile( - r'^(bl{0}|blx{0})(\.\w)?$'.format(CONDITION_CODES_RE)) - CALL_OPERAND_RE = re.compile(r'^{}$'.format(IMM_ADDRESS_RE)) - CBZ_CBNZ_OPCODE_RE = re.compile(r'^(cbz|cbnz)(\.\w)?$') - # Example: "r0, 1009bcbe <host_cmd_motion_sense+0x1d2>" - CBZ_CBNZ_OPERAND_RE = re.compile(r'^[^,]+,\s+{}$'.format(IMM_ADDRESS_RE)) - # Ignore lr register because it's for return. - INDIRECT_CALL_OPERAND_RE = re.compile(r'^r\d+|sb|sl|fp|ip|sp|pc$') - # TODO(cheyuw): Handle conditional versions of following - # instructions. - # TODO(cheyuw): Handle other kinds of pc modifying instructions (e.g. mov pc). - LDR_OPCODE_RE = re.compile(r'^ldr(\.\w)?$') - # Example: "pc, [sp], #4" - LDR_PC_OPERAND_RE = re.compile(r'^pc, \[([^\]]+)\]') - # TODO(cheyuw): Handle other kinds of stm instructions. - PUSH_OPCODE_RE = re.compile(r'^push$') - STM_OPCODE_RE = re.compile(r'^stmdb$') - # Stack subtraction instructions. - SUB_OPCODE_RE = re.compile(r'^sub(s|w)?(\.\w)?$') - SUB_OPERAND_RE = re.compile(r'^sp[^#]+#(\d+)') - # Example: "44d94: f893 0068 ldrb.w r0, [r3, #104] ; 0x68" - # Assume there is always a "\t" after the hex data. - DISASM_REGEX_RE = re.compile(r'^(?P<address>[0-9A-Fa-f]+):\s+[0-9A-Fa-f ]+' - r'\t\s*(?P<opcode>\S+)(\s+(?P<operand>[^;]*))?') - - def ParseInstruction(self, line, function_end): - """Parse the line of instruction. - - Args: - line: Text of disassembly. - function_end: End address of the current function. None if unknown. - - Returns: - (address, opcode, operand_text): The instruction address, opcode, - and the text of operands. None if it - isn't an instruction line. - """ - result = self.DISASM_REGEX_RE.match(line) - if result is None: - return None - - address = int(result.group('address'), 16) - # Check if it's out of bound. - if function_end is not None and address >= function_end: - return None - - opcode = result.group('opcode').strip() - operand_text = result.group('operand') - if operand_text is None: - operand_text = '' - else: - operand_text = operand_text.strip() - - return (address, opcode, operand_text) - - def AnalyzeFunction(self, function_symbol, instructions): - """Analyze function, resolve the size of stack frame and callsites. - - Args: - function_symbol: Function symbol. - instructions: Instruction list. - - Returns: - (stack_frame, callsites): Size of stack frame, callsite list. - """ - stack_frame = 0 - callsites = [] - for address, opcode, operand_text in instructions: - is_jump_opcode = self.JUMP_OPCODE_RE.match(opcode) is not None - is_call_opcode = self.CALL_OPCODE_RE.match(opcode) is not None - is_cbz_cbnz_opcode = self.CBZ_CBNZ_OPCODE_RE.match(opcode) is not None - if is_jump_opcode or is_call_opcode or is_cbz_cbnz_opcode: - is_tail = is_jump_opcode or is_cbz_cbnz_opcode - - if is_cbz_cbnz_opcode: - result = self.CBZ_CBNZ_OPERAND_RE.match(operand_text) - else: - result = self.CALL_OPERAND_RE.match(operand_text) - - if result is None: - # Failed to match immediate address, maybe it is an indirect call. - # CBZ and CBNZ can't be indirect calls. - if (not is_cbz_cbnz_opcode and - self.INDIRECT_CALL_OPERAND_RE.match(operand_text) is not None): - # Found an indirect call. - callsites.append(Callsite(address, None, is_tail)) - - else: - target_address = int(result.group(1), 16) - # Filter out the in-function target (branches and in-function calls, - # which are actually branches). - if not (function_symbol.size > 0 and - function_symbol.address < target_address < - (function_symbol.address + function_symbol.size)): - # Maybe it is a callsite. - callsites.append(Callsite(address, target_address, is_tail)) - - elif self.LDR_OPCODE_RE.match(opcode) is not None: - result = self.LDR_PC_OPERAND_RE.match(operand_text) - if result is not None: - # Ignore "ldr pc, [sp], xx" because it's usually a return. - if result.group(1) != 'sp': - # Found an indirect call. - callsites.append(Callsite(address, None, True)) - - elif self.PUSH_OPCODE_RE.match(opcode) is not None: - # Example: "{r4, r5, r6, r7, lr}" - stack_frame += (len(operand_text.split(',')) * - self.GENERAL_PURPOSE_REGISTER_SIZE) - elif self.SUB_OPCODE_RE.match(opcode) is not None: - result = self.SUB_OPERAND_RE.match(operand_text) - if result is not None: - stack_frame += int(result.group(1)) - else: - # Unhandled stack register subtraction. - assert not operand_text.startswith('sp') - - elif self.STM_OPCODE_RE.match(opcode) is not None: - if operand_text.startswith('sp!'): - # Subtract and writeback to stack register. - # Example: "sp!, {r4, r5, r6, r7, r8, r9, lr}" - # Get the text of pushed register list. - unused_sp, unused_sep, parameter_text = operand_text.partition(',') - stack_frame += (len(parameter_text.split(',')) * - self.GENERAL_PURPOSE_REGISTER_SIZE) - - return (stack_frame, callsites) - -class RiscvAnalyzer(object): - """Disassembly analyzer for RISC-V architecture. - - Public Methods: - AnalyzeFunction: Analyze stack frame and callsites of the function. - """ - - # Possible condition code suffixes. - CONDITION_CODES = [ 'eqz', 'nez', 'lez', 'gez', 'ltz', 'gtz', 'gt', 'le', - 'gtu', 'leu', 'eq', 'ne', 'ge', 'lt', 'ltu', 'geu'] - CONDITION_CODES_RE = '({})'.format('|'.join(CONDITION_CODES)) - # Branch instructions. - JUMP_OPCODE_RE = re.compile(r'^(b{0}|j|jr)$'.format(CONDITION_CODES_RE)) - # Call instructions. - CALL_OPCODE_RE = re.compile(r'^(jal|jalr)$') - # Example: "j 8009b318 <set_state_prl_hr>" or - # "jal ra,800a4394 <power_get_signals>" or - # "bltu t0,t1,80080300 <data_loop>" - JUMP_ADDRESS_RE = r'((\w(\w|\d\d),){0,2})([0-9A-Fa-f]+)\s+<([^>]+)>' - CALL_OPERAND_RE = re.compile(r'^{}$'.format(JUMP_ADDRESS_RE)) - # Capture address, Example: 800a4394 - CAPTURE_ADDRESS = re.compile(r'[0-9A-Fa-f]{8}') - # Indirect jump, Example: jalr a5 - INDIRECT_CALL_OPERAND_RE = re.compile(r'^t\d+|s\d+|a\d+$') - # Example: addi - ADDI_OPCODE_RE = re.compile(r'^addi$') - # Allocate stack instructions. - ADDI_OPERAND_RE = re.compile(r'^(sp,sp,-\d+)$') - # Example: "800804b6: 1101 addi sp,sp,-32" - DISASM_REGEX_RE = re.compile(r'^(?P<address>[0-9A-Fa-f]+):\s+[0-9A-Fa-f ]+' - r'\t\s*(?P<opcode>\S+)(\s+(?P<operand>[^;]*))?') - - def ParseInstruction(self, line, function_end): - """Parse the line of instruction. - - Args: - line: Text of disassembly. - function_end: End address of the current function. None if unknown. - - Returns: - (address, opcode, operand_text): The instruction address, opcode, - and the text of operands. None if it - isn't an instruction line. - """ - result = self.DISASM_REGEX_RE.match(line) - if result is None: - return None - - address = int(result.group('address'), 16) - # Check if it's out of bound. - if function_end is not None and address >= function_end: - return None - - opcode = result.group('opcode').strip() - operand_text = result.group('operand') - if operand_text is None: - operand_text = '' - else: - operand_text = operand_text.strip() - - return (address, opcode, operand_text) - - def AnalyzeFunction(self, function_symbol, instructions): - - stack_frame = 0 - callsites = [] - for address, opcode, operand_text in instructions: - is_jump_opcode = self.JUMP_OPCODE_RE.match(opcode) is not None - is_call_opcode = self.CALL_OPCODE_RE.match(opcode) is not None - - if is_jump_opcode or is_call_opcode: - is_tail = is_jump_opcode - - result = self.CALL_OPERAND_RE.match(operand_text) - if result is None: - if (self.INDIRECT_CALL_OPERAND_RE.match(operand_text) is not None): - # Found an indirect call. - callsites.append(Callsite(address, None, is_tail)) - - else: - # Capture address form operand_text and then convert to string - address_str = "".join(self.CAPTURE_ADDRESS.findall(operand_text)) - # String to integer - target_address = int(address_str, 16) - # Filter out the in-function target (branches and in-function calls, - # which are actually branches). - if not (function_symbol.size > 0 and - function_symbol.address < target_address < - (function_symbol.address + function_symbol.size)): - # Maybe it is a callsite. - callsites.append(Callsite(address, target_address, is_tail)) - - elif self.ADDI_OPCODE_RE.match(opcode) is not None: - # Example: sp,sp,-32 - if self.ADDI_OPERAND_RE.match(operand_text) is not None: - stack_frame += abs(int(operand_text.split(",")[2])) - - return (stack_frame, callsites) - -class StackAnalyzer(object): - """Class to analyze stack usage. - - Public Methods: - Analyze: Run the stack analysis. - """ - - C_FUNCTION_NAME = r'_A-Za-z0-9' - - # Assume there is no ":" in the path. - # Example: "driver/accel_kionix.c:321 (discriminator 3)" - ADDRTOLINE_RE = re.compile( - r'^(?P<path>[^:]+):(?P<linenum>\d+)(\s+\(discriminator\s+\d+\))?$') - # To eliminate the suffix appended by compilers, try to extract the - # C function name from the prefix of symbol name. - # Example: "SHA256_transform.constprop.28" - FUNCTION_PREFIX_NAME_RE = re.compile( - r'^(?P<name>[{0}]+)([^{0}].*)?$'.format(C_FUNCTION_NAME)) - - # Errors of annotation resolving. - ANNOTATION_ERROR_INVALID = 'invalid signature' - ANNOTATION_ERROR_NOTFOUND = 'function is not found' - ANNOTATION_ERROR_AMBIGUOUS = 'signature is ambiguous' - - def __init__(self, options, symbols, rodata, tasklist, annotation): - """Constructor. - - Args: - options: Namespace from argparse.parse_args(). - symbols: Symbol list. - rodata: Content of .rodata section (offset, data) - tasklist: Task list. - annotation: Annotation config. - """ - self.options = options - self.symbols = symbols - self.rodata_offset = rodata[0] - self.rodata = rodata[1] - self.tasklist = tasklist - self.annotation = annotation - self.address_to_line_cache = {} - - def AddressToLine(self, address, resolve_inline=False): - """Convert address to line. - - Args: - address: Target address. - resolve_inline: Output the stack of inlining. - - Returns: - lines: List of the corresponding lines. - - Raises: - StackAnalyzerError: If addr2line is failed. - """ - cache_key = (address, resolve_inline) - if cache_key in self.address_to_line_cache: - return self.address_to_line_cache[cache_key] - - try: - args = [self.options.addr2line, - '-f', - '-e', - self.options.elf_path, - '{:x}'.format(address)] - if resolve_inline: - args.append('-i') - - line_text = subprocess.check_output(args, encoding='utf-8') - except subprocess.CalledProcessError: - raise StackAnalyzerError('addr2line failed to resolve lines.') - except OSError: - raise StackAnalyzerError('Failed to run addr2line.') - - lines = [line.strip() for line in line_text.splitlines()] - # Assume the output has at least one pair like "function\nlocation\n", and - # they always show up in pairs. - # Example: "handle_request\n - # common/usb_pd_protocol.c:1191\n" - assert len(lines) >= 2 and len(lines) % 2 == 0 - - line_infos = [] - for index in range(0, len(lines), 2): - (function_name, line_text) = lines[index:index + 2] - if line_text in ['??:0', ':?']: - line_infos.append(None) - else: - result = self.ADDRTOLINE_RE.match(line_text) - # Assume the output is always well-formed. - assert result is not None - line_infos.append((function_name.strip(), - os.path.realpath(result.group('path').strip()), - int(result.group('linenum')))) - - self.address_to_line_cache[cache_key] = line_infos - return line_infos - - def AnalyzeDisassembly(self, disasm_text): - """Parse the disassembly text, analyze, and build a map of all functions. - - Args: - disasm_text: Disassembly text. - - Returns: - function_map: Dict of functions. - """ - disasm_lines = [line.strip() for line in disasm_text.splitlines()] - - if 'nds' in disasm_lines[1]: - analyzer = AndesAnalyzer() - elif 'arm' in disasm_lines[1]: - analyzer = ArmAnalyzer() - elif 'riscv' in disasm_lines[1]: - analyzer = RiscvAnalyzer() - else: - raise StackAnalyzerError('Unsupported architecture.') - - # Example: "08028c8c <motion_lid_calc>:" - function_signature_regex = re.compile( - r'^(?P<address>[0-9A-Fa-f]+)\s+<(?P<name>[^>]+)>:$') - - def DetectFunctionHead(line): - """Check if the line is a function head. - - Args: - line: Text of disassembly. - - Returns: - symbol: Function symbol. None if it isn't a function head. - """ - result = function_signature_regex.match(line) - if result is None: - return None - - address = int(result.group('address'), 16) - symbol = symbol_map.get(address) - - # Check if the function exists and matches. - if symbol is None or symbol.symtype != 'F': - return None - - return symbol - - # Build symbol map, indexed by symbol address. - symbol_map = {} - for symbol in self.symbols: - # If there are multiple symbols with same address, keeping any of them is - # good enough. - symbol_map[symbol.address] = symbol - - # Parse the disassembly text. We update the variable "line" to next line - # when needed. There are two steps of parser: - # - # Step 1: Searching for the function head. Once reach the function head, - # move to the next line, which is the first line of function body. - # - # Step 2: Parsing each instruction line of function body. Once reach a - # non-instruction line, stop parsing and analyze the parsed instructions. - # - # Finally turn back to the step 1 without updating the line, because the - # current non-instruction line can be another function head. - function_map = {} - # The following three variables are the states of the parsing processing. - # They will be initialized properly during the state changes. - function_symbol = None - function_end = None - instructions = [] - - # Remove heading and tailing spaces for each line. - line_index = 0 - while line_index < len(disasm_lines): - # Get the current line. - line = disasm_lines[line_index] - - if function_symbol is None: - # Step 1: Search for the function head. - - function_symbol = DetectFunctionHead(line) - if function_symbol is not None: - # Assume there is no empty function. If the function head is followed - # by EOF, it is an empty function. - assert line_index + 1 < len(disasm_lines) - - # Found the function head, initialize and turn to the step 2. - instructions = [] - # If symbol size exists, use it as a hint of function size. - if function_symbol.size > 0: - function_end = function_symbol.address + function_symbol.size - else: - function_end = None - - else: - # Step 2: Parse the function body. - - instruction = analyzer.ParseInstruction(line, function_end) - if instruction is not None: - instructions.append(instruction) - - if instruction is None or line_index + 1 == len(disasm_lines): - # Either the invalid instruction or EOF indicates the end of the - # function, finalize the function analysis. - - # Assume there is no empty function. - assert len(instructions) > 0 - - (stack_frame, callsites) = analyzer.AnalyzeFunction(function_symbol, - instructions) - # Assume the function addresses are unique in the disassembly. - assert function_symbol.address not in function_map - function_map[function_symbol.address] = Function( - function_symbol.address, - function_symbol.name, - stack_frame, - callsites) - - # Initialize and turn back to the step 1. - function_symbol = None - - # If the current line isn't an instruction, it can be another function - # head, skip moving to the next line. - if instruction is None: - continue - - # Move to the next line. - line_index += 1 - - # Resolve callees of functions. - for function in function_map.values(): - for callsite in function.callsites: - if callsite.target is not None: - # Remain the callee as None if we can't resolve it. - callsite.callee = function_map.get(callsite.target) - - return function_map - - def MapAnnotation(self, function_map, signature_set): - """Map annotation signatures to functions. - - Args: - function_map: Function map. - signature_set: Set of annotation signatures. - - Returns: - Map of signatures to functions, map of signatures which can't be resolved. - """ - # Build the symbol map indexed by symbol name. If there are multiple symbols - # with the same name, add them into a set. (e.g. symbols of static function - # with the same name) - symbol_map = collections.defaultdict(set) - for symbol in self.symbols: - if symbol.symtype == 'F': - # Function symbol. - result = self.FUNCTION_PREFIX_NAME_RE.match(symbol.name) - if result is not None: - function = function_map.get(symbol.address) - # Ignore the symbol not in disassembly. - if function is not None: - # If there are multiple symbol with the same name and point to the - # same function, the set will deduplicate them. - symbol_map[result.group('name').strip()].add(function) - - # Build the signature map indexed by annotation signature. - signature_map = {} - sig_error_map = {} - symbol_path_map = {} - for sig in signature_set: - (name, path, _) = sig - - functions = symbol_map.get(name) - if functions is None: - sig_error_map[sig] = self.ANNOTATION_ERROR_NOTFOUND - continue - - if name not in symbol_path_map: - # Lazy symbol path resolving. Since the addr2line isn't fast, only - # resolve needed symbol paths. - group_map = collections.defaultdict(list) - for function in functions: - line_info = self.AddressToLine(function.address)[0] - if line_info is None: - continue - - (_, symbol_path, _) = line_info - - # Group the functions with the same symbol signature (symbol name + - # symbol path). Assume they are the same copies and do the same - # annotation operations of them because we don't know which copy is - # indicated by the users. - group_map[symbol_path].append(function) - - symbol_path_map[name] = group_map - - # Symbol matching. - function_group = None - group_map = symbol_path_map[name] - if len(group_map) > 0: - if path is None: - if len(group_map) > 1: - # There is ambiguity but the path isn't specified. - sig_error_map[sig] = self.ANNOTATION_ERROR_AMBIGUOUS - continue - - # No path signature but all symbol signatures of functions are same. - # Assume they are the same functions, so there is no ambiguity. - (function_group,) = group_map.values() - else: - function_group = group_map.get(path) - - if function_group is None: - sig_error_map[sig] = self.ANNOTATION_ERROR_NOTFOUND - continue - - # The function_group is a list of all the same functions (according to - # our assumption) which should be annotated together. - signature_map[sig] = function_group - - return (signature_map, sig_error_map) - - def LoadAnnotation(self): - """Load annotation rules. - - Returns: - Map of add rules, set of remove rules, set of text signatures which can't - be parsed. - """ - # Assume there is no ":" in the path. - # Example: "get_range.lto.2501[driver/accel_kionix.c:327]" - annotation_signature_regex = re.compile( - r'^(?P<name>[^\[]+)(\[(?P<path>[^:]+)(:(?P<linenum>\d+))?\])?$') - - def NormalizeSignature(signature_text): - """Parse and normalize the annotation signature. - - Args: - signature_text: Text of the annotation signature. - - Returns: - (function name, path, line number) of the signature. The path and line - number can be None if not exist. None if failed to parse. - """ - result = annotation_signature_regex.match(signature_text.strip()) - if result is None: - return None - - name_result = self.FUNCTION_PREFIX_NAME_RE.match( - result.group('name').strip()) - if name_result is None: - return None - - path = result.group('path') - if path is not None: - path = os.path.realpath(path.strip()) - - linenum = result.group('linenum') - if linenum is not None: - linenum = int(linenum.strip()) - - return (name_result.group('name').strip(), path, linenum) - - def ExpandArray(dic): - """Parse and expand a symbol array - - Args: - dic: Dictionary for the array annotation - - Returns: - array of (symbol name, None, None). - """ - # TODO(drinkcat): This function is quite inefficient, as it goes through - # the symbol table multiple times. - - begin_name = dic['name'] - end_name = dic['name'] + "_end" - offset = dic['offset'] if 'offset' in dic else 0 - stride = dic['stride'] - - begin_address = None - end_address = None - - for symbol in self.symbols: - if (symbol.name == begin_name): - begin_address = symbol.address - if (symbol.name == end_name): - end_address = symbol.address - - if (not begin_address or not end_address): - return None - - output = [] - # TODO(drinkcat): This is inefficient as we go from address to symbol - # object then to symbol name, and later on we'll go back from symbol name - # to symbol object. - for addr in range(begin_address+offset, end_address, stride): - # TODO(drinkcat): Not all architectures need to drop the first bit. - val = self.rodata[(addr-self.rodata_offset) // 4] & 0xfffffffe - name = None - for symbol in self.symbols: - if (symbol.address == val): - result = self.FUNCTION_PREFIX_NAME_RE.match(symbol.name) - name = result.group('name') - break - - if not name: - raise StackAnalyzerError('Cannot find function for address %s.', - hex(val)) - - output.append((name, None, None)) - - return output - - add_rules = collections.defaultdict(set) - remove_rules = list() - invalid_sigtxts = set() - - if 'add' in self.annotation and self.annotation['add'] is not None: - for src_sigtxt, dst_sigtxts in self.annotation['add'].items(): - src_sig = NormalizeSignature(src_sigtxt) - if src_sig is None: - invalid_sigtxts.add(src_sigtxt) - continue - - for dst_sigtxt in dst_sigtxts: - if isinstance(dst_sigtxt, dict): - dst_sig = ExpandArray(dst_sigtxt) - if dst_sig is None: - invalid_sigtxts.add(str(dst_sigtxt)) - else: - add_rules[src_sig].update(dst_sig) - else: - dst_sig = NormalizeSignature(dst_sigtxt) - if dst_sig is None: - invalid_sigtxts.add(dst_sigtxt) - else: - add_rules[src_sig].add(dst_sig) - - if 'remove' in self.annotation and self.annotation['remove'] is not None: - for sigtxt_path in self.annotation['remove']: - if isinstance(sigtxt_path, str): - # The path has only one vertex. - sigtxt_path = [sigtxt_path] - - if len(sigtxt_path) == 0: - continue - - # Generate multiple remove paths from all the combinations of the - # signatures of each vertex. - sig_paths = [[]] - broken_flag = False - for sigtxt_node in sigtxt_path: - if isinstance(sigtxt_node, str): - # The vertex has only one signature. - sigtxt_set = {sigtxt_node} - elif isinstance(sigtxt_node, list): - # The vertex has multiple signatures. - sigtxt_set = set(sigtxt_node) - else: - # Assume the format of annotation is verified. There should be no - # invalid case. - assert False - - sig_set = set() - for sigtxt in sigtxt_set: - sig = NormalizeSignature(sigtxt) - if sig is None: - invalid_sigtxts.add(sigtxt) - broken_flag = True - elif not broken_flag: - sig_set.add(sig) - - if broken_flag: - continue - - # Append each signature of the current node to the all previous - # remove paths. - sig_paths = [path + [sig] for path in sig_paths for sig in sig_set] - - if not broken_flag: - # All signatures are normalized. The remove path has no error. - remove_rules.extend(sig_paths) - - return (add_rules, remove_rules, invalid_sigtxts) - - def ResolveAnnotation(self, function_map): - """Resolve annotation. - - Args: - function_map: Function map. - - Returns: - Set of added call edges, list of remove paths, set of eliminated - callsite addresses, set of annotation signatures which can't be resolved. - """ - def StringifySignature(signature): - """Stringify the tupled signature. - - Args: - signature: Tupled signature. - - Returns: - Signature string. - """ - (name, path, linenum) = signature - bracket_text = '' - if path is not None: - path = os.path.relpath(path) - if linenum is None: - bracket_text = '[{}]'.format(path) - else: - bracket_text = '[{}:{}]'.format(path, linenum) - - return name + bracket_text - - (add_rules, remove_rules, invalid_sigtxts) = self.LoadAnnotation() - - signature_set = set() - for src_sig, dst_sigs in add_rules.items(): - signature_set.add(src_sig) - signature_set.update(dst_sigs) - - for remove_sigs in remove_rules: - signature_set.update(remove_sigs) - - # Map signatures to functions. - (signature_map, sig_error_map) = self.MapAnnotation(function_map, - signature_set) - - # Build the indirect callsite map indexed by callsite signature. - indirect_map = collections.defaultdict(set) - for function in function_map.values(): - for callsite in function.callsites: - if callsite.target is not None: - continue - - # Found an indirect callsite. - line_info = self.AddressToLine(callsite.address)[0] - if line_info is None: - continue - - (name, path, linenum) = line_info - result = self.FUNCTION_PREFIX_NAME_RE.match(name) - if result is None: - continue - - indirect_map[(result.group('name').strip(), path, linenum)].add( - (function, callsite.address)) - - # Generate the annotation sets. - add_set = set() - remove_list = list() - eliminated_addrs = set() - - for src_sig, dst_sigs in add_rules.items(): - src_funcs = set(signature_map.get(src_sig, [])) - # Try to match the source signature to the indirect callsites. Even if it - # can't be found in disassembly. - indirect_calls = indirect_map.get(src_sig) - if indirect_calls is not None: - for function, callsite_address in indirect_calls: - # Add the caller of the indirect callsite to the source functions. - src_funcs.add(function) - # Assume each callsite can be represented by a unique address. - eliminated_addrs.add(callsite_address) - - if src_sig in sig_error_map: - # Assume the error is always the not found error. Since the signature - # found in indirect callsite map must be a full signature, it can't - # happen the ambiguous error. - assert sig_error_map[src_sig] == self.ANNOTATION_ERROR_NOTFOUND - # Found in inline stack, remove the not found error. - del sig_error_map[src_sig] - - for dst_sig in dst_sigs: - dst_funcs = signature_map.get(dst_sig) - if dst_funcs is None: - continue - - # Duplicate the call edge for all the same source and destination - # functions. - for src_func in src_funcs: - for dst_func in dst_funcs: - add_set.add((src_func, dst_func)) - - for remove_sigs in remove_rules: - # Since each signature can be mapped to multiple functions, generate - # multiple remove paths from all the combinations of these functions. - remove_paths = [[]] - skip_flag = False - for remove_sig in remove_sigs: - # Transform each signature to the corresponding functions. - remove_funcs = signature_map.get(remove_sig) - if remove_funcs is None: - # There is an unresolved signature in the remove path. Ignore the - # whole broken remove path. - skip_flag = True - break - else: - # Append each function of the current signature to the all previous - # remove paths. - remove_paths = [p + [f] for p in remove_paths for f in remove_funcs] - - if skip_flag: - # Ignore the broken remove path. - continue - - for remove_path in remove_paths: - # Deduplicate the remove paths. - if remove_path not in remove_list: - remove_list.append(remove_path) - - # Format the error messages. - failed_sigtxts = set() - for sigtxt in invalid_sigtxts: - failed_sigtxts.add((sigtxt, self.ANNOTATION_ERROR_INVALID)) - - for sig, error in sig_error_map.items(): - failed_sigtxts.add((StringifySignature(sig), error)) - - return (add_set, remove_list, eliminated_addrs, failed_sigtxts) - - def PreprocessAnnotation(self, function_map, add_set, remove_list, - eliminated_addrs): - """Preprocess the annotation and callgraph. - - Add the missing call edges, and delete simple remove paths (the paths have - one or two vertices) from the function_map. - - Eliminate the annotated indirect callsites. - - Return the remaining remove list. - - Args: - function_map: Function map. - add_set: Set of missing call edges. - remove_list: List of remove paths. - eliminated_addrs: Set of eliminated callsite addresses. - - Returns: - List of remaining remove paths. - """ - def CheckEdge(path): - """Check if all edges of the path are on the callgraph. - - Args: - path: Path. - - Returns: - True or False. - """ - for index in range(len(path) - 1): - if (path[index], path[index + 1]) not in edge_set: - return False - - return True - - for src_func, dst_func in add_set: - # TODO(cheyuw): Support tailing call annotation. - src_func.callsites.append( - Callsite(None, dst_func.address, False, dst_func)) - - # Delete simple remove paths. - remove_simple = set(tuple(p) for p in remove_list if len(p) <= 2) - edge_set = set() - for function in function_map.values(): - cleaned_callsites = [] - for callsite in function.callsites: - if ((callsite.callee,) in remove_simple or - (function, callsite.callee) in remove_simple): - continue - - if callsite.target is None and callsite.address in eliminated_addrs: - continue - - cleaned_callsites.append(callsite) - if callsite.callee is not None: - edge_set.add((function, callsite.callee)) - - function.callsites = cleaned_callsites - - return [p for p in remove_list if len(p) >= 3 and CheckEdge(p)] - - def AnalyzeCallGraph(self, function_map, remove_list): - """Analyze callgraph. - - It will update the max stack size and path for each function. - - Args: - function_map: Function map. - remove_list: List of remove paths. - - Returns: - List of function cycles. - """ - def Traverse(curr_state): - """Traverse the callgraph and calculate the max stack usages of functions. - - Args: - curr_state: Current state. - - Returns: - SCC lowest link. - """ - scc_index = scc_index_counter[0] - scc_index_counter[0] += 1 - scc_index_map[curr_state] = scc_index - scc_lowlink = scc_index - scc_stack.append(curr_state) - # Push the current state in the stack. We can use a set to maintain this - # because the stacked states are unique; otherwise we will find a cycle - # first. - stacked_states.add(curr_state) - - (curr_address, curr_positions) = curr_state - curr_func = function_map[curr_address] - - invalid_flag = False - new_positions = list(curr_positions) - for index, position in enumerate(curr_positions): - remove_path = remove_list[index] - - # The position of each remove path in the state is the length of the - # longest matching path between the prefix of the remove path and the - # suffix of the current traversing path. We maintain this length when - # appending the next callee to the traversing path. And it can be used - # to check if the remove path appears in the traversing path. - - # TODO(cheyuw): Implement KMP algorithm to match remove paths - # efficiently. - if remove_path[position] is curr_func: - # Matches the current function, extend the length. - new_positions[index] = position + 1 - if new_positions[index] == len(remove_path): - # The length of the longest matching path is equal to the length of - # the remove path, which means the suffix of the current traversing - # path matches the remove path. - invalid_flag = True - break - - else: - # We can't get the new longest matching path by extending the previous - # one directly. Fallback to search the new longest matching path. - - # If we can't find any matching path in the following search, reset - # the matching length to 0. - new_positions[index] = 0 - - # We want to find the new longest matching prefix of remove path with - # the suffix of the current traversing path. Because the new longest - # matching path won't be longer than the prevous one now, and part of - # the suffix matches the prefix of remove path, we can get the needed - # suffix from the previous matching prefix of the invalid path. - suffix = remove_path[:position] + [curr_func] - for offset in range(1, len(suffix)): - length = position - offset - if remove_path[:length] == suffix[offset:]: - new_positions[index] = length - break - - new_positions = tuple(new_positions) - - # If the current suffix is invalid, set the max stack usage to 0. - max_stack_usage = 0 - max_callee_state = None - self_loop = False - - if not invalid_flag: - # Max stack usage is at least equal to the stack frame. - max_stack_usage = curr_func.stack_frame - for callsite in curr_func.callsites: - callee = callsite.callee - if callee is None: - continue - - callee_state = (callee.address, new_positions) - if callee_state not in scc_index_map: - # Unvisited state. - scc_lowlink = min(scc_lowlink, Traverse(callee_state)) - elif callee_state in stacked_states: - # The state is shown in the stack. There is a cycle. - sub_stack_usage = 0 - scc_lowlink = min(scc_lowlink, scc_index_map[callee_state]) - if callee_state == curr_state: - self_loop = True - - done_result = done_states.get(callee_state) - if done_result is not None: - # Already done this state and use its result. If the state reaches a - # cycle, reusing the result will cause inaccuracy (the stack usage - # of cycle depends on where the entrance is). But it's fine since we - # can't get accurate stack usage under this situation, and we rely - # on user-provided annotations to break the cycle, after which the - # result will be accurate again. - (sub_stack_usage, _) = done_result - - if callsite.is_tail: - # For tailing call, since the callee reuses the stack frame of the - # caller, choose the larger one directly. - stack_usage = max(curr_func.stack_frame, sub_stack_usage) - else: - stack_usage = curr_func.stack_frame + sub_stack_usage - - if stack_usage > max_stack_usage: - max_stack_usage = stack_usage - max_callee_state = callee_state - - if scc_lowlink == scc_index: - group = [] - while scc_stack[-1] != curr_state: - scc_state = scc_stack.pop() - stacked_states.remove(scc_state) - group.append(scc_state) - - scc_stack.pop() - stacked_states.remove(curr_state) - - # If the cycle is not empty, record it. - if len(group) > 0 or self_loop: - group.append(curr_state) - cycle_groups.append(group) - - # Store the done result. - done_states[curr_state] = (max_stack_usage, max_callee_state) - - if curr_positions == initial_positions: - # If the current state is initial state, we traversed the callgraph by - # using the current function as start point. Update the stack usage of - # the function. - # If the function matches a single vertex remove path, this will set its - # max stack usage to 0, which is not expected (we still calculate its - # max stack usage, but prevent any function from calling it). However, - # all the single vertex remove paths have been preprocessed and removed. - curr_func.stack_max_usage = max_stack_usage - - # Reconstruct the max stack path by traversing the state transitions. - max_stack_path = [curr_func] - callee_state = max_callee_state - while callee_state is not None: - # The first element of state tuple is function address. - max_stack_path.append(function_map[callee_state[0]]) - done_result = done_states.get(callee_state) - # All of the descendants should be done. - assert done_result is not None - (_, callee_state) = done_result - - curr_func.stack_max_path = max_stack_path - - return scc_lowlink - - # The state is the concatenation of the current function address and the - # state of matching position. - initial_positions = (0,) * len(remove_list) - done_states = {} - stacked_states = set() - scc_index_counter = [0] - scc_index_map = {} - scc_stack = [] - cycle_groups = [] - for function in function_map.values(): - if function.stack_max_usage is None: - Traverse((function.address, initial_positions)) - - cycle_functions = [] - for group in cycle_groups: - cycle = set(function_map[state[0]] for state in group) - if cycle not in cycle_functions: - cycle_functions.append(cycle) - - return cycle_functions - - def Analyze(self): - """Run the stack analysis. - - Raises: - StackAnalyzerError: If disassembly fails. - """ - def OutputInlineStack(address, prefix=''): - """Output beautiful inline stack. - - Args: - address: Address. - prefix: Prefix of each line. - - Returns: - Key for sorting, output text - """ - line_infos = self.AddressToLine(address, True) - - if line_infos[0] is None: - order_key = (None, None) - else: - (_, path, linenum) = line_infos[0] - order_key = (linenum, path) - - line_texts = [] - for line_info in reversed(line_infos): - if line_info is None: - (function_name, path, linenum) = ('??', '??', 0) - else: - (function_name, path, linenum) = line_info - - line_texts.append('{}[{}:{}]'.format(function_name, - os.path.relpath(path), - linenum)) - - output = '{}-> {} {:x}\n'.format(prefix, line_texts[0], address) - for depth, line_text in enumerate(line_texts[1:]): - output += '{} {}- {}\n'.format(prefix, ' ' * depth, line_text) - - # Remove the last newline character. - return (order_key, output.rstrip('\n')) - - # Analyze disassembly. - try: - disasm_text = subprocess.check_output([self.options.objdump, - '-d', - self.options.elf_path], - encoding='utf-8') - except subprocess.CalledProcessError: - raise StackAnalyzerError('objdump failed to disassemble.') - except OSError: - raise StackAnalyzerError('Failed to run objdump.') - - function_map = self.AnalyzeDisassembly(disasm_text) - result = self.ResolveAnnotation(function_map) - (add_set, remove_list, eliminated_addrs, failed_sigtxts) = result - remove_list = self.PreprocessAnnotation(function_map, - add_set, - remove_list, - eliminated_addrs) - cycle_functions = self.AnalyzeCallGraph(function_map, remove_list) - - # Print the results of task-aware stack analysis. - extra_stack_frame = self.annotation.get('exception_frame_size', - DEFAULT_EXCEPTION_FRAME_SIZE) - for task in self.tasklist: - routine_func = function_map[task.routine_address] - print('Task: {}, Max size: {} ({} + {}), Allocated size: {}'.format( - task.name, - routine_func.stack_max_usage + extra_stack_frame, - routine_func.stack_max_usage, - extra_stack_frame, - task.stack_max_size)) - - print('Call Trace:') - max_stack_path = routine_func.stack_max_path - # Assume the routine function is resolved. - assert max_stack_path is not None - for depth, curr_func in enumerate(max_stack_path): - line_info = self.AddressToLine(curr_func.address)[0] - if line_info is None: - (path, linenum) = ('??', 0) - else: - (_, path, linenum) = line_info - - print(' {} ({}) [{}:{}] {:x}'.format(curr_func.name, - curr_func.stack_frame, - os.path.relpath(path), - linenum, - curr_func.address)) - - if depth + 1 < len(max_stack_path): - succ_func = max_stack_path[depth + 1] - text_list = [] - for callsite in curr_func.callsites: - if callsite.callee is succ_func: - indent_prefix = ' ' - if callsite.address is None: - order_text = (None, '{}-> [annotation]'.format(indent_prefix)) - else: - order_text = OutputInlineStack(callsite.address, indent_prefix) - - text_list.append(order_text) - - for _, text in sorted(text_list, key=lambda item: item[0]): - print(text) - - print('Unresolved indirect callsites:') - for function in function_map.values(): - indirect_callsites = [] - for callsite in function.callsites: - if callsite.target is None: - indirect_callsites.append(callsite.address) - - if len(indirect_callsites) > 0: - print(' In function {}:'.format(function.name)) - text_list = [] - for address in indirect_callsites: - text_list.append(OutputInlineStack(address, ' ')) - - for _, text in sorted(text_list, key=lambda item: item[0]): - print(text) - - print('Unresolved annotation signatures:') - for sigtxt, error in failed_sigtxts: - print(' {}: {}'.format(sigtxt, error)) - - if len(cycle_functions) > 0: - print('There are cycles in the following function sets:') - for functions in cycle_functions: - print('[{}]'.format(', '.join(function.name for function in functions))) - - -def ParseArgs(): - """Parse commandline arguments. - - Returns: - options: Namespace from argparse.parse_args(). - """ - parser = argparse.ArgumentParser(description="EC firmware stack analyzer.") - parser.add_argument('elf_path', help="the path of EC firmware ELF") - parser.add_argument('--export_taskinfo', required=True, - help="the path of export_taskinfo.so utility") - parser.add_argument('--section', required=True, help='the section.', - choices=[SECTION_RO, SECTION_RW]) - parser.add_argument('--objdump', default='objdump', - help='the path of objdump') - parser.add_argument('--addr2line', default='addr2line', - help='the path of addr2line') - parser.add_argument('--annotation', default=None, - help='the path of annotation file') - - # TODO(cheyuw): Add an option for dumping stack usage of all functions. - - return parser.parse_args() - - -def ParseSymbolText(symbol_text): - """Parse the content of the symbol text. - - Args: - symbol_text: Text of the symbols. - - Returns: - symbols: Symbol list. - """ - # Example: "10093064 g F .text 0000015c .hidden hook_task" - symbol_regex = re.compile(r'^(?P<address>[0-9A-Fa-f]+)\s+[lwg]\s+' - r'((?P<type>[OF])\s+)?\S+\s+' - r'(?P<size>[0-9A-Fa-f]+)\s+' - r'(\S+\s+)?(?P<name>\S+)$') - - symbols = [] - for line in symbol_text.splitlines(): - line = line.strip() - result = symbol_regex.match(line) - if result is not None: - address = int(result.group('address'), 16) - symtype = result.group('type') - if symtype is None: - symtype = 'O' - - size = int(result.group('size'), 16) - name = result.group('name') - symbols.append(Symbol(address, symtype, size, name)) - - return symbols - - -def ParseRoDataText(rodata_text): - """Parse the content of rodata - - Args: - symbol_text: Text of the rodata dump. - - Returns: - symbols: Symbol list. - """ - # Examples: 8018ab0 00040048 00010000 10020000 4b8e0108 ...H........K... - # 100a7294 00000000 00000000 01000000 ............ - - base_offset = None - offset = None - rodata = [] - for line in rodata_text.splitlines(): - line = line.strip() - space = line.find(' ') - if space < 0: - continue - try: - address = int(line[0:space], 16) - except ValueError: - continue - - if not base_offset: - base_offset = address - offset = address - elif address != offset: - raise StackAnalyzerError('objdump of rodata not contiguous.') - - for i in range(0, 4): - num = line[(space + 1 + i*9):(space + 9 + i*9)] - if len(num.strip()) > 0: - val = int(num, 16) - else: - val = 0 - # TODO(drinkcat): Not all platforms are necessarily big-endian - rodata.append((val & 0x000000ff) << 24 | - (val & 0x0000ff00) << 8 | - (val & 0x00ff0000) >> 8 | - (val & 0xff000000) >> 24) - - offset = offset + 4*4 - - return (base_offset, rodata) - - -def LoadTasklist(section, export_taskinfo, symbols): - """Load the task information. - - Args: - section: Section (RO | RW). - export_taskinfo: Handle of export_taskinfo.so. - symbols: Symbol list. - - Returns: - tasklist: Task list. - """ - - TaskInfoPointer = ctypes.POINTER(TaskInfo) - taskinfos = TaskInfoPointer() - if section == SECTION_RO: - get_taskinfos_func = export_taskinfo.get_ro_taskinfos - else: - get_taskinfos_func = export_taskinfo.get_rw_taskinfos - - taskinfo_num = get_taskinfos_func(ctypes.pointer(taskinfos)) - - tasklist = [] - for index in range(taskinfo_num): - taskinfo = taskinfos[index] - tasklist.append(Task(taskinfo.name.decode('utf-8'), - taskinfo.routine.decode('utf-8'), - taskinfo.stack_size)) - - # Resolve routine address for each task. It's more efficient to resolve all - # routine addresses of tasks together. - routine_map = dict((task.routine_name, None) for task in tasklist) - - for symbol in symbols: - # Resolve task routine address. - if symbol.name in routine_map: - # Assume the symbol of routine is unique. - assert routine_map[symbol.name] is None - routine_map[symbol.name] = symbol.address - - for task in tasklist: - address = routine_map[task.routine_name] - # Assume we have resolved all routine addresses. - assert address is not None - task.routine_address = address - - return tasklist - - -def main(): - """Main function.""" - try: - options = ParseArgs() - - # Load annotation config. - if options.annotation is None: - annotation = {} - elif not os.path.exists(options.annotation): - print('Warning: Annotation file {} does not exist.' - .format(options.annotation)) - annotation = {} - else: - try: - with open(options.annotation, 'r') as annotation_file: - annotation = yaml.safe_load(annotation_file) - - except yaml.YAMLError: - raise StackAnalyzerError('Failed to parse annotation file {}.' - .format(options.annotation)) - except IOError: - raise StackAnalyzerError('Failed to open annotation file {}.' - .format(options.annotation)) - - # TODO(cheyuw): Do complete annotation format verification. - if not isinstance(annotation, dict): - raise StackAnalyzerError('Invalid annotation file {}.' - .format(options.annotation)) - - # Generate and parse the symbols. - try: - symbol_text = subprocess.check_output([options.objdump, - '-t', - options.elf_path], - encoding='utf-8') - rodata_text = subprocess.check_output([options.objdump, - '-s', - '-j', '.rodata', - options.elf_path], - encoding='utf-8') - except subprocess.CalledProcessError: - raise StackAnalyzerError('objdump failed to dump symbol table or rodata.') - except OSError: - raise StackAnalyzerError('Failed to run objdump.') - - symbols = ParseSymbolText(symbol_text) - rodata = ParseRoDataText(rodata_text) - - # Load the tasklist. - try: - export_taskinfo = ctypes.CDLL(options.export_taskinfo) - except OSError: - raise StackAnalyzerError('Failed to load export_taskinfo.') - - tasklist = LoadTasklist(options.section, export_taskinfo, symbols) - - analyzer = StackAnalyzer(options, symbols, rodata, tasklist, annotation) - analyzer.Analyze() - except StackAnalyzerError as e: - print('Error: {}'.format(e)) - - -if __name__ == '__main__': - main() diff --git a/extra/stack_analyzer/stack_analyzer_unittest.py b/extra/stack_analyzer/stack_analyzer_unittest.py deleted file mode 100755 index c36fa9da45..0000000000 --- a/extra/stack_analyzer/stack_analyzer_unittest.py +++ /dev/null @@ -1,830 +0,0 @@ -#!/usr/bin/env python3 -# Copyright 2017 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. -# -# Ignore indention messages, since legacy scripts use 2 spaces instead of 4. -# pylint: disable=bad-indentation,docstring-section-indent -# pylint: disable=docstring-trailing-quotes - -"""Tests for Stack Analyzer classes and functions.""" - -from __future__ import print_function - -import mock -import os -import subprocess -import unittest - -import stack_analyzer as sa - - -class ObjectTest(unittest.TestCase): - """Tests for classes of basic objects.""" - - def testTask(self): - task_a = sa.Task('a', 'a_task', 1234) - task_b = sa.Task('b', 'b_task', 5678, 0x1000) - self.assertEqual(task_a, task_a) - self.assertNotEqual(task_a, task_b) - self.assertNotEqual(task_a, None) - - def testSymbol(self): - symbol_a = sa.Symbol(0x1234, 'F', 32, 'a') - symbol_b = sa.Symbol(0x234, 'O', 42, 'b') - self.assertEqual(symbol_a, symbol_a) - self.assertNotEqual(symbol_a, symbol_b) - self.assertNotEqual(symbol_a, None) - - def testCallsite(self): - callsite_a = sa.Callsite(0x1002, 0x3000, False) - callsite_b = sa.Callsite(0x1002, 0x3000, True) - self.assertEqual(callsite_a, callsite_a) - self.assertNotEqual(callsite_a, callsite_b) - self.assertNotEqual(callsite_a, None) - - def testFunction(self): - func_a = sa.Function(0x100, 'a', 0, []) - func_b = sa.Function(0x200, 'b', 0, []) - self.assertEqual(func_a, func_a) - self.assertNotEqual(func_a, func_b) - self.assertNotEqual(func_a, None) - - -class ArmAnalyzerTest(unittest.TestCase): - """Tests for class ArmAnalyzer.""" - - def AppendConditionCode(self, opcodes): - rets = [] - for opcode in opcodes: - rets.extend(opcode + cc for cc in sa.ArmAnalyzer.CONDITION_CODES) - - return rets - - def testInstructionMatching(self): - jump_list = self.AppendConditionCode(['b', 'bx']) - jump_list += (list(opcode + '.n' for opcode in jump_list) + - list(opcode + '.w' for opcode in jump_list)) - for opcode in jump_list: - self.assertIsNotNone(sa.ArmAnalyzer.JUMP_OPCODE_RE.match(opcode)) - - self.assertIsNone(sa.ArmAnalyzer.JUMP_OPCODE_RE.match('bl')) - self.assertIsNone(sa.ArmAnalyzer.JUMP_OPCODE_RE.match('blx')) - - cbz_list = ['cbz', 'cbnz', 'cbz.n', 'cbnz.n', 'cbz.w', 'cbnz.w'] - for opcode in cbz_list: - self.assertIsNotNone(sa.ArmAnalyzer.CBZ_CBNZ_OPCODE_RE.match(opcode)) - - self.assertIsNone(sa.ArmAnalyzer.CBZ_CBNZ_OPCODE_RE.match('cbn')) - - call_list = self.AppendConditionCode(['bl', 'blx']) - call_list += list(opcode + '.n' for opcode in call_list) - for opcode in call_list: - self.assertIsNotNone(sa.ArmAnalyzer.CALL_OPCODE_RE.match(opcode)) - - self.assertIsNone(sa.ArmAnalyzer.CALL_OPCODE_RE.match('ble')) - - result = sa.ArmAnalyzer.CALL_OPERAND_RE.match('53f90 <get_time+0x18>') - self.assertIsNotNone(result) - self.assertEqual(result.group(1), '53f90') - self.assertEqual(result.group(2), 'get_time+0x18') - - result = sa.ArmAnalyzer.CBZ_CBNZ_OPERAND_RE.match('r6, 53f90 <get+0x0>') - self.assertIsNotNone(result) - self.assertEqual(result.group(1), '53f90') - self.assertEqual(result.group(2), 'get+0x0') - - self.assertIsNotNone(sa.ArmAnalyzer.PUSH_OPCODE_RE.match('push')) - self.assertIsNone(sa.ArmAnalyzer.PUSH_OPCODE_RE.match('pushal')) - self.assertIsNotNone(sa.ArmAnalyzer.STM_OPCODE_RE.match('stmdb')) - self.assertIsNone(sa.ArmAnalyzer.STM_OPCODE_RE.match('lstm')) - self.assertIsNotNone(sa.ArmAnalyzer.SUB_OPCODE_RE.match('sub')) - self.assertIsNotNone(sa.ArmAnalyzer.SUB_OPCODE_RE.match('subs')) - self.assertIsNotNone(sa.ArmAnalyzer.SUB_OPCODE_RE.match('subw')) - self.assertIsNotNone(sa.ArmAnalyzer.SUB_OPCODE_RE.match('sub.w')) - self.assertIsNotNone(sa.ArmAnalyzer.SUB_OPCODE_RE.match('subs.w')) - - result = sa.ArmAnalyzer.SUB_OPERAND_RE.match('sp, sp, #1668 ; 0x684') - self.assertIsNotNone(result) - self.assertEqual(result.group(1), '1668') - result = sa.ArmAnalyzer.SUB_OPERAND_RE.match('sp, #1668') - self.assertIsNotNone(result) - self.assertEqual(result.group(1), '1668') - self.assertIsNone(sa.ArmAnalyzer.SUB_OPERAND_RE.match('sl, #1668')) - - def testAnalyzeFunction(self): - analyzer = sa.ArmAnalyzer() - symbol = sa.Symbol(0x10, 'F', 0x100, 'foo') - instructions = [ - (0x10, 'push', '{r4, r5, r6, r7, lr}'), - (0x12, 'subw', 'sp, sp, #16 ; 0x10'), - (0x16, 'movs', 'lr, r1'), - (0x18, 'beq.n', '26 <foo+0x26>'), - (0x1a, 'bl', '30 <foo+0x30>'), - (0x1e, 'bl', 'deadbeef <bar>'), - (0x22, 'blx', '0 <woo>'), - (0x26, 'push', '{r1}'), - (0x28, 'stmdb', 'sp!, {r4, r5, r6, r7, r8, r9, lr}'), - (0x2c, 'stmdb', 'sp!, {r4}'), - (0x30, 'stmdb', 'sp, {r4}'), - (0x34, 'bx.n', '10 <foo>'), - (0x36, 'bx.n', 'r3'), - (0x38, 'ldr', 'pc, [r10]'), - ] - (size, callsites) = analyzer.AnalyzeFunction(symbol, instructions) - self.assertEqual(size, 72) - expect_callsites = [sa.Callsite(0x1e, 0xdeadbeef, False), - sa.Callsite(0x22, 0x0, False), - sa.Callsite(0x34, 0x10, True), - sa.Callsite(0x36, None, True), - sa.Callsite(0x38, None, True)] - self.assertEqual(callsites, expect_callsites) - - -class StackAnalyzerTest(unittest.TestCase): - """Tests for class StackAnalyzer.""" - - def setUp(self): - symbols = [sa.Symbol(0x1000, 'F', 0x15C, 'hook_task'), - sa.Symbol(0x2000, 'F', 0x51C, 'console_task'), - sa.Symbol(0x3200, 'O', 0x124, '__just_data'), - sa.Symbol(0x4000, 'F', 0x11C, 'touchpad_calc'), - sa.Symbol(0x5000, 'F', 0x12C, 'touchpad_calc.constprop.42'), - sa.Symbol(0x12000, 'F', 0x13C, 'trackpad_range'), - sa.Symbol(0x13000, 'F', 0x200, 'inlined_mul'), - sa.Symbol(0x13100, 'F', 0x200, 'inlined_mul'), - sa.Symbol(0x13100, 'F', 0x200, 'inlined_mul_alias'), - sa.Symbol(0x20000, 'O', 0x0, '__array'), - sa.Symbol(0x20010, 'O', 0x0, '__array_end'), - ] - tasklist = [sa.Task('HOOKS', 'hook_task', 2048, 0x1000), - sa.Task('CONSOLE', 'console_task', 460, 0x2000)] - # Array at 0x20000 that contains pointers to hook_task and console_task, - # with stride=8, offset=4 - rodata = (0x20000, [ 0xDEAD1000, 0x00001000, 0xDEAD2000, 0x00002000 ]) - options = mock.MagicMock(elf_path='./ec.RW.elf', - export_taskinfo='fake', - section='RW', - objdump='objdump', - addr2line='addr2line', - annotation=None) - self.analyzer = sa.StackAnalyzer(options, symbols, rodata, tasklist, {}) - - def testParseSymbolText(self): - symbol_text = ( - '0 g F .text e8 Foo\n' - '0000dead w F .text 000000e8 .hidden Bar\n' - 'deadbeef l O .bss 00000004 .hidden Woooo\n' - 'deadbee g O .rodata 00000008 __Hooo_ooo\n' - 'deadbee g .rodata 00000000 __foo_doo_coo_end\n' - ) - symbols = sa.ParseSymbolText(symbol_text) - expect_symbols = [sa.Symbol(0x0, 'F', 0xe8, 'Foo'), - sa.Symbol(0xdead, 'F', 0xe8, 'Bar'), - sa.Symbol(0xdeadbeef, 'O', 0x4, 'Woooo'), - sa.Symbol(0xdeadbee, 'O', 0x8, '__Hooo_ooo'), - sa.Symbol(0xdeadbee, 'O', 0x0, '__foo_doo_coo_end')] - self.assertEqual(symbols, expect_symbols) - - def testParseRoData(self): - rodata_text = ( - '\n' - 'Contents of section .rodata:\n' - ' 20000 dead1000 00100000 dead2000 00200000 He..f.He..s.\n' - ) - rodata = sa.ParseRoDataText(rodata_text) - expect_rodata = (0x20000, - [ 0x0010adde, 0x00001000, 0x0020adde, 0x00002000 ]) - self.assertEqual(rodata, expect_rodata) - - def testLoadTasklist(self): - def tasklist_to_taskinfos(pointer, tasklist): - taskinfos = [] - for task in tasklist: - taskinfos.append(sa.TaskInfo(name=task.name.encode('utf-8'), - routine=task.routine_name.encode('utf-8'), - stack_size=task.stack_max_size)) - - TaskInfoArray = sa.TaskInfo * len(taskinfos) - pointer.contents.contents = TaskInfoArray(*taskinfos) - return len(taskinfos) - - def ro_taskinfos(pointer): - return tasklist_to_taskinfos(pointer, expect_ro_tasklist) - - def rw_taskinfos(pointer): - return tasklist_to_taskinfos(pointer, expect_rw_tasklist) - - expect_ro_tasklist = [ - sa.Task('HOOKS', 'hook_task', 2048, 0x1000), - ] - - expect_rw_tasklist = [ - sa.Task('HOOKS', 'hook_task', 2048, 0x1000), - sa.Task('WOOKS', 'hook_task', 4096, 0x1000), - sa.Task('CONSOLE', 'console_task', 460, 0x2000), - ] - - export_taskinfo = mock.MagicMock( - get_ro_taskinfos=mock.MagicMock(side_effect=ro_taskinfos), - get_rw_taskinfos=mock.MagicMock(side_effect=rw_taskinfos)) - - tasklist = sa.LoadTasklist('RO', export_taskinfo, self.analyzer.symbols) - self.assertEqual(tasklist, expect_ro_tasklist) - tasklist = sa.LoadTasklist('RW', export_taskinfo, self.analyzer.symbols) - self.assertEqual(tasklist, expect_rw_tasklist) - - def testResolveAnnotation(self): - self.analyzer.annotation = {} - (add_rules, remove_rules, invalid_sigtxts) = self.analyzer.LoadAnnotation() - self.assertEqual(add_rules, {}) - self.assertEqual(remove_rules, []) - self.assertEqual(invalid_sigtxts, set()) - - self.analyzer.annotation = {'add': None, 'remove': None} - (add_rules, remove_rules, invalid_sigtxts) = self.analyzer.LoadAnnotation() - self.assertEqual(add_rules, {}) - self.assertEqual(remove_rules, []) - self.assertEqual(invalid_sigtxts, set()) - - self.analyzer.annotation = { - 'add': None, - 'remove': [ - [['a', 'b'], ['0', '[', '2'], 'x'], - [['a', 'b[x:3]'], ['0', '1', '2'], 'x'], - ], - } - (add_rules, remove_rules, invalid_sigtxts) = self.analyzer.LoadAnnotation() - self.assertEqual(add_rules, {}) - self.assertEqual(list.sort(remove_rules), list.sort([ - [('a', None, None), ('1', None, None), ('x', None, None)], - [('a', None, None), ('0', None, None), ('x', None, None)], - [('a', None, None), ('2', None, None), ('x', None, None)], - [('b', os.path.abspath('x'), 3), ('1', None, None), ('x', None, None)], - [('b', os.path.abspath('x'), 3), ('0', None, None), ('x', None, None)], - [('b', os.path.abspath('x'), 3), ('2', None, None), ('x', None, None)], - ])) - self.assertEqual(invalid_sigtxts, {'['}) - - self.analyzer.annotation = { - 'add': { - 'touchpad_calc': [ dict(name='__array', stride=8, offset=4) ], - } - } - (add_rules, remove_rules, invalid_sigtxts) = self.analyzer.LoadAnnotation() - self.assertEqual(add_rules, { - ('touchpad_calc', None, None): - set([('console_task', None, None), ('hook_task', None, None)])}) - - funcs = { - 0x1000: sa.Function(0x1000, 'hook_task', 0, []), - 0x2000: sa.Function(0x2000, 'console_task', 0, []), - 0x4000: sa.Function(0x4000, 'touchpad_calc', 0, []), - 0x5000: sa.Function(0x5000, 'touchpad_calc.constprop.42', 0, []), - 0x13000: sa.Function(0x13000, 'inlined_mul', 0, []), - 0x13100: sa.Function(0x13100, 'inlined_mul', 0, []), - } - funcs[0x1000].callsites = [ - sa.Callsite(0x1002, None, False, None)] - # Set address_to_line_cache to fake the results of addr2line. - self.analyzer.address_to_line_cache = { - (0x1000, False): [('hook_task', os.path.abspath('a.c'), 10)], - (0x1002, False): [('toot_calc', os.path.abspath('t.c'), 1234)], - (0x2000, False): [('console_task', os.path.abspath('b.c'), 20)], - (0x4000, False): [('toudhpad_calc', os.path.abspath('a.c'), 20)], - (0x5000, False): [ - ('touchpad_calc.constprop.42', os.path.abspath('b.c'), 40)], - (0x12000, False): [('trackpad_range', os.path.abspath('t.c'), 10)], - (0x13000, False): [('inlined_mul', os.path.abspath('x.c'), 12)], - (0x13100, False): [('inlined_mul', os.path.abspath('x.c'), 12)], - } - self.analyzer.annotation = { - 'add': { - 'hook_task.lto.573': ['touchpad_calc.lto.2501[a.c]'], - 'console_task': ['touchpad_calc[b.c]', 'inlined_mul_alias'], - 'hook_task[q.c]': ['hook_task'], - 'inlined_mul[x.c]': ['inlined_mul'], - 'toot_calc[t.c:1234]': ['hook_task'], - }, - 'remove': [ - ['touchpad?calc['], - 'touchpad_calc', - ['touchpad_calc[a.c]'], - ['task_unk[a.c]'], - ['touchpad_calc[x/a.c]'], - ['trackpad_range'], - ['inlined_mul'], - ['inlined_mul', 'console_task', 'touchpad_calc[a.c]'], - ['inlined_mul', 'inlined_mul_alias', 'console_task'], - ['inlined_mul', 'inlined_mul_alias', 'console_task'], - ], - } - (add_rules, remove_rules, invalid_sigtxts) = self.analyzer.LoadAnnotation() - self.assertEqual(invalid_sigtxts, {'touchpad?calc['}) - - signature_set = set() - for src_sig, dst_sigs in add_rules.items(): - signature_set.add(src_sig) - signature_set.update(dst_sigs) - - for remove_sigs in remove_rules: - signature_set.update(remove_sigs) - - (signature_map, failed_sigs) = self.analyzer.MapAnnotation(funcs, - signature_set) - result = self.analyzer.ResolveAnnotation(funcs) - (add_set, remove_list, eliminated_addrs, failed_sigs) = result - - expect_signature_map = { - ('hook_task', None, None): {funcs[0x1000]}, - ('touchpad_calc', os.path.abspath('a.c'), None): {funcs[0x4000]}, - ('touchpad_calc', os.path.abspath('b.c'), None): {funcs[0x5000]}, - ('console_task', None, None): {funcs[0x2000]}, - ('inlined_mul_alias', None, None): {funcs[0x13100]}, - ('inlined_mul', os.path.abspath('x.c'), None): {funcs[0x13000], - funcs[0x13100]}, - ('inlined_mul', None, None): {funcs[0x13000], funcs[0x13100]}, - } - self.assertEqual(len(signature_map), len(expect_signature_map)) - for sig, funclist in signature_map.items(): - self.assertEqual(set(funclist), expect_signature_map[sig]) - - self.assertEqual(add_set, { - (funcs[0x1000], funcs[0x4000]), - (funcs[0x1000], funcs[0x1000]), - (funcs[0x2000], funcs[0x5000]), - (funcs[0x2000], funcs[0x13100]), - (funcs[0x13000], funcs[0x13000]), - (funcs[0x13000], funcs[0x13100]), - (funcs[0x13100], funcs[0x13000]), - (funcs[0x13100], funcs[0x13100]), - }) - expect_remove_list = [ - [funcs[0x4000]], - [funcs[0x13000]], - [funcs[0x13100]], - [funcs[0x13000], funcs[0x2000], funcs[0x4000]], - [funcs[0x13100], funcs[0x2000], funcs[0x4000]], - [funcs[0x13000], funcs[0x13100], funcs[0x2000]], - [funcs[0x13100], funcs[0x13100], funcs[0x2000]], - ] - self.assertEqual(len(remove_list), len(expect_remove_list)) - for remove_path in remove_list: - self.assertTrue(remove_path in expect_remove_list) - - self.assertEqual(eliminated_addrs, {0x1002}) - self.assertEqual(failed_sigs, { - ('touchpad?calc[', sa.StackAnalyzer.ANNOTATION_ERROR_INVALID), - ('touchpad_calc', sa.StackAnalyzer.ANNOTATION_ERROR_AMBIGUOUS), - ('hook_task[q.c]', sa.StackAnalyzer.ANNOTATION_ERROR_NOTFOUND), - ('task_unk[a.c]', sa.StackAnalyzer.ANNOTATION_ERROR_NOTFOUND), - ('touchpad_calc[x/a.c]', sa.StackAnalyzer.ANNOTATION_ERROR_NOTFOUND), - ('trackpad_range', sa.StackAnalyzer.ANNOTATION_ERROR_NOTFOUND), - }) - - def testPreprocessAnnotation(self): - funcs = { - 0x1000: sa.Function(0x1000, 'hook_task', 0, []), - 0x2000: sa.Function(0x2000, 'console_task', 0, []), - 0x4000: sa.Function(0x4000, 'touchpad_calc', 0, []), - } - funcs[0x1000].callsites = [ - sa.Callsite(0x1002, 0x1000, False, funcs[0x1000])] - funcs[0x2000].callsites = [ - sa.Callsite(0x2002, 0x1000, False, funcs[0x1000]), - sa.Callsite(0x2006, None, True, None), - ] - add_set = { - (funcs[0x2000], funcs[0x2000]), - (funcs[0x2000], funcs[0x4000]), - (funcs[0x4000], funcs[0x1000]), - (funcs[0x4000], funcs[0x2000]), - } - remove_list = [ - [funcs[0x1000]], - [funcs[0x2000], funcs[0x2000]], - [funcs[0x4000], funcs[0x1000]], - [funcs[0x2000], funcs[0x4000], funcs[0x2000]], - [funcs[0x4000], funcs[0x1000], funcs[0x4000]], - ] - eliminated_addrs = {0x2006} - - remaining_remove_list = self.analyzer.PreprocessAnnotation(funcs, - add_set, - remove_list, - eliminated_addrs) - - expect_funcs = { - 0x1000: sa.Function(0x1000, 'hook_task', 0, []), - 0x2000: sa.Function(0x2000, 'console_task', 0, []), - 0x4000: sa.Function(0x4000, 'touchpad_calc', 0, []), - } - expect_funcs[0x2000].callsites = [ - sa.Callsite(None, 0x4000, False, expect_funcs[0x4000])] - expect_funcs[0x4000].callsites = [ - sa.Callsite(None, 0x2000, False, expect_funcs[0x2000])] - self.assertEqual(funcs, expect_funcs) - self.assertEqual(remaining_remove_list, [ - [funcs[0x2000], funcs[0x4000], funcs[0x2000]], - ]) - - def testAndesAnalyzeDisassembly(self): - disasm_text = ( - '\n' - 'build/{BOARD}/RW/ec.RW.elf: file format elf32-nds32le' - '\n' - 'Disassembly of section .text:\n' - '\n' - '00000900 <wook_task>:\n' - ' ...\n' - '00001000 <hook_task>:\n' - ' 1000: fc 42\tpush25 $r10, #16 ! {$r6~$r10, $fp, $gp, $lp}\n' - ' 1004: 47 70\t\tmovi55 $r0, #1\n' - ' 1006: b1 13\tbnezs8 100929de <flash_command_write>\n' - ' 1008: 00 01 5c fc\tbne $r6, $r0, 2af6a\n' - '00002000 <console_task>:\n' - ' 2000: fc 00\t\tpush25 $r6, #0 ! {$r6, $fp, $gp, $lp} \n' - ' 2002: f0 0e fc c5\tjal 1000 <hook_task>\n' - ' 2006: f0 0e bd 3b\tj 53968 <get_program_memory_addr>\n' - ' 200a: de ad be ef\tswi.gp $r0, [ + #-11036]\n' - '00004000 <touchpad_calc>:\n' - ' 4000: 47 70\t\tmovi55 $r0, #1\n' - '00010000 <look_task>:' - ) - function_map = self.analyzer.AnalyzeDisassembly(disasm_text) - func_hook_task = sa.Function(0x1000, 'hook_task', 48, [ - sa.Callsite(0x1006, 0x100929de, True, None)]) - expect_funcmap = { - 0x1000: func_hook_task, - 0x2000: sa.Function(0x2000, 'console_task', 16, - [sa.Callsite(0x2002, 0x1000, False, func_hook_task), - sa.Callsite(0x2006, 0x53968, True, None)]), - 0x4000: sa.Function(0x4000, 'touchpad_calc', 0, []), - } - self.assertEqual(function_map, expect_funcmap) - - def testArmAnalyzeDisassembly(self): - disasm_text = ( - '\n' - 'build/{BOARD}/RW/ec.RW.elf: file format elf32-littlearm' - '\n' - 'Disassembly of section .text:\n' - '\n' - '00000900 <wook_task>:\n' - ' ...\n' - '00001000 <hook_task>:\n' - ' 1000: dead beef\tfake\n' - ' 1004: 4770\t\tbx lr\n' - ' 1006: b113\tcbz r3, 100929de <flash_command_write>\n' - ' 1008: 00015cfc\t.word 0x00015cfc\n' - '00002000 <console_task>:\n' - ' 2000: b508\t\tpush {r3, lr} ; malformed comments,; r0, r1 \n' - ' 2002: f00e fcc5\tbl 1000 <hook_task>\n' - ' 2006: f00e bd3b\tb.w 53968 <get_program_memory_addr>\n' - ' 200a: dead beef\tfake\n' - '00004000 <touchpad_calc>:\n' - ' 4000: 4770\t\tbx lr\n' - '00010000 <look_task>:' - ) - function_map = self.analyzer.AnalyzeDisassembly(disasm_text) - func_hook_task = sa.Function(0x1000, 'hook_task', 0, [ - sa.Callsite(0x1006, 0x100929de, True, None)]) - expect_funcmap = { - 0x1000: func_hook_task, - 0x2000: sa.Function(0x2000, 'console_task', 8, - [sa.Callsite(0x2002, 0x1000, False, func_hook_task), - sa.Callsite(0x2006, 0x53968, True, None)]), - 0x4000: sa.Function(0x4000, 'touchpad_calc', 0, []), - } - self.assertEqual(function_map, expect_funcmap) - - def testAnalyzeCallGraph(self): - funcs = { - 0x1000: sa.Function(0x1000, 'hook_task', 0, []), - 0x2000: sa.Function(0x2000, 'console_task', 8, []), - 0x3000: sa.Function(0x3000, 'task_a', 12, []), - 0x4000: sa.Function(0x4000, 'task_b', 96, []), - 0x5000: sa.Function(0x5000, 'task_c', 32, []), - 0x6000: sa.Function(0x6000, 'task_d', 100, []), - 0x7000: sa.Function(0x7000, 'task_e', 24, []), - 0x8000: sa.Function(0x8000, 'task_f', 20, []), - 0x9000: sa.Function(0x9000, 'task_g', 20, []), - 0x10000: sa.Function(0x10000, 'task_x', 16, []), - } - funcs[0x1000].callsites = [ - sa.Callsite(0x1002, 0x3000, False, funcs[0x3000]), - sa.Callsite(0x1006, 0x4000, False, funcs[0x4000])] - funcs[0x2000].callsites = [ - sa.Callsite(0x2002, 0x5000, False, funcs[0x5000]), - sa.Callsite(0x2006, 0x2000, False, funcs[0x2000]), - sa.Callsite(0x200a, 0x10000, False, funcs[0x10000])] - funcs[0x3000].callsites = [ - sa.Callsite(0x3002, 0x4000, False, funcs[0x4000]), - sa.Callsite(0x3006, 0x1000, False, funcs[0x1000])] - funcs[0x4000].callsites = [ - sa.Callsite(0x4002, 0x6000, True, funcs[0x6000]), - sa.Callsite(0x4006, 0x7000, False, funcs[0x7000]), - sa.Callsite(0x400a, 0x8000, False, funcs[0x8000])] - funcs[0x5000].callsites = [ - sa.Callsite(0x5002, 0x4000, False, funcs[0x4000])] - funcs[0x7000].callsites = [ - sa.Callsite(0x7002, 0x7000, False, funcs[0x7000])] - funcs[0x8000].callsites = [ - sa.Callsite(0x8002, 0x9000, False, funcs[0x9000])] - funcs[0x9000].callsites = [ - sa.Callsite(0x9002, 0x4000, False, funcs[0x4000])] - funcs[0x10000].callsites = [ - sa.Callsite(0x10002, 0x2000, False, funcs[0x2000])] - - cycles = self.analyzer.AnalyzeCallGraph(funcs, [ - [funcs[0x2000]] * 2, - [funcs[0x10000], funcs[0x2000]] * 3, - [funcs[0x1000], funcs[0x3000], funcs[0x1000]] - ]) - - expect_func_stack = { - 0x1000: (268, [funcs[0x1000], - funcs[0x3000], - funcs[0x4000], - funcs[0x8000], - funcs[0x9000], - funcs[0x4000], - funcs[0x7000]]), - 0x2000: (208, [funcs[0x2000], - funcs[0x10000], - funcs[0x2000], - funcs[0x10000], - funcs[0x2000], - funcs[0x5000], - funcs[0x4000], - funcs[0x7000]]), - 0x3000: (280, [funcs[0x3000], - funcs[0x1000], - funcs[0x3000], - funcs[0x4000], - funcs[0x8000], - funcs[0x9000], - funcs[0x4000], - funcs[0x7000]]), - 0x4000: (120, [funcs[0x4000], funcs[0x7000]]), - 0x5000: (152, [funcs[0x5000], funcs[0x4000], funcs[0x7000]]), - 0x6000: (100, [funcs[0x6000]]), - 0x7000: (24, [funcs[0x7000]]), - 0x8000: (160, [funcs[0x8000], - funcs[0x9000], - funcs[0x4000], - funcs[0x7000]]), - 0x9000: (140, [funcs[0x9000], funcs[0x4000], funcs[0x7000]]), - 0x10000: (200, [funcs[0x10000], - funcs[0x2000], - funcs[0x10000], - funcs[0x2000], - funcs[0x5000], - funcs[0x4000], - funcs[0x7000]]), - } - expect_cycles = [ - {funcs[0x4000], funcs[0x8000], funcs[0x9000]}, - {funcs[0x7000]}, - ] - for func in funcs.values(): - (stack_max_usage, stack_max_path) = expect_func_stack[func.address] - self.assertEqual(func.stack_max_usage, stack_max_usage) - self.assertEqual(func.stack_max_path, stack_max_path) - - self.assertEqual(len(cycles), len(expect_cycles)) - for cycle in cycles: - self.assertTrue(cycle in expect_cycles) - - @mock.patch('subprocess.check_output') - def testAddressToLine(self, checkoutput_mock): - checkoutput_mock.return_value = 'fake_func\n/test.c:1' - self.assertEqual(self.analyzer.AddressToLine(0x1234), - [('fake_func', '/test.c', 1)]) - checkoutput_mock.assert_called_once_with( - ['addr2line', '-f', '-e', './ec.RW.elf', '1234'], encoding='utf-8') - checkoutput_mock.reset_mock() - - checkoutput_mock.return_value = 'fake_func\n/a.c:1\nbake_func\n/b.c:2\n' - self.assertEqual(self.analyzer.AddressToLine(0x1234, True), - [('fake_func', '/a.c', 1), ('bake_func', '/b.c', 2)]) - checkoutput_mock.assert_called_once_with( - ['addr2line', '-f', '-e', './ec.RW.elf', '1234', '-i'], - encoding='utf-8') - checkoutput_mock.reset_mock() - - checkoutput_mock.return_value = 'fake_func\n/test.c:1 (discriminator 128)' - self.assertEqual(self.analyzer.AddressToLine(0x12345), - [('fake_func', '/test.c', 1)]) - checkoutput_mock.assert_called_once_with( - ['addr2line', '-f', '-e', './ec.RW.elf', '12345'], encoding='utf-8') - checkoutput_mock.reset_mock() - - checkoutput_mock.return_value = '??\n:?\nbake_func\n/b.c:2\n' - self.assertEqual(self.analyzer.AddressToLine(0x123456), - [None, ('bake_func', '/b.c', 2)]) - checkoutput_mock.assert_called_once_with( - ['addr2line', '-f', '-e', './ec.RW.elf', '123456'], encoding='utf-8') - checkoutput_mock.reset_mock() - - with self.assertRaisesRegexp(sa.StackAnalyzerError, - 'addr2line failed to resolve lines.'): - checkoutput_mock.side_effect = subprocess.CalledProcessError(1, '') - self.analyzer.AddressToLine(0x5678) - - with self.assertRaisesRegexp(sa.StackAnalyzerError, - 'Failed to run addr2line.'): - checkoutput_mock.side_effect = OSError() - self.analyzer.AddressToLine(0x9012) - - @mock.patch('subprocess.check_output') - @mock.patch('stack_analyzer.StackAnalyzer.AddressToLine') - def testAndesAnalyze(self, addrtoline_mock, checkoutput_mock): - disasm_text = ( - '\n' - 'build/{BOARD}/RW/ec.RW.elf: file format elf32-nds32le' - '\n' - 'Disassembly of section .text:\n' - '\n' - '00000900 <wook_task>:\n' - ' ...\n' - '00001000 <hook_task>:\n' - ' 1000: fc 00\t\tpush25 $r10, #16 ! {$r6~$r10, $fp, $gp, $lp}\n' - ' 1002: 47 70\t\tmovi55 $r0, #1\n' - ' 1006: 00 01 5c fc\tbne $r6, $r0, 2af6a\n' - '00002000 <console_task>:\n' - ' 2000: fc 00\t\tpush25 $r6, #0 ! {$r6, $fp, $gp, $lp} \n' - ' 2002: f0 0e fc c5\tjal 1000 <hook_task>\n' - ' 2006: f0 0e bd 3b\tj 53968 <get_program_memory_addr>\n' - ' 200a: 12 34 56 78\tjral5 $r0\n' - ) - - addrtoline_mock.return_value = [('??', '??', 0)] - self.analyzer.annotation = { - 'exception_frame_size': 64, - 'remove': [['fake_func']], - } - - with mock.patch('builtins.print') as print_mock: - checkoutput_mock.return_value = disasm_text - self.analyzer.Analyze() - print_mock.assert_has_calls([ - mock.call( - 'Task: HOOKS, Max size: 96 (32 + 64), Allocated size: 2048'), - mock.call('Call Trace:'), - mock.call(' hook_task (32) [??:0] 1000'), - mock.call( - 'Task: CONSOLE, Max size: 112 (48 + 64), Allocated size: 460'), - mock.call('Call Trace:'), - mock.call(' console_task (16) [??:0] 2000'), - mock.call(' -> ??[??:0] 2002'), - mock.call(' hook_task (32) [??:0] 1000'), - mock.call('Unresolved indirect callsites:'), - mock.call(' In function console_task:'), - mock.call(' -> ??[??:0] 200a'), - mock.call('Unresolved annotation signatures:'), - mock.call(' fake_func: function is not found'), - ]) - - with self.assertRaisesRegexp(sa.StackAnalyzerError, - 'Failed to run objdump.'): - checkoutput_mock.side_effect = OSError() - self.analyzer.Analyze() - - with self.assertRaisesRegexp(sa.StackAnalyzerError, - 'objdump failed to disassemble.'): - checkoutput_mock.side_effect = subprocess.CalledProcessError(1, '') - self.analyzer.Analyze() - - @mock.patch('subprocess.check_output') - @mock.patch('stack_analyzer.StackAnalyzer.AddressToLine') - def testArmAnalyze(self, addrtoline_mock, checkoutput_mock): - disasm_text = ( - '\n' - 'build/{BOARD}/RW/ec.RW.elf: file format elf32-littlearm' - '\n' - 'Disassembly of section .text:\n' - '\n' - '00000900 <wook_task>:\n' - ' ...\n' - '00001000 <hook_task>:\n' - ' 1000: b508\t\tpush {r3, lr}\n' - ' 1002: 4770\t\tbx lr\n' - ' 1006: 00015cfc\t.word 0x00015cfc\n' - '00002000 <console_task>:\n' - ' 2000: b508\t\tpush {r3, lr}\n' - ' 2002: f00e fcc5\tbl 1000 <hook_task>\n' - ' 2006: f00e bd3b\tb.w 53968 <get_program_memory_addr>\n' - ' 200a: 1234 5678\tb.w sl\n' - ) - - addrtoline_mock.return_value = [('??', '??', 0)] - self.analyzer.annotation = { - 'exception_frame_size': 64, - 'remove': [['fake_func']], - } - - with mock.patch('builtins.print') as print_mock: - checkoutput_mock.return_value = disasm_text - self.analyzer.Analyze() - print_mock.assert_has_calls([ - mock.call( - 'Task: HOOKS, Max size: 72 (8 + 64), Allocated size: 2048'), - mock.call('Call Trace:'), - mock.call(' hook_task (8) [??:0] 1000'), - mock.call( - 'Task: CONSOLE, Max size: 80 (16 + 64), Allocated size: 460'), - mock.call('Call Trace:'), - mock.call(' console_task (8) [??:0] 2000'), - mock.call(' -> ??[??:0] 2002'), - mock.call(' hook_task (8) [??:0] 1000'), - mock.call('Unresolved indirect callsites:'), - mock.call(' In function console_task:'), - mock.call(' -> ??[??:0] 200a'), - mock.call('Unresolved annotation signatures:'), - mock.call(' fake_func: function is not found'), - ]) - - with self.assertRaisesRegexp(sa.StackAnalyzerError, - 'Failed to run objdump.'): - checkoutput_mock.side_effect = OSError() - self.analyzer.Analyze() - - with self.assertRaisesRegexp(sa.StackAnalyzerError, - 'objdump failed to disassemble.'): - checkoutput_mock.side_effect = subprocess.CalledProcessError(1, '') - self.analyzer.Analyze() - - @mock.patch('subprocess.check_output') - @mock.patch('stack_analyzer.ParseArgs') - def testMain(self, parseargs_mock, checkoutput_mock): - symbol_text = ('1000 g F .text 0000015c .hidden hook_task\n' - '2000 g F .text 0000051c .hidden console_task\n') - rodata_text = ( - '\n' - 'Contents of section .rodata:\n' - ' 20000 dead1000 00100000 dead2000 00200000 He..f.He..s.\n' - ) - - args = mock.MagicMock(elf_path='./ec.RW.elf', - export_taskinfo='fake', - section='RW', - objdump='objdump', - addr2line='addr2line', - annotation='fake') - parseargs_mock.return_value = args - - with mock.patch('os.path.exists') as path_mock: - path_mock.return_value = False - with mock.patch('builtins.print') as print_mock: - with mock.patch('builtins.open', mock.mock_open()) as open_mock: - sa.main() - print_mock.assert_any_call( - 'Warning: Annotation file fake does not exist.') - - with mock.patch('os.path.exists') as path_mock: - path_mock.return_value = True - with mock.patch('builtins.print') as print_mock: - with mock.patch('builtins.open', mock.mock_open()) as open_mock: - open_mock.side_effect = IOError() - sa.main() - print_mock.assert_called_once_with( - 'Error: Failed to open annotation file fake.') - - with mock.patch('builtins.print') as print_mock: - with mock.patch('builtins.open', mock.mock_open()) as open_mock: - open_mock.return_value.read.side_effect = ['{', ''] - sa.main() - open_mock.assert_called_once_with('fake', 'r') - print_mock.assert_called_once_with( - 'Error: Failed to parse annotation file fake.') - - with mock.patch('builtins.print') as print_mock: - with mock.patch('builtins.open', - mock.mock_open(read_data='')) as open_mock: - sa.main() - print_mock.assert_called_once_with( - 'Error: Invalid annotation file fake.') - - args.annotation = None - - with mock.patch('builtins.print') as print_mock: - checkoutput_mock.side_effect = [symbol_text, rodata_text] - sa.main() - print_mock.assert_called_once_with( - 'Error: Failed to load export_taskinfo.') - - with mock.patch('builtins.print') as print_mock: - checkoutput_mock.side_effect = subprocess.CalledProcessError(1, '') - sa.main() - print_mock.assert_called_once_with( - 'Error: objdump failed to dump symbol table or rodata.') - - with mock.patch('builtins.print') as print_mock: - checkoutput_mock.side_effect = OSError() - sa.main() - print_mock.assert_called_once_with('Error: Failed to run objdump.') - - -if __name__ == '__main__': - unittest.main() diff --git a/extra/tigertool/README.md b/extra/tigertool/README.md deleted file mode 100644 index 407a58a751..0000000000 --- a/extra/tigertool/README.md +++ /dev/null @@ -1,28 +0,0 @@ -# tigertool - -tigertool.py is a commandline utility to control the tigertail USB-C mux. It -supports changing the mux status to port A, B, or off. You can set a serial -number to use multiple tigertails at once. - -## Usage - -Typical usage to set the mux port - -``` -./tigertail.py -m [A|B|off] -s [serialno] -``` - -Reboot the tigertail<br> - -``` -./tigertail.py --reboot -``` - -Set the serial number, when only one tigertail is plugged - -``` -./tigertail.py --setserialno=[serialno] -``` - -Tigertail can support up to 20V 3A on the mux and passes through all USB-C lines -except SBU. diff --git a/extra/tigertool/ecusb/__init__.py b/extra/tigertool/ecusb/__init__.py deleted file mode 100644 index fe4dbc6749..0000000000 --- a/extra/tigertool/ecusb/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright 2017 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. -# -# Ignore indention messages, since legacy scripts use 2 spaces instead of 4. -# pylint: disable=bad-indentation,docstring-section-indent -# pylint: disable=docstring-trailing-quotes - -__all__ = ['tiny_servo_common', 'stm32usb', 'stm32uart', 'pty_driver'] diff --git a/extra/tigertool/ecusb/pty_driver.py b/extra/tigertool/ecusb/pty_driver.py deleted file mode 100644 index 137cbc149b..0000000000 --- a/extra/tigertool/ecusb/pty_driver.py +++ /dev/null @@ -1,304 +0,0 @@ -# Copyright 2017 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. -# -# Ignore indention messages, since legacy scripts use 2 spaces instead of 4. -# pylint: disable=bad-indentation,docstring-section-indent -# pylint: disable=docstring-trailing-quotes - -"""ptyDriver class - -This class takes a pty interface and can send commands and expect results -as regex. This is useful for automating console based interfaces, such as -the CrOS EC console commands. -""" - -import ast -import errno -import fcntl -import os -import pexpect -import time -from pexpect import fdpexpect - -# Expecting a result in 3 seconds is plenty even for slow platforms. -DEFAULT_UART_TIMEOUT = 3 -FLUSH_UART_TIMEOUT = 1 - - -class ptyError(Exception): - """Exception class for pty errors.""" - - -UART_PARAMS = { - 'uart_cmd': None, - 'uart_multicmd': None, - 'uart_regexp': None, - 'uart_timeout': DEFAULT_UART_TIMEOUT, -} - - -class ptyDriver(object): - """Automate interactive commands on a pty interface.""" - def __init__(self, interface, params, fast=False): - """Init class variables.""" - self._child = None - self._fd = None - self._interface = interface - self._pty_path = self._interface.get_pty() - self._dict = UART_PARAMS.copy() - self._fast = fast - - def __del__(self): - self.close() - - def close(self): - """Close any open files and interfaces.""" - if self._fd: - self._close() - self._interface.close() - - def _open(self): - """Connect to serial device and create pexpect interface.""" - assert self._fd is None - self._fd = os.open(self._pty_path, os.O_RDWR | os.O_NONBLOCK) - # Don't allow forked processes to access. - fcntl.fcntl(self._fd, fcntl.F_SETFD, - fcntl.fcntl(self._fd, fcntl.F_GETFD) | fcntl.FD_CLOEXEC) - self._child = fdpexpect.fdspawn(self._fd) - # pexpect defaults to a 100ms delay before sending characters, to - # work around race conditions in ssh. We don't need this feature - # so we'll change delaybeforesend from 0.1 to 0.001 to speed things up. - if self._fast: - self._child.delaybeforesend = 0.001 - - def _close(self): - """Close serial device connection.""" - os.close(self._fd) - self._fd = None - self._child = None - - def _flush(self): - """Flush device output to prevent previous messages interfering.""" - if self._child.sendline('') != 1: - raise ptyError('Failed to send newline.') - # Have a maximum timeout for the flush operation. We should have cleared - # all data from the buffer, but if data is regularly being generated, we - # can't guarantee it will ever stop. - flush_end_time = time.time() + FLUSH_UART_TIMEOUT - while time.time() <= flush_end_time: - try: - self._child.expect('.', timeout=0.01) - except (pexpect.TIMEOUT, pexpect.EOF): - break - except OSError as e: - # EAGAIN indicates no data available, maybe we didn't wait long enough. - if e.errno != errno.EAGAIN: - raise - break - - def _send(self, cmds): - """Send command to EC. - - This function always flushes serial device before sending, and is used as - a wrapper function to make sure the channel is always flushed before - sending commands. - - Args: - cmds: The commands to send to the device, either a list or a string. - - Raises: - ptyError: Raised when writing to the device fails. - """ - self._flush() - if not isinstance(cmds, list): - cmds = [cmds] - for cmd in cmds: - if self._child.sendline(cmd) != len(cmd) + 1: - raise ptyError('Failed to send command.') - - def _issue_cmd(self, cmds): - """Send command to the device and do not wait for response. - - Args: - cmds: The commands to send to the device, either a list or a string. - """ - self._issue_cmd_get_results(cmds, []) - - def _issue_cmd_get_results(self, cmds, - regex_list, timeout=DEFAULT_UART_TIMEOUT): - """Send command to the device and wait for response. - - This function waits for response message matching a regular - expressions. - - Args: - cmds: The commands issued, either a list or a string. - regex_list: List of Regular expressions used to match response message. - Note1, list must be ordered. - Note2, empty list sends and returns. - timeout: time to wait for matching results before failing. - - Returns: - List of tuples, each of which contains the entire matched string and - all the subgroups of the match. None if not matched. - For example: - response of the given command: - High temp: 37.2 - Low temp: 36.4 - regex_list: - ['High temp: (\d+)\.(\d+)', 'Low temp: (\d+)\.(\d+)'] - returns: - [('High temp: 37.2', '37', '2'), ('Low temp: 36.4', '36', '4')] - - Raises: - ptyError: If timed out waiting for a response - """ - result_list = [] - self._open() - try: - self._send(cmds) - for regex in regex_list: - self._child.expect(regex, timeout) - match = self._child.match - lastindex = match.lastindex if match and match.lastindex else 0 - # Create a tuple which contains the entire matched string and all - # the subgroups of the match. - result = match.group(*range(lastindex + 1)) if match else None - if result: - result = tuple(res.decode('utf-8') for res in result) - result_list.append(result) - except pexpect.TIMEOUT: - raise ptyError('Timeout waiting for response.') - finally: - self._close() - return result_list - - def _issue_cmd_get_multi_results(self, cmd, regex): - """Send command to the device and wait for multiple response. - - This function waits for arbitrary number of response message - matching a regular expression. - - Args: - cmd: The command issued. - regex: Regular expression used to match response message. - - Returns: - List of tuples, each of which contains the entire matched string and - all the subgroups of the match. None if not matched. - """ - result_list = [] - self._open() - try: - self._send(cmd) - while True: - try: - self._child.expect(regex, timeout=0.1) - match = self._child.match - lastindex = match.lastindex if match and match.lastindex else 0 - # Create a tuple which contains the entire matched string and all - # the subgroups of the match. - result = match.group(*range(lastindex + 1)) if match else None - if result: - result = tuple(res.decode('utf-8') for res in result) - result_list.append(result) - except pexpect.TIMEOUT: - break - finally: - self._close() - return result_list - - def _Set_uart_timeout(self, timeout): - """Set timeout value for waiting for the device response. - - Args: - timeout: Timeout value in second. - """ - self._dict['uart_timeout'] = timeout - - def _Get_uart_timeout(self): - """Get timeout value for waiting for the device response. - - Returns: - Timeout value in second. - """ - return self._dict['uart_timeout'] - - def _Set_uart_regexp(self, regexp): - """Set the list of regular expressions which matches the command response. - - Args: - regexp: A string which contains a list of regular expressions. - """ - if not isinstance(regexp, str): - raise ptyError('The argument regexp should be a string.') - self._dict['uart_regexp'] = ast.literal_eval(regexp) - - def _Get_uart_regexp(self): - """Get the list of regular expressions which matches the command response. - - Returns: - A string which contains a list of regular expressions. - """ - return str(self._dict['uart_regexp']) - - def _Set_uart_cmd(self, cmd): - """Set the UART command and send it to the device. - - If ec_uart_regexp is 'None', the command is just sent and it doesn't care - about its response. - - If ec_uart_regexp is not 'None', the command is send and its response, - which matches the regular expression of ec_uart_regexp, will be kept. - Use its getter to obtain this result. If no match after ec_uart_timeout - seconds, a timeout error will be raised. - - Args: - cmd: A string of UART command. - """ - if self._dict['uart_regexp']: - self._dict['uart_cmd'] = self._issue_cmd_get_results( - cmd, self._dict['uart_regexp'], self._dict['uart_timeout']) - else: - self._dict['uart_cmd'] = None - self._issue_cmd(cmd) - - def _Set_uart_multicmd(self, cmds): - """Set multiple UART commands and send them to the device. - - Note that ec_uart_regexp is not supported to match the results. - - Args: - cmds: A semicolon-separated string of UART commands. - """ - self._issue_cmd(cmds.split(';')) - - def _Get_uart_cmd(self): - """Get the result of the latest UART command. - - Returns: - A string which contains a list of tuples, each of which contains the - entire matched string and all the subgroups of the match. 'None' if - the ec_uart_regexp is 'None'. - """ - return str(self._dict['uart_cmd']) - - def _Set_uart_capture(self, cmd): - """Set UART capture mode (on or off). - - Once capture is enabled, UART output could be collected periodically by - invoking _Get_uart_stream() below. - - Args: - cmd: True for on, False for off - """ - self._interface.set_capture_active(cmd) - - def _Get_uart_capture(self): - """Get the UART capture mode (on or off).""" - return self._interface.get_capture_active() - - def _Get_uart_stream(self): - """Get uart stream generated since last time.""" - return self._interface.get_stream() diff --git a/extra/tigertool/ecusb/stm32uart.py b/extra/tigertool/ecusb/stm32uart.py deleted file mode 100644 index 95219455a9..0000000000 --- a/extra/tigertool/ecusb/stm32uart.py +++ /dev/null @@ -1,248 +0,0 @@ -# Copyright 2017 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. -# -# Ignore indention messages, since legacy scripts use 2 spaces instead of 4. -# pylint: disable=bad-indentation,docstring-section-indent -# pylint: disable=docstring-trailing-quotes - -"""Allow creation of uart/console interface via stm32 usb endpoint.""" - -from __future__ import print_function - -import os -import select -import sys -import termios -import threading -import time -import tty -import usb - -from . import stm32usb - - -class SuartError(Exception): - """Class for exceptions of Suart.""" - def __init__(self, msg, value=0): - """SuartError constructor. - - Args: - msg: string, message describing error in detail - value: integer, value of error when non-zero status returned. Default=0 - """ - super(SuartError, self).__init__(msg, value) - self.msg = msg - self.value = value - - -class Suart(object): - """Provide interface to stm32 serial usb endpoint.""" - def __init__(self, vendor=0x18d1, product=0x501a, interface=0, - serialname=None, debuglog=False): - """Suart contstructor. - - Initializes stm32 USB stream interface. - - Args: - vendor: usb vendor id of stm32 device - product: usb product id of stm32 device - interface: interface number of stm32 device to use - serialname: serial name to target. Defaults to None. - debuglog: chatty output. Defaults to False. - - Raises: - SuartError: If init fails - """ - self._ptym = None - self._ptys = None - self._ptyname = None - self._rx_thread = None - self._tx_thread = None - self._debuglog = debuglog - self._susb = stm32usb.Susb(vendor=vendor, product=product, - interface=interface, serialname=serialname) - self._running = False - - def __del__(self): - """Suart destructor.""" - self.close() - - def close(self): - """Stop all running threads.""" - self._running = False - if self._rx_thread: - self._rx_thread.join(2) - self._rx_thread = None - if self._tx_thread: - self._tx_thread.join(2) - self._tx_thread = None - self._susb.close() - - def run_rx_thread(self): - """Background loop to pass data from USB to pty.""" - ep = select.epoll() - ep.register(self._ptym, select.EPOLLHUP) - try: - while self._running: - events = ep.poll(0) - # Check if the pty is connected to anything, or hungup. - if not events: - try: - r = self._susb._read_ep.read(64, self._susb.TIMEOUT_MS) - if r: - if self._debuglog: - print(''.join([chr(x) for x in r]), end='') - os.write(self._ptym, r) - - # If we miss some characters on pty disconnect, that's fine. - # ep.read() also throws USBError on timeout, which we discard. - except OSError: - pass - except usb.core.USBError: - pass - else: - time.sleep(.1) - except Exception as e: - raise e - - def run_tx_thread(self): - """Background loop to pass data from pty to USB.""" - ep = select.epoll() - ep.register(self._ptym, select.EPOLLHUP) - try: - while self._running: - events = ep.poll(0) - # Check if the pty is connected to anything, or hungup. - if not events: - try: - r = os.read(self._ptym, 64) - # TODO(crosbug.com/936182): Remove when the servo v4/micro console - # issues are fixed. - time.sleep(0.001) - if r: - self._susb._write_ep.write(r, self._susb.TIMEOUT_MS) - - except OSError: - pass - except usb.core.USBError: - pass - else: - time.sleep(.1) - except Exception as e: - raise e - - def run(self): - """Creates pthreads to poll stm32 & PTY for data.""" - m, s = os.openpty() - self._ptyname = os.ttyname(s) - - self._ptym = m - self._ptys = s - - os.fchmod(s, 0o660) - - # Change the owner and group of the PTY to the user who started servod. - try: - uid = int(os.environ.get('SUDO_UID', -1)) - except TypeError: - uid = -1 - - try: - gid = int(os.environ.get('SUDO_GID', -1)) - except TypeError: - gid = -1 - os.fchown(s, uid, gid) - - tty.setraw(self._ptym, termios.TCSADRAIN) - - # Generate a HUP flag on pty slave fd. - os.fdopen(s).close() - - self._running = True - - self._rx_thread = threading.Thread(target=self.run_rx_thread, args=[]) - self._rx_thread.daemon = True - self._rx_thread.start() - - self._tx_thread = threading.Thread(target=self.run_tx_thread, args=[]) - self._tx_thread.daemon = True - self._tx_thread.start() - - def get_uart_props(self): - """Get the uart's properties. - - Returns: - dict where: - baudrate: integer of uarts baudrate - bits: integer, number of bits of data Can be 5|6|7|8 inclusive - parity: integer, parity of 0-2 inclusive where: - 0: no parity - 1: odd parity - 2: even parity - sbits: integer, number of stop bits. Can be 0|1|2 inclusive where: - 0: 1 stop bit - 1: 1.5 stop bits - 2: 2 stop bits - """ - return { - 'baudrate': 115200, - 'bits': 8, - 'parity': 0, - 'sbits': 1, - } - - def set_uart_props(self, line_props): - """Set the uart's properties. - - Note that Suart cannot set properties - and will fail if the properties are not the default 115200,8n1. - - Args: - line_props: dict where: - baudrate: integer of uarts baudrate - bits: integer, number of bits of data ( prior to stop bit) - parity: integer, parity of 0-2 inclusive where - 0: no parity - 1: odd parity - 2: even parity - sbits: integer, number of stop bits. Can be 0|1|2 inclusive where: - 0: 1 stop bit - 1: 1.5 stop bits - 2: 2 stop bits - - Raises: - SuartError: If requested line properties are not the default. - """ - curr_props = self.get_uart_props() - for prop in line_props: - if line_props[prop] != curr_props[prop]: - raise SuartError('Line property %s cannot be set from %s to %s' % ( - prop, curr_props[prop], line_props[prop])) - return True - - def get_pty(self): - """Gets path to pty for communication to/from uart. - - Returns: - String path to the pty connected to the uart - """ - return self._ptyname - - -def main(): - """Run a suart test with the default parameters.""" - try: - sobj = Suart() - sobj.run() - - # run() is a thread so just busy wait to mimic server. - while True: - # Ours sleeps to eleven! - time.sleep(11) - except KeyboardInterrupt: - sys.exit(0) - - -if __name__ == '__main__': - main() diff --git a/extra/tigertool/ecusb/stm32usb.py b/extra/tigertool/ecusb/stm32usb.py deleted file mode 100644 index bfd5fbb1fb..0000000000 --- a/extra/tigertool/ecusb/stm32usb.py +++ /dev/null @@ -1,119 +0,0 @@ -# Copyright 2017 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. -# -# Ignore indention messages, since legacy scripts use 2 spaces instead of 4. -# pylint: disable=bad-indentation,docstring-section-indent -# pylint: disable=docstring-trailing-quotes - -"""Allows creation of an interface via stm32 usb.""" - -import usb - - -class SusbError(Exception): - """Class for exceptions of Susb.""" - def __init__(self, msg, value=0): - """SusbError constructor. - - Args: - msg: string, message describing error in detail - value: integer, value of error when non-zero status returned. Default=0 - """ - super(SusbError, self).__init__(msg, value) - self.msg = msg - self.value = value - - -class Susb(object): - """Provide stm32 USB functionality. - - Instance Variables: - _read_ep: pyUSB read endpoint for this interface - _write_ep: pyUSB write endpoint for this interface - """ - READ_ENDPOINT = 0x81 - WRITE_ENDPOINT = 0x1 - TIMEOUT_MS = 100 - - def __init__(self, vendor=0x18d1, - product=0x5027, interface=1, serialname=None, logger=None): - """Susb constructor. - - Discovers and connects to stm32 USB endpoints. - - Args: - vendor: usb vendor id of stm32 device. - product: usb product id of stm32 device. - interface: interface number ( 1 - 4 ) of stm32 device to use. - serialname: string of device serialname. - logger: none - - Raises: - SusbError: An error accessing Susb object - """ - self._vendor = vendor - self._product = product - self._interface = interface - self._serialname = serialname - self._find_device() - - def _find_device(self): - """Set up the usb endpoint""" - # Find the stm32. - dev_g = usb.core.find(idVendor=self._vendor, idProduct=self._product, - find_all=True) - dev_list = list(dev_g) - - if not dev_list: - raise SusbError('USB device not found') - - # Check if we have multiple stm32s and we've specified the serial. - dev = None - if self._serialname: - for d in dev_list: - dev_serial = usb.util.get_string(d, d.iSerialNumber) - if dev_serial == self._serialname: - dev = d - break - if dev is None: - raise SusbError('USB device(%s) not found' % self._serialname) - else: - try: - dev = dev_list[0] - except StopIteration: - raise SusbError('USB device %04x:%04x not found' % ( - self._vendor, self._product)) - - # If we can't set configuration, it's already been set. - try: - dev.set_configuration() - except usb.core.USBError: - pass - - self._dev = dev - - # Get an endpoint instance. - cfg = dev.get_active_configuration() - intf = usb.util.find_descriptor(cfg, bInterfaceNumber=self._interface) - self._intf = intf - if not intf: - raise SusbError('Interface %04x:%04x - 0x%x not found' % ( - self._vendor, self._product, self._interface)) - - # Detach raiden.ko if it is loaded. CCD endpoints support either a kernel - # module driver that produces a ttyUSB, or direct endpoint access, but - # can't do both at the same time. - if dev.is_kernel_driver_active(intf.bInterfaceNumber) is True: - dev.detach_kernel_driver(intf.bInterfaceNumber) - - read_ep_number = intf.bInterfaceNumber + self.READ_ENDPOINT - read_ep = usb.util.find_descriptor(intf, bEndpointAddress=read_ep_number) - self._read_ep = read_ep - - write_ep_number = intf.bInterfaceNumber + self.WRITE_ENDPOINT - write_ep = usb.util.find_descriptor(intf, bEndpointAddress=write_ep_number) - self._write_ep = write_ep - - def close(self): - usb.util.dispose_resources(self._dev) diff --git a/extra/tigertool/ecusb/tiny_servo_common.py b/extra/tigertool/ecusb/tiny_servo_common.py deleted file mode 100644 index 152c238bdf..0000000000 --- a/extra/tigertool/ecusb/tiny_servo_common.py +++ /dev/null @@ -1,174 +0,0 @@ -# Copyright 2017 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. -# -# Ignore indention messages, since legacy scripts use 2 spaces instead of 4. -# pylint: disable=bad-indentation,docstring-section-indent -# pylint: disable=docstring-trailing-quotes - -"""Utilities for using lightweight console functions.""" - -# Note: This is a py2/3 compatible file. - -import datetime -import errno -import os -import re -import subprocess -import sys -import time - -import six - -from . import pty_driver -from . import stm32uart - - -def get_subprocess_args(): - if six.PY3: - return {'encoding': 'utf-8'} - return {} - - -class TinyServoError(Exception): - """Exceptions.""" - - -def log(output): - """Print output to console, logfiles can be added here. - - Args: - output: string to output. - """ - sys.stdout.write(output) - sys.stdout.write('\n') - sys.stdout.flush() - -def check_usb(vidpid, serialname=None): - """Check if |vidpid| is present on the system's USB. - - Args: - vidpid: string representation of the usb vid:pid, eg. '18d1:2001' - serialname: serialname if specified. - - Returns: True if found, False, otherwise. - """ - if serialname: - output = subprocess.check_output(['lsusb', '-v', '-d', vidpid], - **get_subprocess_args()) - m = re.search(r'^\s*iSerial\s+\d+\s+%s$' % serialname, output, flags=re.M) - if m: - return True - - return False - else: - if subprocess.call(['lsusb', '-d', vidpid], stdout=open('/dev/null', 'w')): - return False - return True - -def check_usb_sn(vidpid): - """Return the serial number - - Return the serial number of the first USB device with VID:PID vidpid, - or None if no device is found. This will not work well with two of - the same device attached. - - Args: - vidpid: string representation of the usb vid:pid, eg. '18d1:2001' - - Returns: string serial number if found, None otherwise. - """ - output = subprocess.check_output(['lsusb', '-v', '-d', vidpid], - **get_subprocess_args()) - m = re.search(r'^\s*iSerial\s+(.*)$', output, flags=re.M) - if m: - return m.group(1) - - return None - -def wait_for_usb_remove(vidpid, serialname=None, timeout=None): - """Wait for USB device with vidpid to be removed. - - Wrapper for wait_for_usb below - """ - wait_for_usb(vidpid, serialname=serialname, - timeout=timeout, desiredpresence=False) - -def wait_for_usb(vidpid, serialname=None, timeout=None, desiredpresence=True): - """Wait for usb device with vidpid to be present/absent. - - Args: - vidpid: string representation of the usb vid:pid, eg. '18d1:2001' - serialname: serialname if specificed. - timeout: timeout in seconds, None for no timeout. - desiredpresence: True for present, False for not present. - - Raises: - TinyServoError: on timeout. - """ - if timeout: - finish = datetime.datetime.now() + datetime.timedelta(seconds=timeout) - while check_usb(vidpid, serialname) != desiredpresence: - time.sleep(.1) - if timeout: - if datetime.datetime.now() > finish: - raise TinyServoError('Timeout', 'Timeout waiting for USB %s' % vidpid) - -def do_serialno(serialno, pty): - """Set serialnumber 'serialno' via ec console 'pty'. - - Commands are: - # > serialno set 1234 - # Saving serial number - # Serial number: 1234 - - Args: - serialno: string serial number to set. - pty: tinyservo console to send commands. - - Raises: - TinyServoError: on failure to set. - ptyError: on command interface error. - """ - cmd = 'serialno set %s' % serialno - regex = 'Serial number:\s+(\S+)' - - results = pty._issue_cmd_get_results(cmd, [regex])[0] - sn = results[1].strip().strip('\n\r') - - if sn == serialno: - log('Success !') - log('Serial set to %s' % sn) - else: - log('Serial number set to %s but saved as %s.' % (serialno, sn)) - raise TinyServoError( - 'Serial Number', - 'Serial number set to %s but saved as %s.' % (serialno, sn)) - -def setup_tinyservod(vidpid, interface, serialname=None, debuglog=False): - """Set up a pty - - Set up a pty to the ec console in order - to send commands. Returns a pty_driver object. - - Args: - vidpid: string vidpid of device to access. - interface: not used. - serialname: string serial name of device requested, optional. - debuglog: chatty printout (boolean) - - Returns: pty object - - Raises: - UsbError, SusbError: on device not found - """ - vidstr, pidstr = vidpid.split(':') - vid = int(vidstr, 16) - pid = int(pidstr, 16) - suart = stm32uart.Suart(vendor=vid, product=pid, - interface=interface, serialname=serialname, - debuglog=debuglog) - suart.run() - pty = pty_driver.ptyDriver(suart, []) - - return pty diff --git a/extra/tigertool/ecusb/tiny_servod.py b/extra/tigertool/ecusb/tiny_servod.py deleted file mode 100644 index 632d9c3a20..0000000000 --- a/extra/tigertool/ecusb/tiny_servod.py +++ /dev/null @@ -1,54 +0,0 @@ -# Copyright 2020 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. -# -# Ignore indention messages, since legacy scripts use 2 spaces instead of 4. -# pylint: disable=bad-indentation,docstring-section-indent -# pylint: disable=docstring-trailing-quotes - -"""Helper class to facilitate communication to servo ec console.""" - -from ecusb import pty_driver -from ecusb import stm32uart - - -class TinyServod(object): - """Helper class to wrap a pty_driver with interface.""" - - def __init__(self, vid, pid, interface, serialname=None, debug=False): - """Build the driver and interface. - - Args: - vid: servo device vid - pid: servo device pid - interface: which usb interface the servo console is on - serialname: the servo device serial (if available) - """ - self._vid = vid - self._pid = pid - self._interface = interface - self._serial = serialname - self._debug = debug - self._init() - - def _init(self): - self.suart = stm32uart.Suart(vendor=self._vid, - product=self._pid, - interface=self._interface, - serialname=self._serial, - debuglog=self._debug) - self.suart.run() - self.pty = pty_driver.ptyDriver(self.suart, []) - - def reinitialize(self): - """Reinitialize the connect after a reset/disconnect/etc.""" - self.close() - self._init() - - def close(self): - """Close out the connection and release resources. - - Note: if another TinyServod process or servod itself needs the same device - it's necessary to call this to ensure the usb device is available. - """ - self.suart.close() diff --git a/extra/tigertool/flash_dfu.sh b/extra/tigertool/flash_dfu.sh deleted file mode 100755 index 7aa6c24f09..0000000000 --- a/extra/tigertool/flash_dfu.sh +++ /dev/null @@ -1,57 +0,0 @@ -#!/bin/bash -# Copyright 2017 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. - -FLAGS_timeout=600 -IMG=${1:-tigertail.bin} - -echo "Flashing ${IMG}" - -error() { - printf "%s\n" "$*" >&2 -} - -die() { - [[ "$#*" == "0" ]] || error "$@" - exit 1 -} - -flash_stm32_dfu() { - local DFU_DEVICE=0483:df11 - local ADDR=0x08000000 - - [[ -e "${IMG}" ]] || die "File ${IMG} not found!" - - # Check for a suitable local dfu-util - local LOCAL_DFU_UTIL=$(which dfu-util) - if [[ -n "${LOCAL_DFU_UTIL}" ]]; then - DFU_VERSION=$("${LOCAL_DFU_UTIL}" -V | head -n1 | cut -d' ' -f2) - if [[ "${DFU_VERSION}" < "0.7" ]]; then - LOCAL_DFU_UTIL="" - fi - fi - local DFU_UTIL=${LOCAL_DFU_UTIL:-'./dfu-util'} - - which "${DFU_UTIL}" &> /dev/null || die \ - "no dfu-util util found. Did you 'sudo emerge dfu-util'." - - local dev_cnt=$(lsusb -d "${DFU_DEVICE}" | wc -l) - if [ $dev_cnt -eq 0 ] ; then - die "unable to locate dfu device at ${DFU_DEVICE}." - elif [ $dev_cnt -ne 1 ] ; then - die "too many dfu devices (${dev_cnt}). Disconnect all but one." - fi - - local SIZE=$(wc -c "${IMG}" | cut -d' ' -f1) - # Remove read protection. - sudo timeout -k 10 -s 9 "${FLAGS_timeout}" \ - ${DFU_UTIL} -a 0 -s ${ADDR}:${SIZE}:force:unprotect -D "${IMG}" - # Wait for mass-erase and reboot after unprotection. - sleep 1 - # Actual image flashing. - sudo timeout -k 10 -s 9 "${FLAGS_timeout}" \ - $DFU_UTIL -a 0 -s ${ADDR}:${SIZE} -D "${IMG}" -} - -flash_stm32_dfu diff --git a/extra/tigertool/make_pkg.sh b/extra/tigertool/make_pkg.sh deleted file mode 100755 index d2860f64c1..0000000000 --- a/extra/tigertool/make_pkg.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/bash -# Copyright 2017 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. - -# Make sure we are in the correct dir. -cd "$( dirname "${BASH_SOURCE[0]}" )" || exit - -# Clean and previous cruft. -rm -rf build - -DEST=build/tigertool -DATE=$(date +"%Y%m%d") - -mkdir -p "${DEST}" -cp ../usb_serial/console.py "${DEST}" -cp ../../../../../chroot/usr/bin/dfu-util "${DEST}" -cp flash_dfu.sh "${DEST}" -cp tigertool.py "${DEST}" - -cp -r ecusb "${DEST}" -cp -r ../../../../../chroot/usr/lib64/python2.7/site-packages/usb "${DEST}" -find "${DEST}" -name "*.py[co]" -delete -cp -r ../usb_serial "${DEST}" - -(cd build; tar -czf tigertool_${DATE}.tgz tigertool) - -echo "Done packaging tigertool_${DATE}.tgz" diff --git a/extra/tigertool/tigertool.py b/extra/tigertool/tigertool.py deleted file mode 100755 index 79aa30c3a4..0000000000 --- a/extra/tigertool/tigertool.py +++ /dev/null @@ -1,266 +0,0 @@ -#!/usr/bin/env python -# Copyright 2017 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. -# -# Ignore indention messages, since legacy scripts use 2 spaces instead of 4. -# pylint: disable=bad-indentation,docstring-section-indent -# pylint: disable=docstring-trailing-quotes - -"""Script to control tigertail USB-C Mux board.""" - -# Note: This is a py2/3 compatible file. - -import argparse -import sys -import time - -import ecusb.tiny_servo_common as c - -STM_VIDPID = '18d1:5027' -serialno = 'Uninitialized' - -def do_mux(mux, pty): - """Set mux via ec console 'pty'. - - Args: - mux: mux to connect to DUT, 'A', 'B', or 'off' - pty: a pty object connected to tigertail - - Commands are: - # > mux A - # TYPE-C mux is A - """ - validmux = ['A', 'B', 'off'] - if mux not in validmux: - c.log('Mux setting %s invalid, try one of %s' % (mux, validmux)) - return False - - cmd = '\r\nmux %s\r\n' % mux - regex = 'TYPE\-C mux is ([^\s\r\n]*)\r' - - results = pty._issue_cmd_get_results(cmd, [regex])[0] - result = results[1].strip().strip('\n\r') - - if result != mux: - c.log('Mux set to %s but saved as %s.' % (mux, result)) - return False - c.log('Mux set to %s' % result) - return True - -def do_version(pty): - """Check version via ec console 'pty'. - - Args: - pty: a pty object connected to tigertail - - Commands are: - # > version - # Chip: stm stm32f07x - # Board: 0 - # RO: tigertail_v1.1.6749-74d1a312e - # RW: tigertail_v1.1.6749-74d1a312e - # Build: tigertail_v1.1.6749-74d1a312e - # 2017-07-25 20:08:34 nsanders@meatball.mtv.corp.google.com - - """ - cmd = '\r\nversion\r\n' - regex = 'RO:\s+(\S+)\s+RW:\s+(\S+)\s+Build:\s+(\S+)\s+' \ - '(\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d) (\S+)' - - results = pty._issue_cmd_get_results(cmd, [regex])[0] - c.log('Version is %s' % results[3]) - c.log('RO: %s' % results[1]) - c.log('RW: %s' % results[2]) - c.log('Date: %s' % results[4]) - c.log('Src: %s' % results[5]) - - return True - -def do_check_serial(pty): - """Check serial via ec console 'pty'. - - Args: - pty: a pty object connected to tigertail - - Commands are: - # > serialno - # Serial number: number - """ - cmd = '\r\nserialno\r\n' - regex = 'Serial number: ([^\n\r]+)' - - results = pty._issue_cmd_get_results(cmd, [regex])[0] - c.log('Serial is %s' % results[1]) - - return True - - -def do_power(count, bus, pty): - """Check power usage via ec console 'pty'. - - Args: - count: number of samples to capture - bus: rail to monitor, 'vbus', 'cc1', or 'cc2' - pty: a pty object connected to tigertail - - Commands are: - # > ina 0 - # Configuration: 4127 - # Shunt voltage: 02c4 => 1770 uV - # Bus voltage : 1008 => 5130 mV - # Power : 0019 => 625 mW - # Current : 0082 => 130 mA - # Calibration : 0155 - # Mask/Enable : 0008 - # Alert limit : 0000 - """ - if bus == 'vbus': - ina = 0 - if bus == 'cc1': - ina = 4 - if bus == 'cc2': - ina = 1 - - start = time.time() - - c.log('time,\tmV,\tmW,\tmA') - - cmd = '\r\nina %s\r\n' % ina - regex = 'Bus voltage : \S+ \S+ (\d+) mV\s+' \ - 'Power : \S+ \S+ (\d+) mW\s+' \ - 'Current : \S+ \S+ (\d+) mA' - - for i in range(0, count): - results = pty._issue_cmd_get_results(cmd, [regex])[0] - c.log('%.2f,\t%s,\t%s\t%s' % (time.time() - start, - results[1], results[2], results[3])) - - return True - -def do_reboot(pty): - """Reboot via ec console pty - - Args: - pty: a pty object connected to tigertail - - Command is: reboot. - """ - cmd = '\r\nreboot\r\n' - regex = 'Rebooting' - - try: - results = pty._issue_cmd_get_results(cmd, [regex])[0] - time.sleep(1) - c.log(results) - except Exception as e: - c.log(e) - return False - - return True - -def do_sysjump(region, pty): - """Set region via ec console 'pty'. - - Args: - region: ec code region to execute, 'ro' or 'rw' - pty: a pty object connected to tigertail - - Commands are: - # > sysjump rw - """ - validregion = ['ro', 'rw'] - if region not in validregion: - c.log('Region setting %s invalid, try one of %s' % ( - region, validregion)) - return False - - cmd = '\r\nsysjump %s\r\n' % region - try: - pty._issue_cmd(cmd) - time.sleep(1) - except Exception as e: - c.log(e) - return False - - c.log('Region requested %s' % region) - return True - -def get_parser(): - parser = argparse.ArgumentParser( - description=__doc__) - parser.add_argument('-s', '--serialno', type=str, default=None, - help='serial number of board to use') - parser.add_argument('-b', '--bus', type=str, default='vbus', - help='Which rail to log: [vbus|cc1|cc2]') - group = parser.add_mutually_exclusive_group() - group.add_argument('--setserialno', type=str, default=None, - help='serial number to set on the board.') - group.add_argument('--check_serial', action='store_true', - help='check serial number set on the board.') - group.add_argument('-m', '--mux', type=str, default=None, - help='mux selection') - group.add_argument('-p', '--power', action='store_true', - help='check VBUS') - group.add_argument('-l', '--powerlog', type=int, default=None, - help='log VBUS') - group.add_argument('-r', '--sysjump', type=str, default=None, - help='region selection') - group.add_argument('--reboot', action='store_true', - help='reboot tigertail') - group.add_argument('--check_version', action='store_true', - help='check tigertail version') - return parser - -def main(argv): - parser = get_parser() - opts = parser.parse_args(argv) - - result = True - - # Let's make sure there's a tigertail - # If nothing found in 5 seconds, fail. - c.wait_for_usb(STM_VIDPID, timeout=5., serialname=opts.serialno) - - pty = c.setup_tinyservod(STM_VIDPID, 0, serialname=opts.serialno) - - if opts.bus not in ('vbus', 'cc1', 'cc2'): - c.log('Try --bus [vbus|cc1|cc2]') - result = False - - elif opts.setserialno: - try: - c.do_serialno(opts.setserialno, pty) - except Exception: - result = False - - elif opts.mux: - result &= do_mux(opts.mux, pty) - - elif opts.sysjump: - result &= do_sysjump(opts.sysjump, pty) - - elif opts.reboot: - result &= do_reboot(pty) - - elif opts.check_version: - result &= do_version(pty) - - elif opts.check_serial: - result &= do_check_serial(pty) - - elif opts.power: - result &= do_power(1, opts.bus, pty) - - elif opts.powerlog: - result &= do_power(opts.powerlog, opts.bus, pty) - - if result: - c.log('PASS') - else: - c.log('FAIL') - exit(-1) - - -if __name__ == '__main__': - sys.exit(main(sys.argv[1:])) diff --git a/extra/touchpad_updater/Makefile b/extra/touchpad_updater/Makefile deleted file mode 100644 index ebf9c3212d..0000000000 --- a/extra/touchpad_updater/Makefile +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright 2017 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. - -CC ?= gcc -PKG_CONFIG ?= pkg-config -PROGRAM := touchpad_updater -SOURCE := $(PROGRAM).c -LIBS := -LFLAGS := -CFLAGS := -std=gnu99 \ - -g3 \ - -O3 \ - -Wall \ - -Werror \ - -Wpointer-arith \ - -Wcast-align \ - -Wcast-qual \ - -Wundef \ - -Wsign-compare \ - -Wredundant-decls \ - -Wmissing-declarations - -# -# Add libusb-1.0 required flags -# -LIBS += $(shell $(PKG_CONFIG) --libs libusb-1.0) -CFLAGS += $(shell $(PKG_CONFIG) --cflags libusb-1.0) - -$(PROGRAM): $(SOURCE) Makefile - $(CC) $(CFLAGS) $(SOURCE) $(LFLAGS) $(LIBS) -o $@ - -.PHONY: clean - -clean: - rm -rf $(PROGRAM) *~ diff --git a/extra/touchpad_updater/touchpad_updater.c b/extra/touchpad_updater/touchpad_updater.c deleted file mode 100644 index 716ded00f5..0000000000 --- a/extra/touchpad_updater/touchpad_updater.c +++ /dev/null @@ -1,669 +0,0 @@ -/* - * Copyright 2017 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. - */ - -#include <errno.h> -#include <getopt.h> -#include <poll.h> -#include <signal.h> -#include <stdarg.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/select.h> -#include <unistd.h> - -#include <libusb.h> - -/* Command line options */ -static uint16_t vid = 0x18d1; /* Google */ -static uint16_t pid = 0x5022; /* Hammer */ -static uint8_t ep_num = 4; /* console endpoint */ -static uint8_t extended_i2c_exercise; /* non-zero to exercise */ -static char *firmware_binary = "144.0_2.0.bin"; /* firmware blob */ - -/* Firmware binary blob related */ -#define MAX_FW_PAGE_SIZE 512 -#define MAX_FW_PAGE_COUNT 1024 -#define MAX_FW_SIZE (128 * 1024) - -static uint8_t fw_data[MAX_FW_SIZE]; -int fw_page_count; -int fw_page_size; -int fw_size; -uint8_t ic_type; -int iap_version; - -/* Utility functions */ -static int le_bytes_to_int(uint8_t *buf) -{ - return buf[0] + (int)(buf[1] << 8); -} - -/* Command line parsing related */ -static char *progname; -static char *short_opts = ":f:v:p:e:hd"; -static const struct option long_opts[] = { - /* name hasarg *flag val */ - {"file", 1, NULL, 'f'}, - {"vid", 1, NULL, 'v'}, - {"pid", 1, NULL, 'p'}, - {"ep", 1, NULL, 'e'}, - {"help", 0, NULL, 'h'}, - {"debug", 0, NULL, 'd'}, - {NULL, 0, NULL, 0}, -}; - -static void usage(int errs) -{ - printf("\nUsage: %s [options]\n" - "\n" - "Firmware updater over USB for trackpad under hammer\n" - "\n" - "Options:\n" - "\n" - " -f,--file STR Firmware binary (default %s)\n" - " -v,--vid HEXVAL Vendor ID (default %04x)\n" - " -p,--pid HEXVAL Product ID (default %04x)\n" - " -e,--ep NUM Endpoint (default %d)\n" - " -d,--debug Exercise extended read I2C over USB\n" - " and print verbose debug messages.\n" - " -h,--help Show this message\n" - "\n", progname, firmware_binary, vid, pid, ep_num); - - exit(!!errs); -} - -static void parse_cmdline(int argc, char *argv[]) -{ - char *e = 0; - int i, errorcnt = 0; - - progname = strrchr(argv[0], '/'); - if (progname) - progname++; - else - progname = argv[0]; - - opterr = 0; /* quiet, you */ - while ((i = getopt_long(argc, argv, short_opts, long_opts, 0)) != -1) { - switch (i) { - case 'f': - firmware_binary = optarg; - break; - case 'p': - pid = (uint16_t) strtoull(optarg, &e, 16); - if (!*optarg || (e && *e)) { - printf("Invalid argument: \"%s\"\n", optarg); - errorcnt++; - } - break; - case 'v': - vid = (uint16_t) strtoull(optarg, &e, 16); - if (!*optarg || (e && *e)) { - printf("Invalid argument: \"%s\"\n", optarg); - errorcnt++; - } - break; - case 'e': - ep_num = (uint8_t) strtoull(optarg, &e, 0); - if (!*optarg || (e && *e)) { - printf("Invalid argument: \"%s\"\n", optarg); - errorcnt++; - } - break; - case 'd': - extended_i2c_exercise = 1; - break; - case 'h': - usage(errorcnt); - break; - case 0: /* auto-handled option */ - break; - case '?': - if (optopt) - printf("Unrecognized option: -%c\n", optopt); - else - printf("Unrecognized option: %s\n", - argv[optind - 1]); - errorcnt++; - break; - case ':': - printf("Missing argument to %s\n", argv[optind - 1]); - errorcnt++; - break; - default: - printf("Internal error at %s:%d\n", __FILE__, __LINE__); - exit(1); - } - } - - if (errorcnt) - usage(errorcnt); - -} - -/* USB transfer related */ -static uint8_t rx_buf[1024]; -static uint8_t tx_buf[1024]; - -static struct libusb_device_handle *devh; -static struct libusb_transfer *rx_transfer; -static struct libusb_transfer *tx_transfer; - -static int claimed_iface; -static int iface_num = -1; -static int do_exit; - -static void request_exit(const char *format, ...) -{ - va_list ap; - va_start(ap, format); - vfprintf(stderr, format, ap); - va_end(ap); - do_exit++; /* Why need this ? */ - - if (tx_transfer) - libusb_free_transfer(tx_transfer); - if (rx_transfer) - libusb_free_transfer(rx_transfer); - if (devh) { - if (claimed_iface) - libusb_release_interface(devh, iface_num); - libusb_close(devh); - } - libusb_exit(NULL); - exit(1); -} - -#define DIE(msg, r) \ - request_exit("%s: line %d, %s\n", msg, __LINE__, \ - libusb_error_name(r)) - -static void sighandler(int signum) -{ - request_exit("caught signal %d: %s\n", signum, strsignal(signum)); -} - -static int find_interface_with_endpoint(int want_ep_num) -{ - int iface_num = -1; - int r, i, j, k; - struct libusb_device *dev; - struct libusb_config_descriptor *conf = 0; - const struct libusb_interface *iface0; - const struct libusb_interface_descriptor *iface; - const struct libusb_endpoint_descriptor *ep; - - dev = libusb_get_device(devh); - r = libusb_get_active_config_descriptor(dev, &conf); - if (r < 0) { - DIE("get_active_config", r); - return -1; - } - - for (i = 0; i < conf->bNumInterfaces; i++) { - iface0 = &conf->interface[i]; - for (j = 0; j < iface0->num_altsetting; j++) { - iface = &iface0->altsetting[j]; - for (k = 0; k < iface->bNumEndpoints; k++) { - ep = &iface->endpoint[k]; - if (ep->bEndpointAddress == want_ep_num) { - iface_num = i; - break; - } - } - } - } - - libusb_free_config_descriptor(conf); - return iface_num; -} - -static void init_with_libusb(void) -{ - int r = 1; - - printf("init usb interface\n"); - r = libusb_init(NULL); - if (r < 0) - DIE("init", r); - - printf("open_device %04x:%04x\n", vid, pid); - devh = libusb_open_device_with_vid_pid(NULL, vid, pid); - if (!devh) - request_exit("can't find device\n"); - - iface_num = find_interface_with_endpoint(ep_num); - if (iface_num < 0) - request_exit("can't find interface owning EP %d\n", ep_num); - - printf("claim_interface %d to use endpoint %d\n", iface_num, ep_num); - r = libusb_claim_interface(devh, iface_num); - if (r < 0) - DIE("claim interface", r); - claimed_iface = 1; -} - -static void register_sigaction(void) -{ - struct sigaction sigact; - sigact.sa_handler = sighandler; - sigemptyset(&sigact.sa_mask); - sigact.sa_flags = 0; - sigaction(SIGINT, &sigact, NULL); - sigaction(SIGTERM, &sigact, NULL); - sigaction(SIGQUIT, &sigact, NULL); -} - -/* Transfer over libusb */ -#define I2C_PORT_ON_HAMMER 0x00 -#define I2C_ADDRESS_ON_HAMMER 0x15 - -static int check_read_status(int r, int expected, int actual) -{ - int i; - if (r) - printf("Warning: libusb_bulk_transfer return error : %d\n", r); - if (actual != (expected + 4)) { - printf("Warning: Not reading back %d bytes.\n", expected); - r = 1; - } - - /* Check transaction status as defined in usb_i2c.h */ - for (i = 0; i < 4; ++i) - if (rx_buf[i] != 0) - break; - - if (i != 4) { - r = le_bytes_to_int(rx_buf); - printf("Warning: Defined error code (%d) returned.\n", r); - } - - if (r || extended_i2c_exercise) { - printf("\nDumping the receive buffer:\n"); - printf(" Recv %d bytes from USB hosts.\n", actual); - for (i = 0; i < actual; ++i) - printf(" [%2d]bytes: 0x%0x\n", i, rx_buf[i]); - } - return r; -} - -#define MAX_USB_PACKET_SIZE 64 -#define PRIMITIVE_READING_SIZE 60 - -static int libusb_single_write_and_read( - const uint8_t *to_write, uint16_t write_length, - uint8_t *to_read, uint16_t read_length) -{ - int r; - int tx_ready; - int remains; - int sent_bytes = 0; - int actual_length = -1; - int offset = read_length > PRIMITIVE_READING_SIZE ? 6 : 4; - tx_transfer = rx_transfer = 0; - - memmove(tx_buf + offset, to_write, write_length); - tx_buf[0] = I2C_PORT_ON_HAMMER | ((write_length >> 8) << 4); - tx_buf[1] = I2C_ADDRESS_ON_HAMMER; - tx_buf[2] = write_length & 0xff; - if (read_length > PRIMITIVE_READING_SIZE) { - tx_buf[3] = (read_length & 0x7f) | (1 << 7); - tx_buf[4] = read_length >> 7; - if (extended_i2c_exercise) { - printf("Triggering extended reading." - "rc:%0x, rc1:%0x\n", - tx_buf[3], tx_buf[4]); - printf("Expecting %d Bytes.\n", - (tx_buf[3] & 0x7f) | (tx_buf[4] << 7)); - } - } else { - tx_buf[3] = read_length; - } - - /* - * TODO: This loop is probably not required as we write the whole block - * in one transaction. - */ - while (sent_bytes < (offset + write_length)) { - tx_ready = remains = (offset + write_length) - sent_bytes; - - r = libusb_bulk_transfer(devh, - (ep_num | LIBUSB_ENDPOINT_OUT), - tx_buf + sent_bytes, tx_ready, - &actual_length, 5000); - if (r == 0 && actual_length == tx_ready) { - r = libusb_bulk_transfer(devh, - (ep_num | LIBUSB_ENDPOINT_IN), - rx_buf, sizeof(rx_buf), - &actual_length, 5000); - } - r = check_read_status( - r, (remains == tx_ready) ? read_length : 0, - actual_length); - if (r) - break; - sent_bytes += tx_ready; - } - return r; -} - -/* Control Elan trackpad I2C over USB */ -#define ETP_I2C_INF_LENGTH 2 - -static int elan_write_and_read( - int reg, uint8_t *buf, int read_length, - int with_cmd, int cmd) -{ - - tx_buf[0] = (reg >> 0) & 0xff; - tx_buf[1] = (reg >> 8) & 0xff; - if (with_cmd) { - tx_buf[2] = (cmd >> 0) & 0xff; - tx_buf[3] = (cmd >> 8) & 0xff; - } - return libusb_single_write_and_read( - tx_buf, with_cmd ? 4 : 2, rx_buf, read_length); -} - -static int elan_read_block(int reg, uint8_t *buf, int read_length) -{ - return elan_write_and_read(reg, buf, read_length, 0, 0); -} - -static int elan_read_cmd(int reg) -{ - return elan_read_block(reg, rx_buf, ETP_I2C_INF_LENGTH); -} - -static int elan_write_cmd(int reg, int cmd) -{ - return elan_write_and_read(reg, rx_buf, 0, 1, cmd); -} - -/* Elan trackpad firmware information related */ -#define ETP_I2C_IAP_VERSION_CMD 0x0110 -#define ETP_I2C_FW_VERSION_CMD 0x0102 -#define ETP_I2C_IAP_CHECKSUM_CMD 0x0315 -#define ETP_I2C_FW_CHECKSUM_CMD 0x030F -#define ETP_I2C_OSM_VERSION_CMD 0x0103 - -static int elan_get_version(int is_iap) -{ - elan_read_cmd( - is_iap ? ETP_I2C_IAP_VERSION_CMD : ETP_I2C_FW_VERSION_CMD); - return le_bytes_to_int(rx_buf + 4); -} - -static void elan_get_ic_page_count(void) -{ - elan_read_cmd(ETP_I2C_OSM_VERSION_CMD); - - ic_type = rx_buf[5]; - printf("ic_type: %02x\n", ic_type); - - switch (ic_type) { - case 0x09: - fw_page_count = 768; - break; - case 0x0D: - fw_page_count = 896; - break; - case 0x00: - case 0x10: - case 0x14: - fw_page_count = 1024; - break; - default: - request_exit("The IC type is not supported.\n"); - } - - iap_version = elan_get_version(1); - if (ic_type == 0x14 && iap_version >= 2) { - fw_page_count /= 8; - fw_page_size = 512; - } else if (ic_type >= 0x0D && iap_version >= 1) { - fw_page_count /= 2; - fw_page_size = 128; - } else { - fw_page_size = 64; - } -} - -static int elan_get_checksum(int is_iap) -{ - elan_read_cmd( - is_iap ? ETP_I2C_IAP_CHECKSUM_CMD : ETP_I2C_FW_CHECKSUM_CMD); - return le_bytes_to_int(rx_buf + 4); -} - -static uint16_t elan_get_fw_info(void) -{ - int fw_version = -1; - uint16_t iap_checksum = 0xffff; - uint16_t fw_checksum = 0xffff; - - printf("Querying device info...\n"); - fw_checksum = elan_get_checksum(0); - iap_checksum = elan_get_checksum(1); - fw_version = elan_get_version(0); - iap_version = elan_get_version(1); - printf("IAP version: %4x, FW version: %4x\n", - iap_version, fw_version); - printf("IAP checksum: %4x, FW checksum: %4x\n", - iap_checksum, fw_checksum); - return fw_checksum; -} - -/* Update preparation */ -#define ETP_I2C_IAP_RESET_CMD 0x0314 -#define ETP_I2C_IAP_RESET 0xF0F0 -#define ETP_I2C_IAP_CTRL_CMD 0x0310 -#define ETP_I2C_MAIN_MODE_ON (1 << 9) -#define ETP_I2C_IAP_CMD 0x0311 -#define ETP_I2C_IAP_PASSWORD 0x1EA5 -#define ETP_I2C_IAP_TYPE_CMD 0x0304 - -static int elan_in_main_mode(void) -{ - elan_read_cmd(ETP_I2C_IAP_CTRL_CMD); - return le_bytes_to_int(rx_buf + 4) & ETP_I2C_MAIN_MODE_ON; -} - -static int elan_read_write_iap_type(void) -{ - for (int retry = 0; retry < 3; ++retry) { - uint16_t val; - - if (elan_write_cmd(ETP_I2C_IAP_TYPE_CMD, - fw_page_size / 2)) - return -1; - - if (elan_read_cmd(ETP_I2C_IAP_TYPE_CMD)) - return -1; - - val = le_bytes_to_int(rx_buf + 4); - if (val == fw_page_size / 2) { - printf("%s: OK\n", __func__); - return 0; - } - - } - return -1; -} - -static void elan_prepare_for_update(void) -{ - printf("%s\n", __func__); - - int initial_mode = elan_in_main_mode(); - if (!initial_mode) { - printf("In IAP mode, reset IC.\n"); - elan_write_cmd(ETP_I2C_IAP_RESET_CMD, ETP_I2C_IAP_RESET); - usleep(30 * 1000); - } - - /* Send the passphrase */ - elan_write_cmd(ETP_I2C_IAP_CMD, ETP_I2C_IAP_PASSWORD); - usleep((initial_mode ? 100 : 30) * 1000); - - /* We should be in the IAP mode now */ - if (elan_in_main_mode()) - request_exit("Failure to enter IAP mode, still in main mode\n"); - - if (ic_type >= 0x0D && iap_version >= 1) { - if (elan_read_write_iap_type()) - request_exit("Failure to set IAP mode\n"); - } - - /* Send the passphrase again */ - elan_write_cmd(ETP_I2C_IAP_CMD, ETP_I2C_IAP_PASSWORD); - usleep(30 * 1000); - - /* Verify the password */ - if (elan_read_cmd(ETP_I2C_IAP_CMD)) - request_exit("cannot read iap password.\n"); - if (le_bytes_to_int(rx_buf + 4) != ETP_I2C_IAP_PASSWORD) - request_exit("Got an unexpected IAP password %4x\n", - le_bytes_to_int(rx_buf + 4)); -} - -/* Firmware block update */ -#define ETP_IAP_START_ADDR 0x0083 - -static uint16_t elan_calc_checksum(uint8_t *data, int length) -{ - uint16_t checksum = 0; - for (int i = 0; i < length; i += 2) - checksum += ((uint16_t)(data[i+1]) << 8) | (data[i]); - return checksum; -} - -static int elan_get_iap_addr(void) -{ - return le_bytes_to_int(fw_data + ETP_IAP_START_ADDR * 2) * 2; -} - -#define ETP_I2C_IAP_REG_L 0x01 -#define ETP_I2C_IAP_REG_H 0x06 - -#define ETP_FW_IAP_PAGE_ERR (1 << 5) -#define ETP_FW_IAP_INTF_ERR (1 << 4) - -static int elan_write_fw_block(uint8_t *raw_data, uint16_t checksum) -{ - uint8_t page_store[MAX_FW_PAGE_SIZE + 4]; - int rv; - - page_store[0] = ETP_I2C_IAP_REG_L; - page_store[1] = ETP_I2C_IAP_REG_H; - memcpy(page_store + 2, raw_data, fw_page_size); - page_store[fw_page_size + 2 + 0] = (checksum >> 0) & 0xff; - page_store[fw_page_size + 2 + 1] = (checksum >> 8) & 0xff; - - rv = libusb_single_write_and_read( - page_store, fw_page_size + 4, rx_buf, 0); - if (rv) - return rv; - usleep((fw_page_size >= 512 ? 50 : 35) * 1000); - elan_read_cmd(ETP_I2C_IAP_CTRL_CMD); - rv = le_bytes_to_int(rx_buf + 4); - if (rv & (ETP_FW_IAP_PAGE_ERR | ETP_FW_IAP_INTF_ERR)) { - printf("IAP reports failed write : %x\n", rv); - return rv; - } - return 0; -} - - -static uint16_t elan_update_firmware(void) -{ - uint16_t checksum = 0, block_checksum; - int rv; - - printf("%s\n", __func__); - - for (int i = elan_get_iap_addr(); i < fw_size; i += fw_page_size) { - printf("\rUpdating page %3d...", i / fw_page_size); - fflush(stdout); - block_checksum = elan_calc_checksum(fw_data + i, fw_page_size); - rv = elan_write_fw_block(fw_data + i, block_checksum); - if (rv) - request_exit("Failed to update.\n"); - checksum += block_checksum; - printf(" Updated, checksum: %d", checksum); - fflush(stdout); - } - return checksum; -} - -static void pretty_print_buffer(uint8_t *buf, int len) -{ - int i; - - printf("Buffer = 0x"); - for (i = 0; i < len; ++i) - printf("%02X", buf[i]); - printf("\n"); -} - -int main(int argc, char *argv[]) -{ - uint16_t local_checksum; - uint16_t remote_checksum; - - parse_cmdline(argc, argv); - init_with_libusb(); - register_sigaction(); - - /* - * Judge IC type and get page count first. - * Then check the FW file. - */ - elan_get_ic_page_count(); - fw_size = fw_page_count * fw_page_size; - printf("FW has %d bytes x %d pages\n", fw_page_size, fw_page_count); - - /* Read the FW file */ - FILE *f = fopen(firmware_binary, "rb"); - if (!f) - request_exit("Cannot find binary: %s\n", firmware_binary); - if (fread(fw_data, 1, fw_size, f) != (unsigned int)fw_size) - request_exit("binary size mismatch, expect %d\n", fw_size); - - /* - * It is possible that you are not able to get firmware info. This - * might due to an incomplete update last time - */ - elan_get_fw_info(); - - /* Trigger an I2C transaction of expecting reading of 633 bytes. */ - if (extended_i2c_exercise) { - tx_buf[0] = 0x05; - tx_buf[1] = 0x00; - tx_buf[2] = 0x3C; - tx_buf[3] = 0x02; - tx_buf[4] = 0x06; - tx_buf[5] = 0x00; - libusb_single_write_and_read(tx_buf, 6, rx_buf, 633); - pretty_print_buffer(rx_buf, 637); - } - - /* Get the trackpad ready for receiving update */ - elan_prepare_for_update(); - - local_checksum = elan_update_firmware(); - /* Wait for a reset */ - usleep(600 * 1000); - remote_checksum = elan_get_checksum(1); - if (remote_checksum != local_checksum) - printf("checksum diff local=[%04X], remote=[%04X]\n", - local_checksum, remote_checksum); - - /* Print the updated firmware information */ - elan_get_fw_info(); - return 0; -} diff --git a/extra/usb_console/.gitignore b/extra/usb_console/.gitignore deleted file mode 100644 index efee63e87e..0000000000 --- a/extra/usb_console/.gitignore +++ /dev/null @@ -1 +0,0 @@ -usb_console diff --git a/extra/usb_console/Makefile b/extra/usb_console/Makefile deleted file mode 100644 index bddca1d0a2..0000000000 --- a/extra/usb_console/Makefile +++ /dev/null @@ -1,34 +0,0 @@ -# Copyright 2015 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. - -PROGRAM := usb_console -SOURCE := $(PROGRAM).c -LIBS := -LFLAGS := -CFLAGS := -std=gnu99 \ - -g3 \ - -O3 \ - -Wall \ - -Werror \ - -Wpointer-arith \ - -Wcast-align \ - -Wcast-qual \ - -Wundef \ - -Wsign-compare \ - -Wredundant-decls \ - -Wmissing-declarations - -# -# Add libusb-1.0 required flags -# -LIBS += $(shell pkg-config --libs libusb-1.0) -CFLAGS += $(shell pkg-config --cflags libusb-1.0) - -$(PROGRAM): $(SOURCE) Makefile - gcc $(CFLAGS) $(SOURCE) $(LFLAGS) $(LIBS) -o $@ - -.PHONY: clean - -clean: - rm -rf $(PROGRAM) *~ diff --git a/extra/usb_console/usb_console.c b/extra/usb_console/usb_console.c deleted file mode 100644 index e4f8ea504f..0000000000 --- a/extra/usb_console/usb_console.c +++ /dev/null @@ -1,457 +0,0 @@ -/* - * Copyright 2015 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. - */ - -#include <errno.h> -#include <getopt.h> -#include <poll.h> -#include <signal.h> -#include <stdarg.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/select.h> -#include <unistd.h> - -#include <libusb.h> - -/* Options */ -static uint16_t vid = 0x18d1; /* Google */ -static uint16_t pid = 0x500f; /* discovery-stm32f072 */ -static uint8_t ep_num = 4; /* console endpoint */ - -static unsigned char rx_buf[1024]; /* much too big */ -static unsigned char tx_buf[1024]; /* much too big */ -static const struct libusb_pollfd **usb_fds; -static struct libusb_device_handle *devh; -static struct libusb_transfer *rx_transfer; -static struct libusb_transfer *tx_transfer; -static int tx_ready; -static int do_exit; - -static void request_exit(const char *format, ...) -{ - va_list ap; - va_start(ap, format); - vfprintf(stderr, format, ap); - va_end(ap); - do_exit++; -} - -#define BOO(msg, r) \ - request_exit("%s: line %d, %s\n", msg, __LINE__, \ - libusb_error_name(r)) - -static void sighandler(int signum) -{ - request_exit("caught signal %d: %s\n", signum, strsignal(signum)); -} - -#if 0 -static void show_xfer(const char *msg, struct libusb_transfer *t) -{ - printf("%s: f=%02x ep=%02x type=%d status=%d len=%d actlen=%d\n", msg, - t->flags, - t->endpoint, t->type, t->status, t->length, t->actual_length); -} -#endif - -static void LIBUSB_CALL cb_rx(struct libusb_transfer *transfer) -{ - int r; - - if (transfer->actual_length) { - transfer->buffer[transfer->actual_length] = '\0'; - fputs((char *)transfer->buffer, stdout); - fflush(stdout); - } - - if (transfer->status == LIBUSB_TRANSFER_CANCELLED) { - printf("rx_transfer cancelled\n"); - if (rx_transfer) - libusb_free_transfer(rx_transfer); - rx_transfer = NULL; - return; - } - - /* Try again */ - if (!do_exit) { - r = libusb_submit_transfer(rx_transfer); - if (r < 0) - BOO("resubmit rx_transfer failed", r); - } -} - -static void LIBUSB_CALL cb_tx(struct libusb_transfer *transfer) -{ - if (transfer->status == LIBUSB_TRANSFER_CANCELLED) { - if (tx_transfer) - libusb_free_transfer(tx_transfer); - tx_transfer = NULL; - request_exit("tx_transfer cancelled\n"); - return; - } - - if (tx_ready != transfer->actual_length) - printf("%s: only sent %d/%d bytes\n", __func__, - transfer->actual_length, tx_ready); - - tx_ready = 0; -} - -static void send_tx(int len) -{ - int r; - - libusb_fill_bulk_transfer(tx_transfer, devh, - ep_num, tx_buf, len, cb_tx, NULL, 0); - - r = libusb_submit_transfer(tx_transfer); - if (r < 0) - BOO("submit tx_transfer failed", r); -} - -static void handle_stdin(void) -{ - static unsigned int i; - int n; - - for (; i < sizeof(tx_buf) - 1; i++) { - n = read(0, tx_buf + i, 1); - if (n == 0) { - request_exit("EOF on stdin\n"); - return; - } - if (n < 0) { - request_exit("stdin: %s\n", strerror(errno)); - return; - } - - if (tx_buf[i] == '\n') { - i++; - tx_buf[i] = '\0'; - break; - } - } - - tx_ready = strlen((char *)tx_buf) + 1; - send_tx(tx_ready); - i = 0; -} - -static void handle_libusb(void) -{ - struct timeval tv = { 0, 0 }; - int r; - - r = libusb_handle_events_timeout_completed(NULL, &tv, &do_exit); - if (r < 0) - BOO("libusb event problem", r); -} - -static int wait_for_stuff_to_happen(void) -{ - int i, r, nfds = 0; - fd_set readset, writeset; - struct timeval tv = { 1, 0 }; - - if (!usb_fds) { - request_exit("No usb_fds to watch\n"); - return -1; - } - - FD_ZERO(&readset); - FD_ZERO(&writeset); - /* always watch stdin */ - FD_SET(0, &readset); - - for (i = 0; usb_fds[i]; i++) { - int fd = usb_fds[i]->fd; - short events = usb_fds[i]->events; - if (fd > nfds) - nfds = fd; - - if (events & POLLIN) - FD_SET(fd, &readset); - if (events & POLLOUT) - FD_SET(fd, &writeset); - } - - r = select(nfds + 1, &readset, &writeset, NULL, &tv); - if (r < 0) { - request_exit("select: %s\n", strerror(errno)); - return -1; - } - - if (r == 0) /* timed out */ - return 0; - - /* Ignore stdin until we've finished sending the current line */ - if (!tx_ready && FD_ISSET(0, &readset)) - return 1; - - /* libusb, then */ - return 2; -} - -static int find_interface_with_endpoint(int want_ep_num) -{ - int iface_num = -1; - int r, i, j, k; - struct libusb_device *dev; - struct libusb_config_descriptor *conf = 0; - const struct libusb_interface *iface0; - const struct libusb_interface_descriptor *iface; - const struct libusb_endpoint_descriptor *ep; - - dev = libusb_get_device(devh); - r = libusb_get_active_config_descriptor(dev, &conf); - if (r < 0) { - BOO("get_active_config", r); - return -1; - } - - for (i = 0; i < conf->bNumInterfaces; i++) { - iface0 = &conf->interface[i]; - for (j = 0; j < iface0->num_altsetting; j++) { - iface = &iface0->altsetting[j]; - for (k = 0; k < iface->bNumEndpoints; k++) { - ep = &iface->endpoint[k]; - if (ep->bEndpointAddress == want_ep_num) { - iface_num = i; - break; - } - } - } - } - - libusb_free_config_descriptor(conf); - return iface_num; -} - -static char *progname; -static char *short_opts = ":v:p:e:h"; -static const struct option long_opts[] = { - /* name hasarg *flag val */ - {"vid", 1, NULL, 'v'}, - {"pid", 1, NULL, 'p'}, - {"ep", 1, NULL, 'e'}, - {"help", 0, NULL, 'h'}, - {NULL, 0, NULL, 0}, -}; - -static void usage(int errs) -{ - printf("\nUsage: %s [options]\n" - "\n" - "A very simple serial console emulator\n" - "\n" - "Options:\n" - "\n" - " -v,--vid HEXVAL Vendor ID (default %04x)\n" - " -p,--pid HEXVAL Product ID (default %04x)\n" - " -e,--ep NUM Endpoint (default %d)\n" - " -h,--help Show this message\n" - "\n", progname, vid, pid, ep_num); - - exit(!!errs); -} - -int main(int argc, char *argv[]) -{ - struct sigaction sigact; - int iface_num; - int claimed_iface = 0; - int r = 1; - int errorcnt = 0; - char *e = 0; - int i; - - progname = strrchr(argv[0], '/'); - if (progname) - progname++; - else - progname = argv[0]; - - opterr = 0; /* quiet, you */ - while ((i = getopt_long(argc, argv, short_opts, long_opts, 0)) != -1) { - switch (i) { - case 'p': - pid = (uint16_t) strtoull(optarg, &e, 16); - if (!*optarg || (e && *e)) { - printf("Invalid argument: \"%s\"\n", optarg); - errorcnt++; - } - break; - case 'v': - vid = (uint16_t) strtoull(optarg, &e, 16); - if (!*optarg || (e && *e)) { - printf("Invalid argument: \"%s\"\n", optarg); - errorcnt++; - } - break; - case 'e': - ep_num = (uint8_t) strtoull(optarg, &e, 0); - if (!*optarg || (e && *e)) { - printf("Invalid argument: \"%s\"\n", optarg); - errorcnt++; - } - break; - case 'h': - usage(errorcnt); - break; - case 0: /* auto-handled option */ - break; - case '?': - if (optopt) - printf("Unrecognized option: -%c\n", optopt); - else - printf("Unrecognized option: %s\n", - argv[optind - 1]); - errorcnt++; - break; - case ':': - printf("Missing argument to %s\n", argv[optind - 1]); - errorcnt++; - break; - default: - printf("Internal error at %s:%d\n", __FILE__, __LINE__); - exit(1); - } - } - - if (errorcnt) - usage(errorcnt); - - printf("init\n"); - r = libusb_init(NULL); - if (r < 0) { - BOO("init", r); - exit(1); - } - - printf("open_device %04x:%04x\n", vid, pid); - devh = libusb_open_device_with_vid_pid(NULL, vid, pid); - if (!devh) { - printf("can't find device\n"); - goto out; - } - - iface_num = find_interface_with_endpoint(ep_num); - if (iface_num < 0) { - printf("can't find interface owning EP %d\n", ep_num); - goto out; - } - /* NOTE: The EP might be on an alternate interface. We should switch - * to the correct one. */ - - printf("claim_interface %d to use endpoint %d\n", iface_num, ep_num); - r = libusb_claim_interface(devh, iface_num); - if (r < 0) { - BOO("claim interface", r); - goto out; - } - claimed_iface = 1; - - sigact.sa_handler = sighandler; - sigemptyset(&sigact.sa_mask); - sigact.sa_flags = 0; - sigaction(SIGINT, &sigact, NULL); - sigaction(SIGTERM, &sigact, NULL); - sigaction(SIGQUIT, &sigact, NULL); - - printf("alloc_transfers\n"); - rx_transfer = libusb_alloc_transfer(0); - if (!rx_transfer) { - printf("can't alloc rx_transfer"); - goto out; - } - libusb_fill_bulk_transfer(rx_transfer, devh, - 0x80 | ep_num, - rx_buf, sizeof(rx_buf), cb_rx, NULL, 0); - - tx_transfer = libusb_alloc_transfer(0); - if (!tx_transfer) { - printf("can't alloc tx_transfer"); - goto out; - } - - printf("get_pollfds\n"); - usb_fds = libusb_get_pollfds(NULL); - if (!usb_fds) { - printf("can't get usb_fds\n"); - goto out; - } - - printf("submit rx_transfer\n"); - r = libusb_submit_transfer(rx_transfer); - if (r < 0) { - BOO("submit rx_transfer", r); - goto out; - } - - printf("READY\n-------\n"); - while (!do_exit) { - r = wait_for_stuff_to_happen(); - switch (r) { - case 0: /* timed out */ - /* printf("."); */ - /* fflush(stdout); */ - break; - case 1: /* stdin ready */ - handle_stdin(); - break; - case 2: /* libusb ready */ - handle_libusb(); - break; - } - } - - printf("-------\nshutting down\n"); - - r = libusb_cancel_transfer(rx_transfer); - if (r < 0) { - BOO("cancel rx_transfer", r); - if (rx_transfer) - libusb_free_transfer(rx_transfer); - rx_transfer = 0; - } - - if (tx_ready) { - r = libusb_cancel_transfer(tx_transfer); - if (r < 0) { - BOO("cancel tx_transfer", r); - if (tx_transfer) - libusb_free_transfer(tx_transfer); - tx_transfer = 0; - } - } - - while (rx_transfer) { - printf("draining events...\n"); - r = libusb_handle_events(NULL); - if (r < 0) { - printf("Huh: %s\n", libusb_error_name(r)); - break; - } - } - - printf("bye\n"); - r = 0; - out: - if (tx_transfer) - libusb_free_transfer(tx_transfer); - if (rx_transfer) - libusb_free_transfer(rx_transfer); - - if (devh) { - if (claimed_iface) - libusb_release_interface(devh, iface_num); - libusb_close(devh); - } - libusb_exit(NULL); - - return r; -} diff --git a/extra/usb_gpio/.gitignore b/extra/usb_gpio/.gitignore deleted file mode 100644 index 239f1ed4d8..0000000000 --- a/extra/usb_gpio/.gitignore +++ /dev/null @@ -1 +0,0 @@ -usb_gpio diff --git a/extra/usb_gpio/Makefile b/extra/usb_gpio/Makefile deleted file mode 100644 index 644e3ee70f..0000000000 --- a/extra/usb_gpio/Makefile +++ /dev/null @@ -1,34 +0,0 @@ -# Copyright 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. - -PROGRAM := usb_gpio -SOURCE := $(PROGRAM).c -LIBS := -LFLAGS := -CFLAGS := -std=gnu99 \ - -g3 \ - -O3 \ - -Wall \ - -Werror \ - -Wpointer-arith \ - -Wcast-align \ - -Wcast-qual \ - -Wundef \ - -Wsign-compare \ - -Wredundant-decls \ - -Wmissing-declarations - -# -# Add libusb-1.0 required flags -# -LIBS += $(shell pkg-config --libs libusb-1.0) -CFLAGS += $(shell pkg-config --cflags libusb-1.0) - -$(PROGRAM): $(SOURCE) Makefile - gcc $(CFLAGS) $(SOURCE) $(LFLAGS) $(LIBS) -o $@ - -.PHONY: clean - -clean: - rm -rf $(PROGRAM) *~ diff --git a/extra/usb_gpio/usb_gpio.c b/extra/usb_gpio/usb_gpio.c deleted file mode 100644 index 8973f3d304..0000000000 --- a/extra/usb_gpio/usb_gpio.c +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright 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. - */ - -#include <libusb.h> -#include <stdint.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> - -#define CHECK(expression) \ - ({ \ - int error__ = (expression); \ - \ - if (error__ != 0) { \ - fprintf(stderr, \ - "libusb error: %s:%d %s\n", \ - __FILE__, \ - __LINE__, \ - libusb_error_name(error__)); \ - return error__; \ - } \ - \ - error__; \ - }) - -#define TRANSFER_TIMEOUT_MS 100 - -static int gpio_write(libusb_device_handle *device, - uint32_t set_mask, - uint32_t clear_mask) -{ - uint8_t command[8]; - int transferred; - - command[0] = (set_mask >> 0) & 0xff; - command[1] = (set_mask >> 8) & 0xff; - command[2] = (set_mask >> 16) & 0xff; - command[3] = (set_mask >> 24) & 0xff; - - command[4] = (clear_mask >> 0) & 0xff; - command[5] = (clear_mask >> 8) & 0xff; - command[6] = (clear_mask >> 16) & 0xff; - command[7] = (clear_mask >> 24) & 0xff; - - CHECK(libusb_bulk_transfer(device, - LIBUSB_ENDPOINT_OUT | 2, - command, - sizeof(command), - &transferred, - TRANSFER_TIMEOUT_MS)); - - if (transferred != sizeof(command)) { - fprintf(stderr, - "Failed to transfer full command " - "(sent %d of %d bytes)\n", - transferred, - (int)sizeof(command)); - return LIBUSB_ERROR_OTHER; - } - - return 0; -} - -static int gpio_read(libusb_device_handle *device, uint32_t *mask) -{ - uint8_t response[4]; - int transferred; - - /* - * The first query does triggers the sampling of the GPIO values, the - * second query reads them back. - */ - CHECK(libusb_bulk_transfer(device, - LIBUSB_ENDPOINT_IN | 2, - response, - sizeof(response), - &transferred, - TRANSFER_TIMEOUT_MS)); - - CHECK(libusb_bulk_transfer(device, - LIBUSB_ENDPOINT_IN | 2, - response, - sizeof(response), - &transferred, - TRANSFER_TIMEOUT_MS)); - - if (transferred != sizeof(response)) { - fprintf(stderr, - "Failed to transfer full response " - "(read %d of %d bytes)\n", - transferred, - (int)sizeof(response)); - return LIBUSB_ERROR_OTHER; - } - - *mask = (response[0] << 0 | - response[1] << 8 | - response[2] << 16 | - response[3] << 24); - - return 0; -} - -int main(int argc, char **argv) -{ - libusb_context *context; - libusb_device_handle *device; - uint16_t vendor_id = 0x18d1; /* Google */ - uint16_t product_id = 0x500f; /* discovery-stm32f072 */ - int interface = 1; /* gpio interface */ - - if (!(argc == 2 && strcmp(argv[1], "read") == 0) && - !(argc == 4 && strcmp(argv[1], "write") == 0)) { - puts("Usage: usb_gpio read\n" - " usb_gpio write <set_mask> <clear_mask>\n"); - return 1; - } - - CHECK(libusb_init(&context)); - - device = libusb_open_device_with_vid_pid(context, - vendor_id, - product_id); - - if (device == NULL) { - fprintf(stderr, - "Unable to find device 0x%04x:0x%04x\n", - vendor_id, - product_id); - return 1; - } - - CHECK(libusb_set_auto_detach_kernel_driver(device, 1)); - CHECK(libusb_claim_interface(device, interface)); - - if (argc == 2 && strcmp(argv[1], "read") == 0) { - uint32_t mask; - - CHECK(gpio_read(device, &mask)); - - printf("GPIO mask: 0x%08x\n", mask); - } - - if (argc == 4 && strcmp(argv[1], "write") == 0) { - uint32_t set_mask = strtol(argv[2], NULL, 0); - uint32_t clear_mask = strtol(argv[3], NULL, 0); - - CHECK(gpio_write(device, set_mask, clear_mask)); - } - - libusb_close(device); - libusb_exit(context); - - return 0; -} diff --git a/extra/usb_power/__init__.py b/extra/usb_power/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 --- a/extra/usb_power/__init__.py +++ /dev/null diff --git a/extra/usb_power/board/kevin/kevin.board b/extra/usb_power/board/kevin/kevin.board deleted file mode 100644 index 8ab59573c6..0000000000 --- a/extra/usb_power/board/kevin/kevin.board +++ /dev/null @@ -1,18 +0,0 @@ -[ -{"name": "pp5000", "rs": 0.01, "sweetberry": "A", "channel": 0}, -{"name": "ppvar_gpu", "rs": 0.01, "sweetberry": "A", "channel": 1}, -{"name": "pp3300_wifi_bt", "rs": 0.01, "sweetberry": "A", "channel": 2}, -{"name": "pp1500_ap_io", "rs": 0.01, "sweetberry": "A", "channel": 3}, -{"name": "pp3300_alw", "rs": 0.01, "sweetberry": "A", "channel": 4}, -{"name": "ppvar_litcpu", "rs": 0.01, "sweetberry": "A", "channel": 5}, -{"name": "pp1800_s0", "rs": 0.01, "sweetberry": "A", "channel": 6}, -{"name": "pp3300_haven", "rs": 0.1, "sweetberry": "A", "channel": 7}, -{"name": "ppvar_bigcpu", "rs": 0.01, "sweetberry": "A", "channel": 8}, -{"name": "pp900_ap", "rs": 0.01, "sweetberry": "A", "channel": 9}, -{"name": "pp1800_ec", "rs": 0.1, "sweetberry": "A", "channel": 10}, -{"name": "pp1800_sensor", "rs": 0.01, "sweetberry": "A", "channel": 11}, -{"name": "pp1800_alw", "rs": 0.01, "sweetberry": "A", "channel": 12}, -{"name": "pp1200_lpddr", "rs": 0.01, "sweetberry": "A", "channel": 13}, -{"name": "pp3300_ec", "rs": 0.1, "sweetberry": "A", "channel": 14}, -{"name": "pp3300_s0", "rs": 0.01, "sweetberry": "A", "channel": 15} -] diff --git a/extra/usb_power/board/kevin/kevin_all.scenario b/extra/usb_power/board/kevin/kevin_all.scenario deleted file mode 100644 index dbc3953364..0000000000 --- a/extra/usb_power/board/kevin/kevin_all.scenario +++ /dev/null @@ -1,18 +0,0 @@ -[ -"pp5000", -"ppvar_gpu", -"pp3300_wifi_bt", -"pp1500_ap_io", -"pp3300_alw", -"ppvar_litcpu", -"pp1800_s0", -"pp3300_haven", -"ppvar_bigcpu", -"pp900_ap", -"pp1800_ec", -"pp1800_sensor", -"pp1800_alw", -"pp1200_lpddr", -"pp3300_ec", -"pp3300_s0" -] diff --git a/extra/usb_power/board/marlin/marlin.board b/extra/usb_power/board/marlin/marlin.board deleted file mode 100644 index dc4cdad258..0000000000 --- a/extra/usb_power/board/marlin/marlin.board +++ /dev/null @@ -1,74 +0,0 @@ -[ -{"name": "VBAT", "rs": 0.01, "sweetberry": "A", "net": "", "channel": 0}, -{"name": "VBAT_", "rs": 0.01, "sweetberry": "B", "net": "", "channel": 0}, -{"name": "VDD_MEM", "rs": 0.05, "sweetberry": "A", "net": "V_MEM_0V875", "channel": 1}, -{"name": "VDD_EBI_PHY", "rs": 0.1, "sweetberry": "A", "net": "V_EBI_0V875", "channel": 2}, -{"name": "VDD_PCIE_1P8", "rs": 0.5, "sweetberry": "A", "net": "V_USB_1V8", "channel": 3}, -{"name": "VDD_PCIE_CORE", "rs": 0.1, "sweetberry": "A", "net": "V_PCIE_0V925", "channel": 4}, -{"name": "VDD_MIPI_CSI", "rs": 0.1, "sweetberry": "A", "net": "V_CSI_DSI_1V25", "channel": 5}, -{"name": "VDD_A1", "rs": 0.1, "sweetberry": "A", "net": "V_MSMA1_1V225", "channel": 6}, -{"name": "VDD_A2", "rs": 1.0, "sweetberry": "A", "net": "V_MSMA2_1V8", "channel": 7}, -{"name": "VDD_P2", "rs": 0.02, "sweetberry": "A", "net": "V_IO_1V8", "channel": 8}, -{"name": "VDD_P3", "rs": 0.5, "sweetberry": "B", "net": "V_IO_1V8", "channel": 1}, -{"name": "VDD_P5", "rs": 0.5, "sweetberry": "A", "net": "V_RUIM1", "channel": 9}, -{"name": "VDD_P6", "rs": 0.02, "sweetberry": "A", "net": "V_IO_1V8", "channel": 46}, -{"name": "VDD_P10", "rs": 0.1, "sweetberry": "A", "net": "V_UFS_1V2", "channel": 10}, -{"name": "VDD_P12", "rs": 0.1, "sweetberry": "A", "net": "V_SRIO_1V8", "channel": 11}, -{"name": "VDD_USB_HS_3P1", "rs": 0.5, "sweetberry": "A", "net": "V_USB_3V075", "channel": 12}, -{"name": "VDD_CORE", "rs": 0.02, "sweetberry": "A", "net": "V_VDDCORE_0V8", "channel": 13}, -{"name": "VDD_GFX", "rs": 0.05, "sweetberry": "A", "net": "V_GFX_0V98", "channel": 14}, -{"name": "VDD_MODEM", "rs": 0.05, "sweetberry": "A", "net": "V_MODEM_1V0", "channel": 15}, -{"name": "VDD_APC", "rs": 0.01, "sweetberry": "A", "net": "V_APC_0V8", "channel": 16}, -{"name": "VDD_P1", "rs": 0.1, "sweetberry": "A", "net": "V_DDRCORE_1V1", "channel": 17}, -{"name": "VDD_DDR_CORE_1P8", "rs": 0.1, "sweetberry": "B", "net": "V_IO_1V8", "channel": 3}, -{"name": "VDD_SSC_CORE", "rs": 0.05, "sweetberry": "A", "net": "V_SSCCORE_0V8", "channel": 18}, -{"name": "VDD_SSC_MEM", "rs": 0.02, "sweetberry": "A", "net": "V_SSCMEM_0V875", "channel": 19}, -{"name": "V_EMMC_2V95", "rs": 0.1, "sweetberry": "A", "net": "V_EMMC_2V95", "channel": 20}, -{"name": "VCCQ2", "rs": 0.1, "sweetberry": "B", "net": "V_IO_1V8", "channel": 4}, -{"name": "VDD/VDDIO", "rs": 1.0, "sweetberry": "B", "net": "V_SRIO_1V8", "channel": 5}, -{"name": "V_LED_3V3", "rs": 0.1, "sweetberry": "A", "net": "V_LED_3V3", "channel": 21}, -{"name": "VDD/VIO", "rs": 1.0, "sweetberry": "B", "net": "V_SRIO_1V8", "channel": 6}, -{"name": "V_SRIO_1V8", "rs": 1.0, "sweetberry": "B", "net": "V_SRIO_1V8", "channel": 7}, -{"name": "V_SRIO_1V8_", "rs": 1.0, "sweetberry": "B", "net": "V_SRIO_1V8", "channel": 8}, -{"name": "VBAT/VDD/VDDA", "rs": 0.1, "sweetberry": "B", "net": "V_SRIO_1V8", "channel": 9}, -{"name": "V_SRIO_1V8__", "rs": 1.0, "sweetberry": "B", "net": "V_SRIO_1V8", "channel": 10}, -{"name": "V_SR_2V85", "rs": 0.1, "sweetberry": "A", "net": "V_SR_2V85", "channel": 22}, -{"name": "", "rs": 0.1, "sweetberry": "B", "net": "", "channel": 11}, -{"name": "V_USBSS_SW_1V8", "rs": 0.1, "sweetberry": "A", "net": "V_USBSS_SW_1V8", "channel": 23}, -{"name": "V_RF_2V7", "rs": 0.5, "sweetberry": "A", "net": "V_RF_2V7", "channel": 24}, -{"name": "V_TP_3V3", "rs": 0.5, "sweetberry": "A", "net": "V_TP_3V3", "channel": 25}, -{"name": "V_ELVDD", "rs": 0.1, "sweetberry": "B", "net": "V_ELVDD", "channel": 16}, -{"name": "V_AVDD", "rs": 1.0, "sweetberry": "B", "net": "V_AVDD", "channel": 17}, -{"name": "VCI_3V", "rs": 0.1, "sweetberry": "A", "net": "VCI_3V", "channel": 26}, -{"name": "VDD_1V8_PANEL", "rs": 0.5, "sweetberry": "A", "net": "VDD_1V8_PANEL", "channel": 27}, -{"name": "V_TP_1V8", "rs": 0.1, "sweetberry": "A", "net": "V_TP_1V8", "channel": 28}, -{"name": "V_CAM2_D1V2", "rs": 0.1, "sweetberry": "A", "net": "V_CAM2_D1V2", "channel": 31}, -{"name": "V_CAMIO_1V8", "rs": 0.1, "sweetberry": "A", "net": "V_CAMIO_1V8", "channel": 32}, -{"name": "V_CAM1_VCM2V85", "rs": 0.5, "sweetberry": "B", "net": "V_CAM1_VCM2V85", "channel": 18}, -{"name": "V_CAM1_A2V85", "rs": 1.0, "sweetberry": "B", "net": "V_CAM1_A2V85", "channel": 19}, -{"name": "V_CAM1_D1V0", "rs": 0.02, "sweetberry": "A", "net": "V_CAM1_D1V0", "channel": 33}, -{"name": "V_CAMIO_1V8_", "rs": 1.0, "sweetberry": "B", "net": "V_CAMIO_1V8", "channel": 20}, -{"name": "VBAT_ADC_IN", "rs": 0.01, "sweetberry": "A", "net": "V_DCIN", "channel": 34}, -{"name": "VDD_RX", "rs": 1.0, "sweetberry": "B", "net": "V_IO_1V8", "channel": 21}, -{"name": "VDD_MIC_BIAS", "rs": 0.01, "sweetberry": "A", "net": "V_BOOST_BYPASS", "channel": 35}, -{"name": "PVDD/VDD", "rs": 0.1, "sweetberry": "A", "net": "V_AUD_AMP_3V3", "channel": 36}, -{"name": "V_DCIN", "rs": 0.01, "sweetberry": "A", "net": "V_DCIN", "channel": 47}, -{"name": "V_AUDIO_2V15", "rs": 0.02, "sweetberry": "A", "net": "V_AUDIO_2V15", "channel": 38}, -{"name": "V_AUDIO_1V3", "rs": 0.02, "sweetberry": "A", "net": "V_AUDIO_1V3", "channel": 39}, -{"name": "PVIN/AVIN", "rs": 0.02, "sweetberry": "A", "net": "V_DCIN", "channel": 40}, -{"name": "VBATT", "rs": 0.5, "sweetberry": "B", "net": "VPA_BATT", "channel": 24}, -{"name": "VCC_GSM", "rs": 0.01, "sweetberry": "B", "net": "VPA_APT", "channel": 25}, -{"name": "VCC1_3G", "rs": 0.01, "sweetberry": "B", "net": "VPA", "channel": 26}, -{"name": "VAPT", "rs": 0.01, "sweetberry": "B", "net": "VPA_APT", "channel": 27}, -{"name": "VCC1", "rs": 0.01, "sweetberry": "B", "net": "VPA", "channel": 28}, -{"name": "VPA_BATT", "rs": 0.5, "sweetberry": "B", "net": "VPA_BATT", "channel": 29}, -{"name": "VDD_RF1_TVCO", "rs": 0.1, "sweetberry": "A", "net": "VREG_RF_1P0", "channel": 42}, -{"name": "V_GPS_1V8", "rs": 1.0, "sweetberry": "A", "net": "V_GPS_1V8", "channel": 43}, -{"name": "VDDIO_XTAL", "rs": 1.0, "sweetberry": "A", "net": "VDDIO_XTAL_1V8", "channel": 44}, -{"name": "VDD_FEM", "rs": 0.05, "sweetberry": "A", "net": "V_DCIN", "channel": 45}, -{"name": "VDD33", "rs": 0.1, "sweetberry": "B", "net": "V_VDDRF_3V2", "channel": 31}, -{"name": "DVDD11", "rs": 0.1, "sweetberry": "B", "net": "V_VDDRF_1V1", "channel": 32}, -{"name": "VDD(PAD)", "rs": 0.5, "sweetberry": "B", "net": "V_NFC_1V8", "channel": 33}, -{"name": "VBAT/VBAT2/VDD(UP)", "rs": 0.1, "sweetberry": "B", "net": "V_MBAT", "channel": 34}, -{"name": "NFC_5V_BOOST", "rs": 0.1, "sweetberry": "B", "net": "NFC_5V_BOOST", "channel": 35} -] diff --git a/extra/usb_power/board/marlin/marlin_all_A.scenario b/extra/usb_power/board/marlin/marlin_all_A.scenario deleted file mode 100644 index a024b698d7..0000000000 --- a/extra/usb_power/board/marlin/marlin_all_A.scenario +++ /dev/null @@ -1,42 +0,0 @@ -[ "VBAT", -"VDD_MEM", -"VDD_EBI_PHY", -"VDD_PCIE_1P8", -"VDD_PCIE_CORE", -"VDD_MIPI_CSI", -"VDD_A1", -"VDD_A2", -"VDD_P2", -"VDD_P5", -"VDD_P6", -"VDD_P10", -"VDD_P12", -"VDD_USB_HS_3P1", -"VDD_CORE", -"VDD_GFX", -"VDD_MODEM", -"VDD_APC", -"VDD_P1", -"VDD_SSC_CORE", -"VDD_SSC_MEM", -"V_EMMC_2V95", -"V_LED_3V3", -"V_SR_2V85", -"V_USBSS_SW_1V8", -"V_RF_2V7", -"V_TP_3V3", -"VCI_3V", -"VDD_1V8_PANEL", -"V_TP_1V8", -"V_CAM2_D1V2", -"V_CAMIO_1V8", -"V_CAM1_D1V0", -"VBAT_ADC_IN", -"VDD_MIC_BIAS", -"PVDD/VDD", -"V_DCIN", -"V_AUDIO_2V15", -"V_AUDIO_1V3", -"VDD_RF1_TVCO", -"V_GPS_1V8", -"VDD_FEM"] diff --git a/extra/usb_power/board/marlin/marlin_all_B.scenario b/extra/usb_power/board/marlin/marlin_all_B.scenario deleted file mode 100644 index 876d2dbfbd..0000000000 --- a/extra/usb_power/board/marlin/marlin_all_B.scenario +++ /dev/null @@ -1,28 +0,0 @@ -[ "VBAT_", -"VDD_P3", -"VDD_DDR_CORE_1P8", -"VCCQ2", -"VDD/VDDIO", -"VDD/VIO", -"V_SRIO_1V8", -"V_SRIO_1V8_", -"VBAT/VDD/VDDA", -"V_SRIO_1V8__", -"", -"V_ELVDD", -"V_AVDD", -"V_CAM1_VCM2V85", -"V_CAM1_A2V85", -"V_CAMIO_1V8_", -"VDD_RX", -"VBATT", -"VCC_GSM", -"VCC1_3G", -"VAPT", -"VCC1", -"VPA_BATT", -"VDD33", -"DVDD11", -"VDD(PAD)", -"VBAT/VBAT2/VDD(UP)", -"NFC_5V_BOOST" ] diff --git a/extra/usb_power/board/marlin/marlin_common.scenario b/extra/usb_power/board/marlin/marlin_common.scenario deleted file mode 100644 index 7e20236c34..0000000000 --- a/extra/usb_power/board/marlin/marlin_common.scenario +++ /dev/null @@ -1 +0,0 @@ -["VBAT", "VDD_MEM", "VDD_EBI_PHY", "VDD_PCIE_1P8", "VDD_PCIE_CORE", "VDD_MIPI_CSI", "VDD_A1", "VDD_CORE", "VDD_GFX", "VDD_MODEM", "VDD_APC", "VDD_P1", "VDD_SSC_CORE", "VDD_SSC_MEM", "VDD_1V8_PANEL", "V_CAM2_D1V2", "VBAT_ADC_IN", "VDD_FEM"] diff --git a/extra/usb_power/board/marlin/marlin_pvc.scenario b/extra/usb_power/board/marlin/marlin_pvc.scenario deleted file mode 100644 index 426cd1479c..0000000000 --- a/extra/usb_power/board/marlin/marlin_pvc.scenario +++ /dev/null @@ -1 +0,0 @@ -["VBAT", ["VBAT", "BUSV"], ["VBAT", "CURRENT"], ["VBAT", "SHUNTV"]] diff --git a/extra/usb_power/board/marlin/marlin_short.scenario b/extra/usb_power/board/marlin/marlin_short.scenario deleted file mode 100644 index 2cfc8b0f9a..0000000000 --- a/extra/usb_power/board/marlin/marlin_short.scenario +++ /dev/null @@ -1 +0,0 @@ -["VBAT", "VDD_MEM", "VDD_CORE", "VDD_GFX", "VDD_1V8_PANEL"] diff --git a/extra/usb_power/board/marlin/marlin_vbat.scenario b/extra/usb_power/board/marlin/marlin_vbat.scenario deleted file mode 100644 index f1c18ca202..0000000000 --- a/extra/usb_power/board/marlin/marlin_vbat.scenario +++ /dev/null @@ -1 +0,0 @@ -["VBAT"] diff --git a/extra/usb_power/convert_power_log_board.py b/extra/usb_power/convert_power_log_board.py deleted file mode 100644 index 8aab77ee4c..0000000000 --- a/extra/usb_power/convert_power_log_board.py +++ /dev/null @@ -1,92 +0,0 @@ -#!/usr/bin/env python -# Copyright 2018 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. -# -# Ignore indention messages, since legacy scripts use 2 spaces instead of 4. -# pylint: disable=bad-indentation,docstring-section-indent -# pylint: disable=docstring-trailing-quotes - -""" -Program to convert sweetberry config to servod config template. -""" - -# Note: This is a py2/3 compatible file. - -from __future__ import print_function -import json -import os -import sys - -from powerlog import Spower - - -def fetch_records(board_file): - """Import records from servo_ina file. - - board files are json files, and have a list of tuples with - the INA data. - (name, rs, swetberry_num, net_name, channel) - - Args: - board_file: board file - - Returns: - list of tuples as described above. - """ - data = None - with open(board_file) as f: - data = json.load(f) - return data - - -def write_to_file(file, sweetberry, inas): - """Writes records of |sweetberry| to |file| - Args: - file: file to write to. - sweetberry: sweetberry type. A or B. - inas: list of inas read from board file. - """ - - with open(file, 'w') as pyfile: - - pyfile.write('inas = [\n') - - for rec in inas: - if rec['sweetberry'] != sweetberry: - continue - - # EX : ('sweetberry', 0x40, 'SB_FW_CAM_2P8', 5.0, 1.000, 3, False), - channel, i2c_addr = Spower.CHMAP[rec['channel']] - record = (" ('sweetberry', 0x%02x, '%s', 5.0, %f, %d, 'True')" - ",\n" % (i2c_addr, rec['name'], rec['rs'], channel)) - pyfile.write(record) - - pyfile.write(']\n') - - -def main(argv): - if len(argv) != 2: - print("usage:") - print(" %s input.board" % argv[0]) - return - - inputf = argv[1] - basename = os.path.splitext(inputf)[0] - - inas = fetch_records(inputf) - - sweetberry = set(rec['sweetberry'] for rec in inas) - - if len(sweetberry) == 2: - print("Converting %s to %s and %s" % (inputf, basename + '_a.py', - basename + '_b.py')) - write_to_file(basename + '_a.py', 'A', inas) - write_to_file(basename + '_b.py', 'B', inas) - else: - print("Converting %s to %s" % (inputf, basename + '.py')) - write_to_file(basename + '.py', sweetberry.pop(), inas) - - -if __name__ == "__main__": - main(sys.argv) diff --git a/extra/usb_power/convert_servo_ina.py b/extra/usb_power/convert_servo_ina.py deleted file mode 100755 index 1c70f31aeb..0000000000 --- a/extra/usb_power/convert_servo_ina.py +++ /dev/null @@ -1,80 +0,0 @@ -#!/usr/bin/env python -# Copyright 2017 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. -# -# Ignore indention messages, since legacy scripts use 2 spaces instead of 4. -# pylint: disable=bad-indentation,docstring-section-indent -# pylint: disable=docstring-trailing-quotes - -"""Program to convert power logging config from a servo_ina device - to a sweetberry config. -""" - -# Note: This is a py2/3 compatible file. - -from __future__ import print_function -import os -import sys - - -def fetch_records(basename): - """Import records from servo_ina file. - - servo_ina files are python imports, and have a list of tuples with - the INA data. - (inatype, i2caddr, rail name, bus voltage, shunt ohms, mux, True) - - Args: - basename: python import name (filename -.py) - - Returns: - list of tuples as described above. - """ - ina_desc = __import__(basename) - return ina_desc.inas - - -def main(argv): - if len(argv) != 2: - print("usage:") - print(" %s input.py" % argv[0]) - return - - inputf = argv[1] - basename = os.path.splitext(inputf)[0] - outputf = basename + '.board' - outputs = basename + '.scenario' - - print("Converting %s to %s, %s" % (inputf, outputf, outputs)) - - inas = fetch_records(basename) - - - boardfile = open(outputf, 'w') - scenario = open(outputs, 'w') - - boardfile.write('[\n') - scenario.write('[\n') - start = True - - for rec in inas: - if start: - start = False - else: - boardfile.write(',\n') - scenario.write(',\n') - - record = ' {"name": "%s", "rs": %f, "sweetberry": "A", "channel": %d}' % ( - rec[2], rec[4], rec[1] - 64) - boardfile.write(record) - scenario.write('"%s"' % rec[2]) - - boardfile.write('\n') - boardfile.write(']') - - scenario.write('\n') - scenario.write(']') - -if __name__ == "__main__": - main(sys.argv) diff --git a/extra/usb_power/marlin_v.scenario b/extra/usb_power/marlin_v.scenario deleted file mode 100644 index 99c132cb27..0000000000 --- a/extra/usb_power/marlin_v.scenario +++ /dev/null @@ -1 +0,0 @@ -[["VBAT", "BUSV"], ["VDD_1V8_PANEL", "BUSV"], ["V_EMMC_2V95", "BUSV"], ["V_SR_2V85", "BUSV"], ["V_USBSS_SW_1V8", "BUSV"], ["V_AUDIO_2V15", "BUSV"]] diff --git a/extra/usb_power/powerlog.README.md b/extra/usb_power/powerlog.README.md deleted file mode 100644 index 105516330a..0000000000 --- a/extra/usb_power/powerlog.README.md +++ /dev/null @@ -1,210 +0,0 @@ -# Sweetberry USB power monitoring - -This tool allows high speed monitoring of power rails via a special USB -endpoint. Currently this is implemented for the Sweetberry board. - -To use on a board, you'll need two config files, one describing the board, a -`.board` file, and one describing the particular rails you want to monitor in -this session, a `.scenario` file. - -## Converting from servo_ina configs - -- Method 1 (not limited to chroot) - - Many configs can be found for the servo_ina_board in `hdctools/servo/data/`. - Sweetberry is plug compatible with servo_ina headers, and config files can - be converted with the following tool: - - ``` - ./convert_servo_ina.py <board>_r0_loc.py - ``` - - This will generate `<board>_r0_loc.board` and `<board>_r0_loc.scenario` - locally, which can be used with `powerlog.py`. - -- Method 2 (recommended for Chrome OS developers, requires chroot) - - If you are using `powerlog.py` within the chroot, copy `<board>_r0_loc.py` - to `src/third_party/hdctools/servo/data`, then add this line to file: - - ```python - config_type = 'sweetberry' - ``` - - And run command in chroot: - - ``` - (Anywhere in chroot, just ONCE) cros_workon --host start dev-util/hdctools - ``` - - Then every time you make a change to `<board>_r0_loc.py`, run: - - ``` - (Anywhere in chroot) sudo emerge dev-util/hdctools - ``` - - The command will install the corresponding `.board` and `.scenario` file in - the chroot. To use `powerlog.py` use the command: - - ``` - (Anywhere in chroot) powerlog -b <board>_r0_loc.board -c <board>_r0_loc.scenario - ``` - - There is no need to specify the absolute path to the `.board` and - `.scenario` file, once they are installed into the chroot. If there is any - changes to `<board>_r0_loc.py`, you need to `sudo emerge dev-util/hdctools` - again. - -## Board files - -Board files contain a list of rails, supporting 48 channels each on up to two -Sweetberries. For each rail you must specify a name, sense resistor value, and -channel number. You can optionally list expected voltage and net name. The -format is as follows, in json: - -example.board: - -```json -[ -{ "name": "railname", - "rs": <sense resistor value in ohms>, - "sweetberry": <"A" for main Sweetberry, "B" for a secondary Sweetberry>, - "channel": <0-47 according to board schematic>, - "v": <optional expected bus voltage in volts>, - "net": <optional schematic net name> -}, -{...} -] -``` - -## Scenario files - -Scenario files contain the set of rails to monitor in this session. The file -format is simply a list of rail names from the board file. - -Optionally, you can specify the type of measurement, from the set of `"POWER"`, -`"BUSV"`, `"CURRENT"`, `"SHUNTV"`. If not specified, the default is power. - -example.scenario: - -```json -[ -"railname", -"another_railname", -["railname", "BUSV"], -["railname", "CURRENT"], -... -] -``` - -## Output - -`powerlog.py` will output a csv formatted log to stdout, at timing intervals -specified on the command line. Currently values below `-t 10000` do not work -reliably but further updates should allow faster updating. - -An example run of: - -``` -./powerlog.py -b board/marlin/marlin.board -c board/marlin/marlin_short.scenario -t 100000 -``` - -Will result in: `ts:32976us, VBAT uW, VDD_MEM uW, VDD_CORE uW, VDD_GFX uW, -VDD_1V8_PANEL uW 0.033004, 12207.03, 4882.81, 9155.27, 2441.41, 0.00 0.066008, -12207.03, 3662.11, 9155.27, 2441.41, 0.00 0.099012, 12207.03, 3662.11, 9155.27, -2441.41, 0.00 ...` - -The output format is as follows: - -- `ts:32976us` - - Timestamps either zero based or synced to system clock, in seconds. The - column header indicates the selected sampling interval. Since the INA231 has - specific hardware defines sampling options, this will be the closest - supported option lower than the requested `-t` value on the command line. - -- `VBAT uW` - - Microwatt reading from this rail, generated on the INA by integrating the - voltage/amperage on the sense resistor over the sampling time, and - multiplying by the sampled bus voltage. - -- `... uW` - - Further microwatt entry columns for each rail specified in your scenario - file. - -- `... xX` - - Measurement in uW, mW, mV, uA, uV as per config. - -## Calculate stats and store data and stats - -When appropriate flag is set, powerlog.py is capable of calculating statistics -and storing statistics and raw data. - -- Example 1 - - ``` - ./powerlog.py -b board/eve_dvt2_loc/eve_dvt2_loc.board -c board/eve_dvt2_loc/eve_dvt2_loc.scenario --save_stats [<directory>] - ``` - - If `<directory>` is specified, this will save stats as: - `<directory>/sweetberry<timestamp>/summary.txt`. If `<directory>` does not - exist, it will be created. - - If `<directory>` is not specified but the flag is set, this will save stats - under the directory which `powerlog.py` is in: `<directory of - powerlog.py>/sweetberry<timestamp>/summary.txt`. - - If `--save_stats` flag is not set, stats will not be saved. - -- Example 2 - - ``` - ./powerlog.py -b board/eve_dvt2_loc/eve_dvt2_loc.board -c board/eve_dvt2_loc/eve_dvt2_loc.scenario --save_raw_data [<directory>] - ``` - - If `<directory>` is specified, this will save raw data in: - `<directory>/sweetberry<timestamp>/raw_data/`. If `<directory>` does not - exist, it will be created. - - If `<directory>` is not specified but the flag is set, this will save raw - data under the directory which `powerlog.py` is in: `<directory of - powerlog.py>/sweetberry<timestamp>/raw_data/`. - - If `--save_raw_data` flag is not set, raw data will not be saved. - -- Example 3: - - ``` - ./powerlog.py -b board/eve_dvt2_loc/eve_dvt2_loc.board -c board/eve_dvt2_loc/eve_dvt2_loc.scenario --save_stats_json [<directory>] - ``` - - If `<directory>` is specified, this will save MEANS in json as: - `<directory>/sweetberry<timestamp>/summary.json`. If `<directory>` does not - exist, it will be created. - - If `<directory>` is not specified but the flag is set, this will save MEANS - in json under the directory which `powerlog.py` is in: `<directory of - powerlog.py>/sweetberry<timestamp>/summary.json`. - - If `--save_stats` flag is not set, stats will not be saved. - - `--save_stats_json` is designed for `power_telemetry_logger` for easy - reading and writing. - -## Making developer changes to `powerlog.py` - -`powerlog.py` is installed in chroot, and the developer can import `powerlog` or -use `powerlog` directly anywhere within chroot. Anytime the developer makes a -change to `powerlog.py`, the developer needs to re-install `powerlog.py` so that -anything that imports `powerlog` does not break. The following is how the -developer installs `powerlog.py` during development. - -Run command in chroot: - -``` -(Anywhere in chroot, just ONCE) cros_workon --host start chromeos-base/ec-devutils -(Anywhere in chroot, every time powerlog.py is changed) sudo emerge chromeos-base/ec-devutils -``` diff --git a/extra/usb_power/powerlog.py b/extra/usb_power/powerlog.py deleted file mode 100755 index 82cce3daed..0000000000 --- a/extra/usb_power/powerlog.py +++ /dev/null @@ -1,908 +0,0 @@ -#!/usr/bin/env python -# Copyright 2016 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. -# -# Ignore indention messages, since legacy scripts use 2 spaces instead of 4. -# pylint: disable=bad-indentation,docstring-section-indent -# pylint: disable=docstring-trailing-quotes - -"""Program to fetch power logging data from a sweetberry device - or other usb device that exports a USB power logging interface. -""" - -# Note: This is a py2/3 compatible file. - -from __future__ import print_function -import argparse -import array -from distutils import sysconfig -import json -import logging -import os -import pprint -import struct -import sys -import time -import traceback - -import usb - -from stats_manager import StatsManager - -# Directory where hdctools installs configuration files into. -LIB_DIR = os.path.join(sysconfig.get_python_lib(standard_lib=False), 'servo', - 'data') - -# Potential config file locations: current working directory, the same directory -# as powerlog.py file or LIB_DIR. -CONFIG_LOCATIONS = [os.getcwd(), os.path.dirname(os.path.realpath(__file__)), - LIB_DIR] - -def logoutput(msg): - print(msg) - sys.stdout.flush() - -def process_filename(filename): - """Find the file path from the filename. - - If filename is already the complete path, return that directly. If filename is - just the short name, look for the file in the current working directory, in - the directory of the current .py file, and then in the directory installed by - hdctools. If the file is found, return the complete path of the file. - - Args: - filename: complete file path or short file name. - - Returns: - a complete file path. - - Raises: - IOError if filename does not exist. - """ - # Check if filename is absolute path. - if os.path.isabs(filename) and os.path.isfile(filename): - return filename - # Check if filename is relative to a known config location. - for dirname in CONFIG_LOCATIONS: - file_at_dir = os.path.join(dirname, filename) - if os.path.isfile(file_at_dir): - return file_at_dir - raise IOError('No such file or directory: \'%s\'' % filename) - - -class Spower(object): - """Power class to access devices on the bus. - - Usage: - bus = Spower() - - Instance Variables: - _dev: pyUSB device object - _read_ep: pyUSB read endpoint for this interface - _write_ep: pyUSB write endpoint for this interface - """ - - # INA interface type. - INA_POWER = 1 - INA_BUSV = 2 - INA_CURRENT = 3 - INA_SHUNTV = 4 - # INA_SUFFIX is used to differentiate multiple ina types for the same power - # rail. No suffix for when ina type is 0 (non-existent) and when ina type is 1 - # (power, no suffix for backward compatibility). - INA_SUFFIX = ['', '', '_busv', '_cur', '_shuntv'] - - # usb power commands - CMD_RESET = 0x0000 - CMD_STOP = 0x0001 - CMD_ADDINA = 0x0002 - CMD_START = 0x0003 - CMD_NEXT = 0x0004 - CMD_SETTIME = 0x0005 - - # Map between header channel number (0-47) - # and INA I2C bus/addr on sweetberry. - CHMAP = { - 0: (3, 0x40), - 1: (1, 0x40), - 2: (2, 0x40), - 3: (0, 0x40), - 4: (3, 0x41), - 5: (1, 0x41), - 6: (2, 0x41), - 7: (0, 0x41), - 8: (3, 0x42), - 9: (1, 0x42), - 10: (2, 0x42), - 11: (0, 0x42), - 12: (3, 0x43), - 13: (1, 0x43), - 14: (2, 0x43), - 15: (0, 0x43), - 16: (3, 0x44), - 17: (1, 0x44), - 18: (2, 0x44), - 19: (0, 0x44), - 20: (3, 0x45), - 21: (1, 0x45), - 22: (2, 0x45), - 23: (0, 0x45), - 24: (3, 0x46), - 25: (1, 0x46), - 26: (2, 0x46), - 27: (0, 0x46), - 28: (3, 0x47), - 29: (1, 0x47), - 30: (2, 0x47), - 31: (0, 0x47), - 32: (3, 0x48), - 33: (1, 0x48), - 34: (2, 0x48), - 35: (0, 0x48), - 36: (3, 0x49), - 37: (1, 0x49), - 38: (2, 0x49), - 39: (0, 0x49), - 40: (3, 0x4a), - 41: (1, 0x4a), - 42: (2, 0x4a), - 43: (0, 0x4a), - 44: (3, 0x4b), - 45: (1, 0x4b), - 46: (2, 0x4b), - 47: (0, 0x4b), - } - - def __init__(self, board, vendor=0x18d1, - product=0x5020, interface=1, serialname=None): - self._logger = logging.getLogger(__name__) - self._board = board - - # Find the stm32. - dev_g = usb.core.find(idVendor=vendor, idProduct=product, find_all=True) - dev_list = list(dev_g) - if dev_list is None: - raise Exception("Power", "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: - dev_serial = "PyUSB dioesn't have a stable interface" - try: - dev_serial = usb.util.get_string(d, 256, d.iSerialNumber) - except ValueError: - # Incompatible pyUsb version. - dev_serial = usb.util.get_string(d, d.iSerialNumber) - if dev_serial == serialname: - dev = d - break - if dev is None: - raise Exception("Power", "USB device(%s) not found" % serialname) - else: - try: - dev = dev_list[0] - except TypeError: - # Incompatible pyUsb version. - dev = dev_list.next() - - self._logger.debug("Found USB device: %04x:%04x", vendor, product) - self._dev = dev - - # Get an endpoint instance. - try: - dev.set_configuration() - except usb.USBError: - pass - cfg = dev.get_active_configuration() - - intf = usb.util.find_descriptor(cfg, custom_match=lambda i: \ - i.bInterfaceClass==255 and i.bInterfaceSubClass==0x54) - - self._intf = intf - self._logger.debug("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 - self._logger.debug("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 - self._logger.debug("Writer endpoint: 0x%x", write_ep.bEndpointAddress) - - self.clear_ina_struct() - - self._logger.debug("Found power logging USB endpoint.") - - def clear_ina_struct(self): - """ Clear INA description struct.""" - self._inas = [] - - def append_ina_struct(self, name, rs, port, addr, - data=None, ina_type=INA_POWER): - """Add an INA descriptor into the list of active INAs. - - Args: - name: Readable name of this channel. - rs: Sense resistor value in ohms, floating point. - port: I2C channel this INA is connected to. - addr: I2C addr of this INA. - data: Misc data for special handling, board specific. - ina_type: INA function to use, power, voltage, etc. - """ - ina = {} - ina['name'] = name - ina['rs'] = rs - ina['port'] = port - ina['addr'] = addr - ina['type'] = ina_type - # Calculate INA231 Calibration register - # (see INA231 spec p.15) - # CurrentLSB = uA per div = 80mV / (Rsh * 2^15) - # CurrentLSB uA = 80000000nV / (Rsh mOhm * 0x8000) - ina['uAscale'] = 80000000. / (rs * 0x8000); - ina['uWscale'] = 25. * ina['uAscale']; - ina['mVscale'] = 1.25 - ina['uVscale'] = 2.5 - ina['data'] = data - self._inas.append(ina) - - def wr_command(self, write_list, read_count=1, wtimeout=100, rtimeout=1000): - """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. - - Interface: - write: [command, data ... ] - read: [status ] - - Returns: - bytes read, or None on failure. - """ - self._logger.debug("Spower.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) - - self._logger.debug("RET: %s ", ret) - - # Read back response if necessary. - if read_count: - bytesread = self._read_ep.read(512, rtimeout) - self._logger.debug("BYTES: [%s]", bytesread) - - if len(bytesread) != read_count: - pass - - self._logger.debug("STATUS: 0x%02x", int(bytesread[0])) - if read_count == 1: - return bytesread[0] - else: - return bytesread - - return None - - def clear(self): - """Clear pending reads on the stm32""" - try: - while True: - ret = self.wr_command(b"", read_count=512, rtimeout=100, wtimeout=50) - self._logger.debug("Try Clear: read %s", - "success" if ret == 0 else "failure") - except: - pass - - def send_reset(self): - """Reset the power interface on the stm32""" - cmd = struct.pack("<H", self.CMD_RESET) - ret = self.wr_command(cmd, rtimeout=50, wtimeout=50) - self._logger.debug("Command RESET: %s", - "success" if ret == 0 else "failure") - - def reset(self): - """Try resetting the USB interface until success. - - Use linear back off strategy when encounter the error with 10ms increment. - - Raises: - Exception on failure. - """ - max_reset_retry = 100 - for count in range(1, max_reset_retry + 1): - self.clear() - try: - self.send_reset() - return - except Exception as e: - self.clear() - self.clear() - self._logger.debug("TRY %d of %d: %s", count, max_reset_retry, e) - time.sleep(count * 0.01) - raise Exception("Power", "Failed to reset") - - def stop(self): - """Stop any active data acquisition.""" - cmd = struct.pack("<H", self.CMD_STOP) - ret = self.wr_command(cmd) - self._logger.debug("Command STOP: %s", - "success" if ret == 0 else "failure") - - def start(self, integration_us): - """Start data acquisition. - - Args: - integration_us: int, how many us between samples, and - how often the data block must be read. - - Returns: - actual sampling interval in ms. - """ - cmd = struct.pack("<HI", self.CMD_START, integration_us) - read = self.wr_command(cmd, read_count=5) - actual_us = 0 - if len(read) == 5: - ret, actual_us = struct.unpack("<BI", read) - self._logger.debug("Command START: %s %dus", - "success" if ret == 0 else "failure", actual_us) - else: - self._logger.debug("Command START: FAIL") - - return actual_us - - def add_ina_name(self, name_tuple): - """Add INA from board config. - - Args: - name_tuple: name and type of power rail in board config. - - Returns: - True if INA added, False if the INA is not on this board. - - Raises: - Exception on unexpected failure. - """ - name, ina_type = name_tuple - - for datum in self._brdcfg: - if datum["name"] == name: - rs = int(float(datum["rs"]) * 1000.) - board = datum["sweetberry"] - - if board == self._board: - if 'port' in datum and 'addr' in datum: - port = datum['port'] - addr = datum['addr'] - else: - channel = int(datum["channel"]) - port, addr = self.CHMAP[channel] - self.add_ina(port, ina_type, addr, 0, rs, data=datum) - return True - else: - return False - raise Exception("Power", "Failed to find INA %s" % name) - - def set_time(self, timestamp_us): - """Set sweetberry time to match host time. - - Args: - timestamp_us: host timestmap in us. - """ - # 0x0005 , 8 byte timestamp - cmd = struct.pack("<HQ", self.CMD_SETTIME, timestamp_us) - ret = self.wr_command(cmd) - - self._logger.debug("Command SETTIME: %s", - "success" if ret == 0 else "failure") - - def add_ina(self, bus, ina_type, addr, extra, resistance, data=None): - """Add an INA to the data acquisition list. - - Args: - bus: which i2c bus the INA is on. Same ordering as Si2c. - ina_type: Ina interface: INA_POWER/BUSV/etc. - addr: 7 bit i2c addr of this INA - extra: extra data for nonstandard configs. - resistance: int, shunt resistance in mOhm - """ - # 0x0002, 1B: bus, 1B:INA type, 1B: INA addr, 1B: extra, 4B: Rs - cmd = struct.pack("<HBBBBI", self.CMD_ADDINA, - bus, ina_type, addr, extra, resistance) - ret = self.wr_command(cmd) - if ret == 0: - if data: - name = data['name'] - else: - name = "ina%d_%02x" % (bus, addr) - self.append_ina_struct(name, resistance, bus, addr, - data=data, ina_type=ina_type) - self._logger.debug("Command ADD_INA: %s", - "success" if ret == 0 else "failure") - - def report_header_size(self): - """Helper function to calculate power record header size.""" - result = 2 - timestamp = 8 - return result + timestamp - - def report_size(self, ina_count): - """Helper function to calculate full power record size.""" - record = 2 - - datasize = self.report_header_size() + ina_count * record - # Round to multiple of 4 bytes. - datasize = int(((datasize + 3) // 4) * 4) - - return datasize - - def read_line(self): - """Read a line of data from the setup INAs - - Returns: - list of dicts of the values read by ina/type tuple, otherwise None. - [{ts:100, (vbat, power):450}, {ts:200, (vbat, power):440}] - """ - try: - expected_bytes = self.report_size(len(self._inas)) - cmd = struct.pack("<H", self.CMD_NEXT) - bytesread = self.wr_command(cmd, read_count=expected_bytes) - except usb.core.USBError as e: - self._logger.error("READ LINE FAILED %s", e) - return None - - if len(bytesread) == 1: - if bytesread[0] != 0x6: - self._logger.debug("READ LINE FAILED bytes: %d ret: %02x", - len(bytesread), bytesread[0]) - return None - - if len(bytesread) % expected_bytes != 0: - self._logger.debug("READ LINE WARNING: expected %d, got %d", - expected_bytes, len(bytesread)) - - packet_count = len(bytesread) // expected_bytes - - values = [] - for i in range(0, packet_count): - start = i * expected_bytes - end = (i + 1) * expected_bytes - record = self.interpret_line(bytesread[start:end]) - values.append(record) - - return values - - def interpret_line(self, data): - """Interpret a power record from INAs - - Args: - data: one single record of bytes. - - Output: - stdout of the record in csv format. - - Returns: - dict containing name, value of recorded data. - """ - status, size = struct.unpack("<BB", data[0:2]) - if len(data) != self.report_size(size): - self._logger.error("READ LINE FAILED st:%d size:%d expected:%d len:%d", - status, size, self.report_size(size), len(data)) - else: - pass - - timestamp = struct.unpack("<Q", data[2:10])[0] - self._logger.debug("READ LINE: st:%d size:%d time:%dus", status, size, - timestamp) - ftimestamp = float(timestamp) / 1000000. - - record = {"ts": ftimestamp, "status": status, "berry":self._board} - - for i in range(0, size): - idx = self.report_header_size() + 2*i - name = self._inas[i]['name'] - name_tuple = (self._inas[i]['name'], self._inas[i]['type']) - - raw_val = struct.unpack("<h", data[idx:idx+2])[0] - - if self._inas[i]['type'] == Spower.INA_POWER: - val = raw_val * self._inas[i]['uWscale'] - elif self._inas[i]['type'] == Spower.INA_BUSV: - val = raw_val * self._inas[i]['mVscale'] - elif self._inas[i]['type'] == Spower.INA_CURRENT: - val = raw_val * self._inas[i]['uAscale'] - elif self._inas[i]['type'] == Spower.INA_SHUNTV: - val = raw_val * self._inas[i]['uVscale'] - - self._logger.debug("READ %d %s: %fs: 0x%04x %f", i, name, ftimestamp, - raw_val, val) - record[name_tuple] = val - - return record - - def load_board(self, brdfile): - """Load a board config. - - Args: - brdfile: Filename of a json file decribing the INA wiring of this board. - """ - with open(process_filename(brdfile)) as data_file: - data = json.load(data_file) - - #TODO: validate this. - self._brdcfg = data; - self._logger.debug(pprint.pformat(data)) - - -class powerlog(object): - """Power class to log aggregated power. - - Usage: - obj = powerlog() - - Instance Variables: - _data: a StatsManager object that records sweetberry readings and calculates - statistics. - _pwr[]: Spower objects for individual sweetberries. - """ - - def __init__(self, brdfile, cfgfile, serial_a=None, serial_b=None, - sync_date=False, use_ms=False, use_mW=False, print_stats=False, - stats_dir=None, stats_json_dir=None, print_raw_data=True, - raw_data_dir=None): - """Init the powerlog class and set the variables. - - Args: - brdfile: string name of json file containing board layout. - cfgfile: string name of json containing list of rails to read. - serial_a: serial number of sweetberry A. - serial_b: serial number of sweetberry B. - sync_date: report timestamps synced with host datetime. - use_ms: report timestamps in ms rather than us. - use_mW: report power as milliwatts, otherwise default to microwatts. - print_stats: print statistics for sweetberry readings at the end. - stats_dir: directory to save sweetberry readings statistics; if None then - do not save the statistics. - stats_json_dir: directory to save means of sweetberry readings in json - format; if None then do not save the statistics. - print_raw_data: print sweetberry readings raw data in real time, default - is to print. - raw_data_dir: directory to save sweetberry readings raw data; if None then - do not save the raw data. - """ - self._logger = logging.getLogger(__name__) - self._data = StatsManager() - self._pwr = {} - self._use_ms = use_ms - self._use_mW = use_mW - self._print_stats = print_stats - self._stats_dir = stats_dir - self._stats_json_dir = stats_json_dir - self._print_raw_data = print_raw_data - self._raw_data_dir = raw_data_dir - - if not serial_a and not serial_b: - self._pwr['A'] = Spower('A') - if serial_a: - self._pwr['A'] = Spower('A', serialname=serial_a) - if serial_b: - self._pwr['B'] = Spower('B', serialname=serial_b) - - with open(process_filename(cfgfile)) as data_file: - names = json.load(data_file) - self._names = self.process_scenario(names) - - for key in self._pwr: - self._pwr[key].load_board(brdfile) - self._pwr[key].reset() - - # Allocate the rails to the appropriate boards. - used_boards = [] - for name in self._names: - success = False - for key in self._pwr.keys(): - if self._pwr[key].add_ina_name(name): - success = True - if key not in used_boards: - used_boards.append(key) - if not success: - raise Exception("Failed to add %s (maybe missing " - "sweetberry, or bad board file?)" % name) - - # Evict unused boards. - for key in list(self._pwr.keys()): - if key not in used_boards: - self._pwr.pop(key) - - for key in self._pwr.keys(): - if sync_date: - self._pwr[key].set_time(time.time() * 1000000) - else: - self._pwr[key].set_time(0) - - def process_scenario(self, name_list): - """Return list of tuples indicating name and type. - - Args: - json originated list of names, or [name, type] - Returns: - list of tuples of (name, type) defaulting to type "POWER" - Raises: exception, invalid INA type. - """ - names = [] - for entry in name_list: - if isinstance(entry, list): - name = entry[0] - if entry[1] == "POWER": - type = Spower.INA_POWER - elif entry[1] == "BUSV": - type = Spower.INA_BUSV - elif entry[1] == "CURRENT": - type = Spower.INA_CURRENT - elif entry[1] == "SHUNTV": - type = Spower.INA_SHUNTV - else: - raise Exception("Invalid INA type", "Type of %s [%s] not recognized," - " try one of POWER, BUSV, CURRENT" % (entry[0], entry[1])) - else: - name = entry - type = Spower.INA_POWER - - names.append((name, type)) - return names - - def start(self, integration_us_request, seconds, sync_speed=.8): - """Starts sampling. - - Args: - integration_us_request: requested interval between sample values. - seconds: time until exit, or None to run until cancel. - sync_speed: A usb request is sent every [.8] * integration_us. - """ - # We will get back the actual integration us. - # It should be the same for all devices. - integration_us = None - for key in self._pwr: - integration_us_new = self._pwr[key].start(integration_us_request) - if integration_us: - if integration_us != integration_us_new: - raise Exception("FAIL", - "Integration on A: %dus != integration on B %dus" % ( - integration_us, integration_us_new)) - integration_us = integration_us_new - - # CSV header - title = "ts:%dus" % integration_us - for name_tuple in self._names: - name, ina_type = name_tuple - - if ina_type == Spower.INA_POWER: - unit = "mW" if self._use_mW else "uW" - elif ina_type == Spower.INA_BUSV: - unit = "mV" - elif ina_type == Spower.INA_CURRENT: - unit = "uA" - elif ina_type == Spower.INA_SHUNTV: - unit = "uV" - - title += ", %s %s" % (name, unit) - name_type = name + Spower.INA_SUFFIX[ina_type] - self._data.SetUnit(name_type, unit) - title += ", status" - if self._print_raw_data: - logoutput(title) - - forever = False - if not seconds: - forever = True - end_time = time.time() + seconds - try: - pending_records = [] - while forever or end_time > time.time(): - if (integration_us > 5000): - time.sleep((integration_us / 1000000.) * sync_speed) - for key in self._pwr: - records = self._pwr[key].read_line() - if not records: - continue - - for record in records: - pending_records.append(record) - - pending_records.sort(key=lambda r: r['ts']) - - aggregate_record = {"boards": set()} - for record in pending_records: - if record["berry"] not in aggregate_record["boards"]: - for rkey in record.keys(): - aggregate_record[rkey] = record[rkey] - aggregate_record["boards"].add(record["berry"]) - else: - self._logger.info("break %s, %s", record["berry"], - aggregate_record["boards"]) - break - - if aggregate_record["boards"] == set(self._pwr.keys()): - csv = "%f" % aggregate_record["ts"] - for name in self._names: - if name in aggregate_record: - multiplier = 0.001 if (self._use_mW and - name[1]==Spower.INA_POWER) else 1 - value = aggregate_record[name] * multiplier - csv += ", %.2f" % value - name_type = name[0] + Spower.INA_SUFFIX[name[1]] - self._data.AddSample(name_type, value) - else: - csv += ", " - csv += ", %d" % aggregate_record["status"] - if self._print_raw_data: - logoutput(csv) - - aggregate_record = {"boards": set()} - for r in range(0, len(self._pwr)): - pending_records.pop(0) - - except KeyboardInterrupt: - self._logger.info('\nCTRL+C caught.') - - finally: - for key in self._pwr: - self._pwr[key].stop() - self._data.CalculateStats() - if self._print_stats: - print(self._data.SummaryToString()) - save_dir = 'sweetberry%s' % time.time() - if self._stats_dir: - stats_dir = os.path.join(self._stats_dir, save_dir) - self._data.SaveSummary(stats_dir) - if self._stats_json_dir: - stats_json_dir = os.path.join(self._stats_json_dir, save_dir) - self._data.SaveSummaryJSON(stats_json_dir) - if self._raw_data_dir: - raw_data_dir = os.path.join(self._raw_data_dir, save_dir) - self._data.SaveRawData(raw_data_dir) - - -def main(argv=None): - if argv is None: - argv = sys.argv[1:] - # Command line argument description. - parser = argparse.ArgumentParser( - description="Gather CSV data from sweetberry") - parser.add_argument('-b', '--board', type=str, - help="Board configuration file, eg. my.board", default="") - parser.add_argument('-c', '--config', type=str, - help="Rail config to monitor, eg my.scenario", default="") - parser.add_argument('-A', '--serial', type=str, - help="Serial number of sweetberry A", default="") - parser.add_argument('-B', '--serial_b', type=str, - help="Serial number of sweetberry B", default="") - parser.add_argument('-t', '--integration_us', type=int, - help="Target integration time for samples", default=100000) - parser.add_argument('-s', '--seconds', type=float, - help="Seconds to run capture", default=0.) - parser.add_argument('--date', default=False, - help="Sync logged timestamp to host date", action="store_true") - parser.add_argument('--ms', default=False, - help="Print timestamp as milliseconds", action="store_true") - parser.add_argument('--mW', default=False, - help="Print power as milliwatts, otherwise default to microwatts", - action="store_true") - parser.add_argument('--slow', default=False, - help="Intentionally overflow", action="store_true") - parser.add_argument('--print_stats', default=False, action="store_true", - help="Print statistics for sweetberry readings at the end") - parser.add_argument('--save_stats', type=str, nargs='?', - dest='stats_dir', metavar='STATS_DIR', - const=os.path.dirname(os.path.abspath(__file__)), default=None, - help="Save statistics for sweetberry readings to %(metavar)s if " - "%(metavar)s is specified, %(metavar)s will be created if it does " - "not exist; if %(metavar)s is not specified but the flag is set, " - "stats will be saved to where %(prog)s is located; if this flag is " - "not set, then do not save stats") - parser.add_argument('--save_stats_json', type=str, nargs='?', - dest='stats_json_dir', metavar='STATS_JSON_DIR', - const=os.path.dirname(os.path.abspath(__file__)), default=None, - help="Save means for sweetberry readings in json to %(metavar)s if " - "%(metavar)s is specified, %(metavar)s will be created if it does " - "not exist; if %(metavar)s is not specified but the flag is set, " - "stats will be saved to where %(prog)s is located; if this flag is " - "not set, then do not save stats") - parser.add_argument('--no_print_raw_data', - dest='print_raw_data', default=True, action="store_false", - help="Not print raw sweetberry readings at real time, default is to " - "print") - parser.add_argument('--save_raw_data', type=str, nargs='?', - dest='raw_data_dir', metavar='RAW_DATA_DIR', - const=os.path.dirname(os.path.abspath(__file__)), default=None, - help="Save raw data for sweetberry readings to %(metavar)s if " - "%(metavar)s is specified, %(metavar)s will be created if it does " - "not exist; if %(metavar)s is not specified but the flag is set, " - "raw data will be saved to where %(prog)s is located; if this flag " - "is not set, then do not save raw data") - parser.add_argument('-v', '--verbose', default=False, - help="Very chatty printout", action="store_true") - - args = parser.parse_args(argv) - - root_logger = logging.getLogger(__name__) - if args.verbose: - root_logger.setLevel(logging.DEBUG) - else: - root_logger.setLevel(logging.INFO) - - # if powerlog is used through main, log to sys.stdout - if __name__ == "__main__": - stdout_handler = logging.StreamHandler(sys.stdout) - stdout_handler.setFormatter(logging.Formatter('%(levelname)s: %(message)s')) - root_logger.addHandler(stdout_handler) - - integration_us_request = args.integration_us - if not args.board: - raise Exception("Power", "No board file selected, see board.README") - if not args.config: - raise Exception("Power", "No config file selected, see board.README") - - brdfile = args.board - cfgfile = args.config - seconds = args.seconds - serial_a = args.serial - serial_b = args.serial_b - sync_date = args.date - use_ms = args.ms - use_mW = args.mW - print_stats = args.print_stats - stats_dir = args.stats_dir - stats_json_dir = args.stats_json_dir - print_raw_data = args.print_raw_data - raw_data_dir = args.raw_data_dir - - boards = [] - - sync_speed = .8 - if args.slow: - sync_speed = 1.2 - - # Set up logging interface. - powerlogger = powerlog(brdfile, cfgfile, serial_a=serial_a, serial_b=serial_b, - sync_date=sync_date, use_ms=use_ms, use_mW=use_mW, - print_stats=print_stats, stats_dir=stats_dir, - stats_json_dir=stats_json_dir, - print_raw_data=print_raw_data,raw_data_dir=raw_data_dir) - - # Start logging. - powerlogger.start(integration_us_request, seconds, sync_speed=sync_speed) - - -if __name__ == "__main__": - main() diff --git a/extra/usb_power/powerlog_unittest.py b/extra/usb_power/powerlog_unittest.py deleted file mode 100644 index 1d0718530e..0000000000 --- a/extra/usb_power/powerlog_unittest.py +++ /dev/null @@ -1,54 +0,0 @@ -# Copyright 2018 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. -# -# Ignore indention messages, since legacy scripts use 2 spaces instead of 4. -# pylint: disable=bad-indentation,docstring-section-indent -# pylint: disable=docstring-trailing-quotes - -"""Unit tests for powerlog.""" - -import os -import shutil -import tempfile -import unittest - -import powerlog - -class TestPowerlog(unittest.TestCase): - """Test to verify powerlog util methods work as expected.""" - - def setUp(self): - """Set up data and create a temporary directory to save data and stats.""" - self.tempdir = tempfile.mkdtemp() - self.filename = 'testfile' - self.filepath = os.path.join(self.tempdir, self.filename) - with open(self.filepath, 'w') as f: - f.write('') - - def tearDown(self): - """Delete the temporary directory and its content.""" - shutil.rmtree(self.tempdir) - - def test_ProcessFilenameAbsoluteFilePath(self): - """Absolute file path is returned unchanged.""" - processed_fname = powerlog.process_filename(self.filepath) - self.assertEqual(self.filepath, processed_fname) - - def test_ProcessFilenameRelativeFilePath(self): - """Finds relative file path inside a known config location.""" - original = powerlog.CONFIG_LOCATIONS - powerlog.CONFIG_LOCATIONS = [self.tempdir] - processed_fname = powerlog.process_filename(self.filename) - try: - self.assertEqual(self.filepath, processed_fname) - finally: - powerlog.CONFIG_LOCATIONS = original - - def test_ProcessFilenameInvalid(self): - """IOError is raised when file cannot be found by any of the four ways.""" - with self.assertRaises(IOError): - powerlog.process_filename(self.filename) - -if __name__ == '__main__': - unittest.main() diff --git a/extra/usb_power/stats_manager.py b/extra/usb_power/stats_manager.py deleted file mode 100644 index 0f8c3fcb15..0000000000 --- a/extra/usb_power/stats_manager.py +++ /dev/null @@ -1,401 +0,0 @@ -# Copyright 2017 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. -# -# Ignore indention messages, since legacy scripts use 2 spaces instead of 4. -# pylint: disable=bad-indentation,docstring-section-indent -# pylint: disable=docstring-trailing-quotes - -"""Calculates statistics for lists of data and pretty print them.""" - -# Note: This is a py2/3 compatible file. - -from __future__ import print_function - -import collections -import json -import logging -import math -import os - -import numpy - -STATS_PREFIX = '@@' -NAN_TAG = '*' -NAN_DESCRIPTION = '%s domains contain NaN samples' % NAN_TAG - -LONG_UNIT = { - '': 'N/A', - 'mW': 'milliwatt', - 'uW': 'microwatt', - 'mV': 'millivolt', - 'uA': 'microamp', - 'uV': 'microvolt' -} - - -class StatsManagerError(Exception): - """Errors in StatsManager class.""" - pass - - -class StatsManager(object): - """Calculates statistics for several lists of data(float). - - Example usage: - - >>> stats = StatsManager(title='Title Banner') - >>> stats.AddSample(TIME_KEY, 50.0) - >>> stats.AddSample(TIME_KEY, 25.0) - >>> stats.AddSample(TIME_KEY, 40.0) - >>> stats.AddSample(TIME_KEY, 10.0) - >>> stats.AddSample(TIME_KEY, 10.0) - >>> stats.AddSample('frobnicate', 11.5) - >>> stats.AddSample('frobnicate', 9.0) - >>> stats.AddSample('foobar', 11111.0) - >>> stats.AddSample('foobar', 22222.0) - >>> stats.CalculateStats() - >>> print(stats.SummaryToString()) - ` @@-------------------------------------------------------------- - ` @@ Title Banner - @@-------------------------------------------------------------- - @@ NAME COUNT MEAN STDDEV MAX MIN - @@ sample_msecs 4 31.25 15.16 50.00 10.00 - @@ foobar 2 16666.50 5555.50 22222.00 11111.00 - @@ frobnicate 2 10.25 1.25 11.50 9.00 - ` @@-------------------------------------------------------------- - - Attributes: - _data: dict of list of readings for each domain(key) - _unit: dict of unit for each domain(key) - _smid: id supplied to differentiate data output to other StatsManager - instances that potentially save to the same directory - if smid all output files will be named |smid|_|fname| - _title: title to add as banner to formatted summary. If no title, - no banner gets added - _order: list of formatting order for domains. Domains not listed are - displayed in sorted order - _hide_domains: collection of domains to hide when formatting summary string - _accept_nan: flag to indicate if NaN samples are acceptable - _nan_domains: set to keep track of which domains contain NaN samples - _summary: dict of stats per domain (key): min, max, count, mean, stddev - _logger = StatsManager logger - - Note: - _summary is empty until CalculateStats() is called, and is updated when - CalculateStats() is called. - """ - - # pylint: disable=W0102 - def __init__(self, smid='', title='', order=[], hide_domains=[], - accept_nan=True): - """Initialize infrastructure for data and their statistics.""" - self._title = title - self._data = collections.defaultdict(list) - self._unit = collections.defaultdict(str) - self._smid = smid - self._order = order - self._hide_domains = hide_domains - self._accept_nan = accept_nan - self._nan_domains = set() - self._summary = {} - self._logger = logging.getLogger(type(self).__name__) - - def AddSample(self, domain, sample): - """Add one sample for a domain. - - Args: - domain: the domain name for the sample. - sample: one time sample for domain, expect type float. - - Raises: - StatsManagerError: if trying to add NaN and |_accept_nan| is false - """ - try: - sample = float(sample) - except ValueError: - # if we don't accept nan this will be caught below - self._logger.debug('sample %s for domain %s is not a number. Making NaN', - sample, domain) - sample = float('NaN') - if not self._accept_nan and math.isnan(sample): - raise StatsManagerError('accept_nan is false. Cannot add NaN sample.') - self._data[domain].append(sample) - if math.isnan(sample): - self._nan_domains.add(domain) - - def SetUnit(self, domain, unit): - """Set the unit for a domain. - - There can be only one unit for each domain. Setting unit twice will - overwrite the original unit. - - Args: - domain: the domain name. - unit: unit of the domain. - """ - if domain in self._unit: - self._logger.warning('overwriting the unit of %s, old unit is %s, new ' - 'unit is %s.', domain, self._unit[domain], unit) - self._unit[domain] = unit - - def CalculateStats(self): - """Calculate stats for all domain-data pairs. - - First erases all previous stats, then calculate stats for all data. - """ - self._summary = {} - for domain, data in self._data.items(): - data_np = numpy.array(data) - self._summary[domain] = { - 'mean': numpy.nanmean(data_np), - 'min': numpy.nanmin(data_np), - 'max': numpy.nanmax(data_np), - 'stddev': numpy.nanstd(data_np), - 'count': data_np.size, - } - - @property - def DomainsToDisplay(self): - """List of domains that the manager will output in summaries.""" - return set(self._summary.keys()) - set(self._hide_domains) - - @property - def NanInOutput(self): - """Return whether any of the domains to display have NaN values.""" - return bool(len(set(self._nan_domains) & self.DomainsToDisplay)) - - def _SummaryTable(self): - """Generate the matrix to output as a summary. - - Returns: - A 2d matrix of headers and their data for each domain - e.g. - [[NAME, COUNT, MEAN, STDDEV, MAX, MIN], - [pp5000_mw, 10, 50, 0, 50, 50]] - """ - headers = ('NAME', 'COUNT', 'MEAN', 'STDDEV', 'MAX', 'MIN') - table = [headers] - # determine what domains to display & and the order - domains_to_display = self.DomainsToDisplay - display_order = [key for key in self._order if key in domains_to_display] - domains_to_display -= set(display_order) - display_order.extend(sorted(domains_to_display)) - for domain in display_order: - stats = self._summary[domain] - if not domain.endswith(self._unit[domain]): - domain = '%s_%s' % (domain, self._unit[domain]) - if domain in self._nan_domains: - domain = '%s%s' % (domain, NAN_TAG) - row = [domain] - row.append(str(stats['count'])) - for entry in headers[2:]: - row.append('%.2f' % stats[entry.lower()]) - table.append(row) - return table - - def SummaryToMarkdownString(self): - """Format the summary into a b/ compatible markdown table string. - - This requires this sort of output format - - | header1 | header2 | header3 | ... - | --------- | --------- | --------- | ... - | sample1h1 | sample1h2 | sample1h3 | ... - . - . - . - - Returns: - formatted summary string. - """ - # All we need to do before processing is insert a row of '-' between - # the headers, and the data - table = self._SummaryTable() - columns = len(table[0]) - # Using '-:' to allow the numbers to be right aligned - sep_row = ['-'] + ['-:'] * (columns - 1) - table.insert(1, sep_row) - text_rows = ['|'.join(r) for r in table] - body = '\n'.join(['|%s|' % r for r in text_rows]) - if self._title: - title_section = '**%s** \n\n' % self._title - body = title_section + body - # Make sure that the body is terminated with a newline. - return body + '\n' - - def SummaryToString(self, prefix=STATS_PREFIX): - """Format summary into a string, ready for pretty print. - - See class description for format example. - - Args: - prefix: start every row in summary string with prefix, for easier reading. - - Returns: - formatted summary string. - """ - table = self._SummaryTable() - max_col_width = [] - for col_idx in range(len(table[0])): - col_item_widths = [len(row[col_idx]) for row in table] - max_col_width.append(max(col_item_widths)) - - formatted_lines = [] - for row in table: - formatted_row = prefix + ' ' - for i in range(len(row)): - formatted_row += row[i].rjust(max_col_width[i] + 2) - formatted_lines.append(formatted_row) - if self.NanInOutput: - formatted_lines.append('%s %s' % (prefix, NAN_DESCRIPTION)) - - if self._title: - line_length = len(formatted_lines[0]) - dec_length = len(prefix) - # trim title to be at most as long as the longest line without the prefix - title = self._title[:(line_length - dec_length)] - # line is a seperator line consisting of ----- - line = '%s%s' % (prefix, '-' * (line_length - dec_length)) - # prepend the prefix to the centered title - padded_title = '%s%s' % (prefix, title.center(line_length)[dec_length:]) - formatted_lines = [line, padded_title, line] + formatted_lines + [line] - formatted_output = '\n'.join(formatted_lines) - return formatted_output - - def GetSummary(self): - """Getter for summary.""" - return self._summary - - def _MakeUniqueFName(self, fname): - """prepend |_smid| to fname & rotate fname to ensure uniqueness. - - Before saving a file through the StatsManager, make sure that the filename - is unique, first by prepending the smid if any and otherwise by appending - increasing integer suffixes until the filename is unique. - - If |smid| is defined /path/to/example/file.txt becomes - /path/to/example/{smid}_file.txt. - - The rotation works by changing /path/to/example/somename.txt to - /path/to/example/somename1.txt if the first one already exists on the - system. - - Note: this is not thread-safe. While it makes sense to use StatsManager - in a threaded data-collection, the data retrieval should happen in a - single threaded environment to ensure files don't get potentially clobbered. - - Args: - fname: filename to ensure uniqueness. - - Returns: - {smid_}fname{tag}.[b].ext - the smid portion gets prepended if |smid| is defined - the tag portion gets appended if necessary to ensure unique fname - """ - fdir = os.path.dirname(fname) - base, ext = os.path.splitext(os.path.basename(fname)) - if self._smid: - base = '%s_%s' % (self._smid, base) - unique_fname = os.path.join(fdir, '%s%s' % (base, ext)) - tag = 0 - while os.path.exists(unique_fname): - old_fname = unique_fname - unique_fname = os.path.join(fdir, '%s%d%s' % (base, tag, ext)) - self._logger.warning('Attempted to store stats information at %s, but ' - 'file already exists. Attempting to store at %s ' - 'now.', old_fname, unique_fname) - tag += 1 - return unique_fname - - def SaveSummary(self, directory, fname='summary.txt', prefix=STATS_PREFIX): - """Save summary to file. - - Args: - directory: directory to save the summary in. - fname: filename to save summary under. - prefix: start every row in summary string with prefix, for easier reading. - - Returns: - full path of summary save location - """ - summary_str = self.SummaryToString(prefix=prefix) + '\n' - return self._SaveSummary(summary_str, directory, fname) - - def SaveSummaryJSON(self, directory, fname='summary.json'): - """Save summary (only MEAN) into a JSON file. - - Args: - directory: directory to save the JSON summary in. - fname: filename to save summary under. - - Returns: - full path of summary save location - """ - data = {} - for domain in self._summary: - unit = LONG_UNIT.get(self._unit[domain], self._unit[domain]) - data_entry = {'mean': self._summary[domain]['mean'], 'unit': unit} - data[domain] = data_entry - summary_str = json.dumps(data, indent=2) - return self._SaveSummary(summary_str, directory, fname) - - def SaveSummaryMD(self, directory, fname='summary.md'): - """Save summary into a MD file to paste into b/. - - Args: - directory: directory to save the MD summary in. - fname: filename to save summary under. - - Returns: - full path of summary save location - """ - summary_str = self.SummaryToMarkdownString() - return self._SaveSummary(summary_str, directory, fname) - - def _SaveSummary(self, output_str, directory, fname): - """Wrote |output_str| to |fname|. - - Args: - output_str: formatted output string - directory: directory to save the summary in. - fname: filename to save summary under. - - Returns: - full path of summary save location - """ - if not os.path.exists(directory): - os.makedirs(directory) - fname = self._MakeUniqueFName(os.path.join(directory, fname)) - with open(fname, 'w') as f: - f.write(output_str) - return fname - - def GetRawData(self): - """Getter for all raw_data.""" - return self._data - - def SaveRawData(self, directory, dirname='raw_data'): - """Save raw data to file. - - Args: - directory: directory to create the raw data folder in. - dirname: folder in which raw data live. - - Returns: - list of full path of each domain's raw data save location - """ - if not os.path.exists(directory): - os.makedirs(directory) - dirname = os.path.join(directory, dirname) - if not os.path.exists(dirname): - os.makedirs(dirname) - fnames = [] - for domain, data in self._data.items(): - if not domain.endswith(self._unit[domain]): - domain = '%s_%s' % (domain, self._unit[domain]) - fname = self._MakeUniqueFName(os.path.join(dirname, '%s.txt' % domain)) - with open(fname, 'w') as f: - f.write('\n'.join('%.2f' % sample for sample in data) + '\n') - fnames.append(fname) - return fnames diff --git a/extra/usb_power/stats_manager_unittest.py b/extra/usb_power/stats_manager_unittest.py deleted file mode 100644 index beb9984b93..0000000000 --- a/extra/usb_power/stats_manager_unittest.py +++ /dev/null @@ -1,315 +0,0 @@ -# Copyright 2017 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. -# -# Ignore indention messages, since legacy scripts use 2 spaces instead of 4. -# pylint: disable=bad-indentation,docstring-section-indent -# pylint: disable=docstring-trailing-quotes - -"""Unit tests for StatsManager.""" - -from __future__ import print_function -import json -import os -import re -import shutil -import tempfile -import unittest - -import stats_manager - - -class TestStatsManager(unittest.TestCase): - """Test to verify StatsManager methods work as expected. - - StatsManager should collect raw data, calculate their statistics, and save - them in expected format. - """ - - def _populate_mock_stats(self): - """Create a populated & processed StatsManager to test data retrieval.""" - self.data.AddSample('A', 99999.5) - self.data.AddSample('A', 100000.5) - self.data.SetUnit('A', 'uW') - self.data.SetUnit('A', 'mW') - self.data.AddSample('B', 1.5) - self.data.AddSample('B', 2.5) - self.data.AddSample('B', 3.5) - self.data.SetUnit('B', 'mV') - self.data.CalculateStats() - - def _populate_mock_stats_no_unit(self): - self.data.AddSample('B', 1000) - self.data.AddSample('A', 200) - self.data.SetUnit('A', 'blue') - - def setUp(self): - """Set up StatsManager and create a temporary directory for test.""" - self.tempdir = tempfile.mkdtemp() - self.data = stats_manager.StatsManager() - - def tearDown(self): - """Delete the temporary directory and its content.""" - shutil.rmtree(self.tempdir) - - def test_AddSample(self): - """Adding a sample successfully adds a sample.""" - self.data.AddSample('Test', 1000) - self.data.SetUnit('Test', 'test') - self.data.CalculateStats() - summary = self.data.GetSummary() - self.assertEqual(1, summary['Test']['count']) - - def test_AddSampleNoFloatAcceptNaN(self): - """Adding a non-number adds 'NaN' and doesn't raise an exception.""" - self.data.AddSample('Test', 10) - self.data.AddSample('Test', 20) - # adding a fake NaN: one that gets converted into NaN internally - self.data.AddSample('Test', 'fiesta') - # adding a real NaN - self.data.AddSample('Test', float('NaN')) - self.data.SetUnit('Test', 'test') - self.data.CalculateStats() - summary = self.data.GetSummary() - # assert that 'NaN' as added. - self.assertEqual(4, summary['Test']['count']) - # assert that mean, min, and max calculatings ignore the 'NaN' - self.assertEqual(10, summary['Test']['min']) - self.assertEqual(20, summary['Test']['max']) - self.assertEqual(15, summary['Test']['mean']) - - def test_AddSampleNoFloatNotAcceptNaN(self): - """Adding a non-number raises a StatsManagerError if accept_nan is False.""" - self.data = stats_manager.StatsManager(accept_nan=False) - with self.assertRaisesRegexp(stats_manager.StatsManagerError, - 'accept_nan is false. Cannot add NaN sample.'): - # adding a fake NaN: one that gets converted into NaN internally - self.data.AddSample('Test', 'fiesta') - with self.assertRaisesRegexp(stats_manager.StatsManagerError, - 'accept_nan is false. Cannot add NaN sample.'): - # adding a real NaN - self.data.AddSample('Test', float('NaN')) - - def test_AddSampleNoUnit(self): - """Not adding a unit does not cause an exception on CalculateStats().""" - self.data.AddSample('Test', 17) - self.data.CalculateStats() - summary = self.data.GetSummary() - self.assertEqual(1, summary['Test']['count']) - - def test_UnitSuffix(self): - """Unit gets appended as a suffix in the displayed summary.""" - self.data.AddSample('test', 250) - self.data.SetUnit('test', 'mw') - self.data.CalculateStats() - summary_str = self.data.SummaryToString() - self.assertIn('test_mw', summary_str) - - def test_DoubleUnitSuffix(self): - """If domain already ends in unit, verify that unit doesn't get appended.""" - self.data.AddSample('test_mw', 250) - self.data.SetUnit('test_mw', 'mw') - self.data.CalculateStats() - summary_str = self.data.SummaryToString() - self.assertIn('test_mw', summary_str) - self.assertNotIn('test_mw_mw', summary_str) - - def test_GetRawData(self): - """GetRawData returns exact same data as fed in.""" - self._populate_mock_stats() - raw_data = self.data.GetRawData() - self.assertListEqual([99999.5, 100000.5], raw_data['A']) - self.assertListEqual([1.5, 2.5, 3.5], raw_data['B']) - - def test_GetSummary(self): - """GetSummary returns expected stats about the data fed in.""" - self._populate_mock_stats() - summary = self.data.GetSummary() - self.assertEqual(2, summary['A']['count']) - self.assertAlmostEqual(100000.5, summary['A']['max']) - self.assertAlmostEqual(99999.5, summary['A']['min']) - self.assertAlmostEqual(0.5, summary['A']['stddev']) - self.assertAlmostEqual(100000.0, summary['A']['mean']) - self.assertEqual(3, summary['B']['count']) - self.assertAlmostEqual(3.5, summary['B']['max']) - self.assertAlmostEqual(1.5, summary['B']['min']) - self.assertAlmostEqual(0.81649658092773, summary['B']['stddev']) - self.assertAlmostEqual(2.5, summary['B']['mean']) - - def test_SaveRawData(self): - """SaveRawData stores same data as fed in.""" - self._populate_mock_stats() - dirname = 'unittest_raw_data' - expected_files = set(['A_mW.txt', 'B_mV.txt']) - fnames = self.data.SaveRawData(self.tempdir, dirname) - files_returned = set([os.path.basename(f) for f in fnames]) - # Assert that only the expected files got returned. - self.assertEqual(expected_files, files_returned) - # Assert that only the returned files are in the outdir. - self.assertEqual(set(os.listdir(os.path.join(self.tempdir, dirname))), - files_returned) - for fname in fnames: - with open(fname, 'r') as f: - if 'A_mW' in fname: - self.assertEqual('99999.50', f.readline().strip()) - self.assertEqual('100000.50', f.readline().strip()) - if 'B_mV' in fname: - self.assertEqual('1.50', f.readline().strip()) - self.assertEqual('2.50', f.readline().strip()) - self.assertEqual('3.50', f.readline().strip()) - - def test_SaveRawDataNoUnit(self): - """SaveRawData appends no unit suffix if the unit is not specified.""" - self._populate_mock_stats_no_unit() - self.data.CalculateStats() - outdir = 'unittest_raw_data' - files = self.data.SaveRawData(self.tempdir, outdir) - files = [os.path.basename(f) for f in files] - # Verify nothing gets appended to domain for filename if no unit exists. - self.assertIn('B.txt', files) - - def test_SaveRawDataSMID(self): - """SaveRawData uses the smid when creating output filename.""" - identifier = 'ec' - self.data = stats_manager.StatsManager(smid=identifier) - self._populate_mock_stats() - files = self.data.SaveRawData(self.tempdir) - for fname in files: - self.assertTrue(os.path.basename(fname).startswith(identifier)) - - def test_SummaryToStringNaNHelp(self): - """NaN containing row gets tagged with *, help banner gets added.""" - help_banner_exp = '%s %s' % (stats_manager.STATS_PREFIX, - stats_manager.NAN_DESCRIPTION) - nan_domain = 'A-domain' - nan_domain_exp = '%s%s' % (nan_domain, stats_manager.NAN_TAG) - # NaN helper banner is added when a NaN domain is found & domain gets tagged - data = stats_manager.StatsManager() - data.AddSample(nan_domain, float('NaN')) - data.AddSample(nan_domain, 17) - data.AddSample('B-domain', 17) - data.CalculateStats() - summarystr = data.SummaryToString() - self.assertIn(help_banner_exp, summarystr) - self.assertIn(nan_domain_exp, summarystr) - # NaN helper banner is not added when no NaN domain output, no tagging - data = stats_manager.StatsManager() - # nan_domain in this scenario does not contain any NaN - data.AddSample(nan_domain, 19) - data.AddSample('B-domain', 17) - data.CalculateStats() - summarystr = data.SummaryToString() - self.assertNotIn(help_banner_exp, summarystr) - self.assertNotIn(nan_domain_exp, summarystr) - - def test_SummaryToStringTitle(self): - """Title shows up in SummaryToString if title specified.""" - title = 'titulo' - data = stats_manager.StatsManager(title=title) - self._populate_mock_stats() - summary_str = data.SummaryToString() - self.assertIn(title, summary_str) - - def test_SummaryToStringHideDomains(self): - """Keys indicated in hide_domains are not printed in the summary.""" - data = stats_manager.StatsManager(hide_domains=['A-domain']) - data.AddSample('A-domain', 17) - data.AddSample('B-domain', 17) - data.CalculateStats() - summary_str = data.SummaryToString() - self.assertIn('B-domain', summary_str) - self.assertNotIn('A-domain', summary_str) - - def test_SummaryToStringOrder(self): - """Order passed into StatsManager is honoured when formatting summary.""" - # StatsManager that should print D & B first, and the subsequent elements - # are sorted. - d_b_a_c_regexp = re.compile('D-domain.*B-domain.*A-domain.*C-domain', - re.DOTALL) - data = stats_manager.StatsManager(order=['D-domain', 'B-domain']) - data.AddSample('A-domain', 17) - data.AddSample('B-domain', 17) - data.AddSample('C-domain', 17) - data.AddSample('D-domain', 17) - data.CalculateStats() - summary_str = data.SummaryToString() - self.assertRegexpMatches(summary_str, d_b_a_c_regexp) - - def test_MakeUniqueFName(self): - data = stats_manager.StatsManager() - testfile = os.path.join(self.tempdir, 'testfile.txt') - with open(testfile, 'w') as f: - f.write('') - expected_fname = os.path.join(self.tempdir, 'testfile0.txt') - self.assertEqual(expected_fname, data._MakeUniqueFName(testfile)) - - def test_SaveSummary(self): - """SaveSummary properly dumps the summary into a file.""" - self._populate_mock_stats() - fname = 'unittest_summary.txt' - expected_fname = os.path.join(self.tempdir, fname) - fname = self.data.SaveSummary(self.tempdir, fname) - # Assert the reported fname is the same as the expected fname - self.assertEqual(expected_fname, fname) - # Assert only the reported fname is output (in the tempdir) - self.assertEqual(set([os.path.basename(fname)]), - set(os.listdir(self.tempdir))) - with open(fname, 'r') as f: - self.assertEqual( - '@@ NAME COUNT MEAN STDDEV MAX MIN\n', - f.readline()) - self.assertEqual( - '@@ A_mW 2 100000.00 0.50 100000.50 99999.50\n', - f.readline()) - self.assertEqual( - '@@ B_mV 3 2.50 0.82 3.50 1.50\n', - f.readline()) - - def test_SaveSummarySMID(self): - """SaveSummary uses the smid when creating output filename.""" - identifier = 'ec' - self.data = stats_manager.StatsManager(smid=identifier) - self._populate_mock_stats() - fname = os.path.basename(self.data.SaveSummary(self.tempdir)) - self.assertTrue(fname.startswith(identifier)) - - def test_SaveSummaryJSON(self): - """SaveSummaryJSON saves the added data properly in JSON format.""" - self._populate_mock_stats() - fname = 'unittest_summary.json' - expected_fname = os.path.join(self.tempdir, fname) - fname = self.data.SaveSummaryJSON(self.tempdir, fname) - # Assert the reported fname is the same as the expected fname - self.assertEqual(expected_fname, fname) - # Assert only the reported fname is output (in the tempdir) - self.assertEqual(set([os.path.basename(fname)]), - set(os.listdir(self.tempdir))) - with open(fname, 'r') as f: - summary = json.load(f) - self.assertAlmostEqual(100000.0, summary['A']['mean']) - self.assertEqual('milliwatt', summary['A']['unit']) - self.assertAlmostEqual(2.5, summary['B']['mean']) - self.assertEqual('millivolt', summary['B']['unit']) - - def test_SaveSummaryJSONSMID(self): - """SaveSummaryJSON uses the smid when creating output filename.""" - identifier = 'ec' - self.data = stats_manager.StatsManager(smid=identifier) - self._populate_mock_stats() - fname = os.path.basename(self.data.SaveSummaryJSON(self.tempdir)) - self.assertTrue(fname.startswith(identifier)) - - def test_SaveSummaryJSONNoUnit(self): - """SaveSummaryJSON marks unknown units properly as N/A.""" - self._populate_mock_stats_no_unit() - self.data.CalculateStats() - fname = 'unittest_summary.json' - fname = self.data.SaveSummaryJSON(self.tempdir, fname) - with open(fname, 'r') as f: - summary = json.load(f) - self.assertEqual('blue', summary['A']['unit']) - # if no unit is specified, JSON should save 'N/A' as the unit. - self.assertEqual('N/A', summary['B']['unit']) - -if __name__ == '__main__': - unittest.main() diff --git a/extra/usb_serial/.gitignore b/extra/usb_serial/.gitignore deleted file mode 100644 index 77fee262b1..0000000000 --- a/extra/usb_serial/.gitignore +++ /dev/null @@ -1,12 +0,0 @@ -.built-in.o.cmd -.raiden.ko.cmd -.raiden.mod.o.cmd -.raiden.o.cmd -.tmp_versions/ -Module.symvers -built-in.o -modules.order -raiden.ko -raiden.mod.c -raiden.mod.o -raiden.o diff --git a/extra/usb_serial/51-google-serial-fallback.rules b/extra/usb_serial/51-google-serial-fallback.rules deleted file mode 100644 index 5f43e58e30..0000000000 --- a/extra/usb_serial/51-google-serial-fallback.rules +++ /dev/null @@ -1,6 +0,0 @@ -# -# Add USB VID/PID for usb-serial compatible CCD devices. This is a fallback -# rule that can be used if the raiden module can't be built, or used for some -# reason. -# -SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ATTR{idVendor}=="18d1", ENV{ID_USB_INTERFACES}=="*:ff5001:*", RUN+="add_usb_serial_id $attr{idVendor} $attr{idProduct}" diff --git a/extra/usb_serial/51-google-serial.rules b/extra/usb_serial/51-google-serial.rules deleted file mode 100644 index 3dedac1b1f..0000000000 --- a/extra/usb_serial/51-google-serial.rules +++ /dev/null @@ -1,33 +0,0 @@ -# -# Rules for Google Case Closed Debugging devices. -# -# The first rule matches the google VID and records the product name, USB bus -# number and USB device path (the device path is the list of hub ports between -# the root and the device). This becomes a unique directory name under which -# later rules can create symlinks. If this rule doesn't match udev will skip -# the rest of this files rules. -# -# This rule intentionally matches using SUBSYSTEMS and ATTRS instead of -# SUBSYSTEM and ATTR so that the GOOGLE_CCD_NAME is available to all nodes -# that descend from a Google USB device (this includes all USB interface nodes -# as well as all of the TTY nodes derived from CCD USB interfaces). -# -SUBSYSTEMS=="usb", ATTRS{idVendor}=="18d1", ENV{GOOGLE_CCD_NAME}="$attr{product}-$attr{busnum}-$attr{devpath}" - -# -# Force ModemManager to ignore all Google case closed debug devices. It would -# be better to just ignore the case closed debug serial console interfaces, but -# ModemManager doesn't look at the usb_interface udev node, it looks at the -# usb_device node, so you have to mark the entire device as incompatible with -# ModemManager. -# -# This node could lose the match against the usb_device DEVTYPE and still work, -# it would just add extraneous ID_MM_DEVICE_IGNORE tags to the TTY and USB -# interface nodes. -# -SUBSYSTEM=="usb", ENV{GOOGLE_CCD_NAME}!="", ENV{DEVTYPE}=="usb_device", ENV{ID_USB_INTERFACES}=="*:ff5001:*", ENV{ID_MM_DEVICE_IGNORE}="1" - -# -# Construct a symlink to a TTY generated from a CCD USB serial interface. -# -SUBSYSTEM=="tty", ENV{GOOGLE_CCD_NAME}!="", ATTRS{bInterfaceClass}=="ff", ATTRS{bInterfaceSubClass}=="50", ATTRS{bInterfaceProtocol}=="01", OPTIONS+="string_escape=replace", SYMLINK+="google/$env{GOOGLE_CCD_NAME}/serial/$attr{interface}" diff --git a/extra/usb_serial/Makefile b/extra/usb_serial/Makefile deleted file mode 100644 index 9c478050cc..0000000000 --- a/extra/usb_serial/Makefile +++ /dev/null @@ -1,10 +0,0 @@ -obj-m := raiden.o - -.PHONY: all - -all: modules - -.DEFAULT: - $(MAKE) -C /lib/modules/$(shell uname -r)/build \ - M=$(shell pwd) \ - $(MAKECMDGOALS) diff --git a/extra/usb_serial/README.md b/extra/usb_serial/README.md deleted file mode 100644 index 7cc6030d0a..0000000000 --- a/extra/usb_serial/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Case Closed Debugging Serial Consoles over USB - -Please see the documentation in [case_closed_debugging doc][1] - -[1]:https://chromium.googlesource.com/chromiumos/platform/ec/+/cr50_stab/docs/case_closed_debugging.md diff --git a/extra/usb_serial/add_usb_serial_id b/extra/usb_serial/add_usb_serial_id deleted file mode 100755 index ef8336afdc..0000000000 --- a/extra/usb_serial/add_usb_serial_id +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/sh -e -# -# Copyright 2016 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. -# -# Add a USB VID:PID device ID to the usbserial list of handled devices - -if [ $# -ne 2 ]; then - echo "" - echo "Usage: $0 <VID> <PID>" - echo "" - echo "Add a USB VID:PID device identification pair to the list of devices" - echo "that usbserial will recognize. This script ensures that a device is" - echo "not added more than once." - echo "" - exit 1 -fi - -# -# Firts ensure that the usbserial module is loaded. This is required as there -# may be no other USB serial adaptor connected yet. -# -modprobe usbserial - -device_id="$1 $2" -file="/sys/bus/usb-serial/drivers/generic/new_id" - -# -# Only add the device ID pair if it isn't already in the ID list. -# -grep -q "$device_id" $file || echo $device_id > $file diff --git a/extra/usb_serial/console.py b/extra/usb_serial/console.py deleted file mode 100755 index 7b3bacd903..0000000000 --- a/extra/usb_serial/console.py +++ /dev/null @@ -1,298 +0,0 @@ -#!/usr/bin/env python -# Copyright 2016 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. -# -# Ignore indention messages, since legacy scripts use 2 spaces instead of 4. -# pylint: disable=bad-indentation,docstring-section-indent -# pylint: disable=docstring-trailing-quotes - -"""Allow creation of uart/console interface via usb google serial endpoint.""" - -# Note: This is a py2/3 compatible file. - -from __future__ import print_function -import argparse -import array -import os -import sys -import termios -import threading -import time -import traceback -import tty -try: - import usb -except: - print("import usb failed") - print("try running these commands:") - print(" sudo apt-get install python-pip") - print(" sudo pip install --pre pyusb") - print() - sys.exit(-1) - -import six - - -def GetBuffer(stream): - if six.PY3: - return stream.buffer - return stream - - -"""Class Susb covers USB device discovery and initialization. - - It can find a particular endpoint by vid:pid, serial number, - and interface number. -""" - -class SusbError(Exception): - """Class for exceptions of Susb.""" - def __init__(self, msg, value=0): - """SusbError constructor. - - Args: - msg: string, message describing error in detail - value: integer, value of error when non-zero status returned. Default=0 - """ - super(SusbError, self).__init__(msg, value) - self.msg = msg - self.value = value - -class Susb(): - """Provide USB functionality. - - Instance Variables: - _read_ep: pyUSB read endpoint for this interface - _write_ep: pyUSB write endpoint for this interface - """ - READ_ENDPOINT = 0x81 - WRITE_ENDPOINT = 0x1 - TIMEOUT_MS = 100 - - def __init__(self, vendor=0x18d1, - product=0x500f, interface=1, serialname=None): - """Susb constructor. - - Discovers and connects to USB endpoints. - - Args: - vendor : usb vendor id of device - product : usb product id of device - interface : interface number ( 1 - 8 ) of device to use - serialname: string of device serialnumber. - - Raises: - SusbError: An error accessing Susb object - """ - # Find the device. - dev_g = usb.core.find(idVendor=vendor, idProduct=product, find_all=True) - dev_list = list(dev_g) - if dev_list is None: - raise SusbError("USB device not found") - - # Check if we have multiple devices. - dev = None - if serialname: - for d in dev_list: - dev_serial = "PyUSB doesn't have a stable interface" - try: - dev_serial = usb.util.get_string(d, 256, d.iSerialNumber) - except: - dev_serial = usb.util.get_string(d, d.iSerialNumber) - if dev_serial == serialname: - dev = d - break - if dev is None: - raise SusbError("USB device(%s) not found" % (serialname,)) - else: - try: - dev = dev_list[0] - except: - try: - dev = dev_list.next() - except: - raise SusbError("USB device %04x:%04x not found" % (vendor, product)) - - # If we can't set configuration, it's already been set. - try: - dev.set_configuration() - except usb.core.USBError: - pass - - # Get an endpoint instance. - cfg = dev.get_active_configuration() - intf = usb.util.find_descriptor(cfg, bInterfaceNumber=interface) - self._intf = intf - - if not intf: - raise SusbError("Interface not found") - - # Detach raiden.ko if it is loaded. - if dev.is_kernel_driver_active(intf.bInterfaceNumber) is True: - dev.detach_kernel_driver(intf.bInterfaceNumber) - - read_ep_number = intf.bInterfaceNumber + self.READ_ENDPOINT - read_ep = usb.util.find_descriptor(intf, bEndpointAddress=read_ep_number) - self._read_ep = read_ep - - write_ep_number = intf.bInterfaceNumber + self.WRITE_ENDPOINT - write_ep = usb.util.find_descriptor(intf, bEndpointAddress=write_ep_number) - self._write_ep = write_ep - - -"""Suart class implements a stream interface, to access Google's USB class. - - This creates a send and receive thread that monitors USB and console input - and forwards them across. This particular class is hardcoded to stdin/out. -""" - -class SuartError(Exception): - """Class for exceptions of Suart.""" - def __init__(self, msg, value=0): - """SuartError constructor. - - Args: - msg: string, message describing error in detail - value: integer, value of error when non-zero status returned. Default=0 - """ - super(SuartError, self).__init__(msg, value) - self.msg = msg - self.value = value - - -class Suart(): - """Provide interface to serial usb endpoint.""" - - def __init__(self, vendor=0x18d1, product=0x501c, interface=0, - serialname=None): - """Suart contstructor. - - Initializes USB stream interface. - - Args: - vendor: usb vendor id of device - product: usb product id of device - interface: interface number of device to use - serialname: Defaults to None. - - Raises: - SuartError: If init fails - """ - self._done = threading.Event() - self._susb = Susb(vendor=vendor, product=product, - interface=interface, serialname=serialname) - - def wait_until_done(self, timeout=None): - return self._done.wait(timeout=timeout) - - def run_rx_thread(self): - try: - while True: - try: - r = self._susb._read_ep.read(64, self._susb.TIMEOUT_MS) - if r: - GetBuffer(sys.stdout).write(r.tostring()) - GetBuffer(sys.stdout).flush() - - except Exception as e: - # If we miss some characters on pty disconnect, that's fine. - # ep.read() also throws USBError on timeout, which we discard. - if not isinstance(e, (OSError, usb.core.USBError)): - print("rx %s" % e) - finally: - self._done.set() - - def run_tx_thread(self): - try: - while True: - try: - r = GetBuffer(sys.stdin).read(1) - if not r or r == b"\x03": - break - if r: - self._susb._write_ep.write(array.array('B', r), - self._susb.TIMEOUT_MS) - except Exception as e: - print("tx %s" % e) - finally: - self._done.set() - - def run(self): - """Creates pthreads to poll USB & PTY for data. - """ - self._exit = False - - self._rx_thread = threading.Thread(target=self.run_rx_thread) - self._rx_thread.daemon = True - self._rx_thread.start() - - self._tx_thread = threading.Thread(target=self.run_tx_thread) - self._tx_thread.daemon = True - self._tx_thread.start() - - - -"""Command line functionality - - Allows specifying vid:pid, serialnumber, interface. - Ctrl-C exits. -""" - -parser = argparse.ArgumentParser(description="Open a console to a USB device") -parser.add_argument('-d', '--device', type=str, - help="vid:pid of target device", default="18d1:501c") -parser.add_argument('-i', '--interface', type=int, - help="interface number of console", default=0) -parser.add_argument('-s', '--serialno', type=str, - help="serial number of device", default="") -parser.add_argument('-S', '--notty-exit-sleep', type=float, default=0.2, - help="When stdin is *not* a TTY, wait this many seconds after EOF from " - "stdin before exiting, to give time for receiving a reply from the USB " - "device.") - - -def runconsole(): - """Run the usb console code - - Starts the pty thread, and idles until a ^C is caught. - """ - args = parser.parse_args() - - vidstr, pidstr = args.device.split(':') - vid = int(vidstr, 16) - pid = int(pidstr, 16) - - serialno = args.serialno - interface = args.interface - - sobj = Suart(vendor=vid, product=pid, interface=interface, - serialname=serialno) - if sys.stdin.isatty(): - tty.setraw(sys.stdin.fileno()) - sobj.run() - sobj.wait_until_done() - if not sys.stdin.isatty() and args.notty_exit_sleep > 0: - time.sleep(args.notty_exit_sleep) - - -def main(): - stdin_isatty = sys.stdin.isatty() - if stdin_isatty: - fd = sys.stdin.fileno() - os.system("stty -echo") - old_settings = termios.tcgetattr(fd) - - try: - runconsole() - finally: - if stdin_isatty: - termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) - os.system("stty echo") - # Avoid having the user's shell prompt start mid-line after the final output - # from this program. - print() - - -if __name__ == '__main__': - main() diff --git a/extra/usb_serial/install b/extra/usb_serial/install deleted file mode 100755 index eba1d2ac83..0000000000 --- a/extra/usb_serial/install +++ /dev/null @@ -1,88 +0,0 @@ -#!/bin/sh -e -# -# Copyright 2016 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. -# -# Build and install raiden module and udev rules - -bold=$(tput bold) -normal=$(tput sgr0) - -error() { - echo "${bold}Install failed${normal}" -} - -trap "error $LINENO" ERR - -fallback=false - -if [ "$1" = "--fallback" ]; then - fallback=true - shift -fi - -if [ $# -ne 0 ]; then - echo "" - echo "Usage: ${bold}$0${normal} [--fallback]" - echo "" - echo "Install Raiden kernel module and udev rules for working with Case" - echo "Closed Debug enabled devices." - echo "" - echo "${bold}--fallback${normal}" - echo " Install udev rules to use usbserial directly without installing" - echo " the raiden module. This can be used when the raiden module fails" - echo " to build, or can not be used for other reasons." - echo "" - echo " The fallback solution will generate extra /dev/ttyUSB? entries" - echo " for the SPI and possibly other CCD bridges. These should be" - echo " ignored by you. Flashrom is smart enough to detach the kernel" - echo " driver from the SPI bridge, so they will not interfere with" - echo " flashing new firmware images over CCD." - echo "" - exit 1 -fi - -if [ "$fallback" = "false" ]; then - # - # The normal path builds and installs the raiden module - # - { - # - # Don't build the module as root so it's easier to clean up after - # - make modules && - - # - # Install the new module and update dependency and alias information - # - sudo make modules_install && - sudo depmod -a - } || { - echo $bold - echo "Building and/or installing the raiden module failed, you may" - echo "want to use the --fallback option." - echo $normal - exit 1; - } -else - # - # The fallback path installs the fallback udev rule and its helper script. - # - sudo install -m644 51-google-serial-fallback.rules /etc/udev/rules.d - sudo install add_usb_serial_id /lib/udev -fi - -# -# Install the udev rule for creating /dev/google symlinks. -# -sudo install -m644 51-google-serial.rules /etc/udev/rules.d - -# -# Trigger udev to create the symlinks for any attached devices that have the -# Google Vendor ID. Limiting triggering like this prevents unwanted resetting -# of some device state, even with the change action specified. -# -for syspath in $(dirname $(grep -rxl --include=idVendor 18d1 /sys/devices)); do - sudo udevadm trigger --action=change --parent-match=${syspath} -done diff --git a/extra/usb_serial/raiden.c b/extra/usb_serial/raiden.c deleted file mode 100644 index e4720b4357..0000000000 --- a/extra/usb_serial/raiden.c +++ /dev/null @@ -1,46 +0,0 @@ -/* - * USB Serial module for Raiden USB debug serial console forwarding. - * SubClass and Protocol allocated in go/usb-ids - * - * Copyright 2014 The Chromium OS Authors <chromium-os-dev@chromium.org> - * Author: Anton Staaf <robotboy@chromium.org> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ - -#include <linux/init.h> -#include <linux/kernel.h> -#include <linux/module.h> -#include <linux/tty.h> -#include <linux/usb.h> -#include <linux/usb/serial.h> - -MODULE_LICENSE("GPL"); - -#define USB_VENDOR_ID_GOOGLE 0x18d1 -#define USB_SUBCLASS_GOOGLE_SERIAL 0x50 -#define USB_PROTOCOL_GOOGLE_SERIAL 0x01 - -static struct usb_device_id const ids[] = { - { USB_VENDOR_AND_INTERFACE_INFO(USB_VENDOR_ID_GOOGLE, - USB_CLASS_VENDOR_SPEC, - USB_SUBCLASS_GOOGLE_SERIAL, - USB_PROTOCOL_GOOGLE_SERIAL) }, - { 0 } -}; - -MODULE_DEVICE_TABLE(usb, ids); - -static struct usb_serial_driver device = -{ - .driver = { .owner = THIS_MODULE, - .name = "Google" }, - .id_table = ids, - .num_ports = 1, -}; - -static struct usb_serial_driver * const drivers[] = { &device, NULL }; - -module_usb_serial_driver(drivers, ids); diff --git a/extra/usb_updater/.gitignore b/extra/usb_updater/.gitignore deleted file mode 100644 index 37c3bd3808..0000000000 --- a/extra/usb_updater/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -generated_version.h -gsctool -usb_updater2 -*.d -*.o -dp
\ No newline at end of file diff --git a/extra/usb_updater/Makefile b/extra/usb_updater/Makefile deleted file mode 100644 index 1dfbc55645..0000000000 --- a/extra/usb_updater/Makefile +++ /dev/null @@ -1,55 +0,0 @@ -# Copyright 2015 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. - -CC ?= gcc -PKG_CONFIG ?= pkg-config -PROGRAMS := usb_updater2 -LIBS := -LFLAGS := -CFLAGS := -std=gnu99 \ - -g \ - -Wall \ - -Werror \ - -Wpointer-arith \ - -Wcast-align \ - -Wcast-qual \ - -Wundef \ - -Wsign-compare \ - -Wredundant-decls \ - -Wmissing-declarations - -ifeq (DEBUG,) -CFLAGS += -O3 -else -CFLAGS += -O0 -endif - -# -# Add libusb-1.0 required flags -# -LIBS += $(shell $(PKG_CONFIG) --libs libusb-1.0) -CFLAGS += $(shell $(PKG_CONFIG) --cflags libusb-1.0) -CFLAGS += -I../../include -I../../util -I../../fuzz -I../../test - -VPATH = ../../util - -LIBS_common = -lfmap - -all: $(PROGRAMS) - -%.o: %.c - $(CC) $(CFLAGS) -c -MMD -MF $(basename $@).d -o $@ $< - -# common EC code USB updater -usb_updater2: usb_updater2.c Makefile - $(CC) $(CFLAGS) $< $(LFLAGS) $(LIBS) $(LIBS_common) -o $@ - -.PHONY: clean - -clean: - rm -rf $(PROGRAMS) *~ *.o *.d dp - -parser_debug: desc_parser.c - gcc -g -O0 -DTEST_PARSER desc_parser.c -o dp - diff --git a/extra/usb_updater/c2d2.json b/extra/usb_updater/c2d2.json deleted file mode 100644 index 79fc6f0992..0000000000 --- a/extra/usb_updater/c2d2.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "Comment": "This file describes the updateable sections of the flash.", - "board": "c2d2", - "vid": "0x18d1", - "pid": "0x5041", - "console": "3", - "Comment on flash": "This is the base address of writeable flash", - "flash": "0x8000000", - "Comment on region format": "name: [baseoffset, length]", - "regions": { - "RW": ["0x10000", "0x10000"], - "PSTATE": ["0xf000", "0x1000"], - "RO": ["0x0000", "0xf000"] - } -} diff --git a/extra/usb_updater/desc_parser.c b/extra/usb_updater/desc_parser.c deleted file mode 100644 index 5bd996bdda..0000000000 --- a/extra/usb_updater/desc_parser.c +++ /dev/null @@ -1,377 +0,0 @@ -/* - * Copyright 2018 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. - */ - -#include <ctype.h> -#include <errno.h> -#include <malloc.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/stat.h> - -#include "desc_parser.h" - -static FILE *hash_file_; -static int line_count_; -static int section_count_; - -/* - * This is used to verify consistency of the description database, namely that - * all hash sections include the same number of hash variants. - */ -static size_t variant_count; - -/* Size of the retrieved string or negative OS error value. */ -static ssize_t get_next_line(char *next_line, size_t line_size) -{ - size_t index = 0; - - while (fgets(next_line + index, line_size - index, hash_file_)) { - line_count_++; - - if (next_line[index] == '#') - continue; /* Skip the comment */ - - if (next_line[index] == '\n') { - /* - * This is an empty line, return all collected data, - * pontintially an array of size zero if this is a - * repeated empty line. - */ - next_line[index] = '\0'; - return index; - } - - /* Make sure next string overwrites this string's newline. */ - index += strlen(next_line + index) - 1; - - if (index >= (line_size - 1)) { - fprintf(stderr, "%s: Input overflow in line %d\n", - __func__, line_count_); - return -EOVERFLOW; - } - } - - if (index) { - /* - * This must be the last line in the file with no empty line - * after it. Drop the closing newline, if it is there. - */ - if (next_line[index] == '\n') - next_line[index--] = '\0'; - - return index; - } - return errno ? -errno : -ENODATA; -} - -static int get_next_token(char *input, size_t expected_size, char **output) -{ - char *next_colon; - - next_colon = strchr(input, ':'); - if (next_colon) - *next_colon = '\0'; - if (!next_colon || (expected_size && - strlen(input) != expected_size)) { - fprintf(stderr, "Invalid entry in section %d\n", - section_count_); - return -EINVAL; - } - - *output = next_colon + 1; - return 0; -} - -static int get_hex_value(char *input, char **output) -{ - char *e; - long int value; - - if (strchr(input, ':')) - get_next_token(input, 0, output); - else - *output = NULL; - - value = strtol(input, &e, 16); - if ((e && *e) || (strlen(input) > 8)) { - 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, - struct addr_range *parsed_range) -{ - char *line_cursor; - char *next_token; - int is_a_hash_range; - struct result_node *node; - int value; - - section_count_++; - line_cursor = next_line; - - /* Range type. */ - if (get_next_token(line_cursor, 1, &next_token)) - return -EINVAL; - - switch (*line_cursor) { - case 'a': - parsed_range->range_type = AP_RANGE; - break; - case 'e': - parsed_range->range_type = EC_RANGE; - break; - case 'g': - parsed_range->range_type = EC_GANG_RANGE; - break; - default: - fprintf(stderr, "Invalid range type %c in section %d\n", - *line_cursor, section_count_); - return -EINVAL; - } - line_cursor = next_token; - - /* Hash or dump? */ - if (get_next_token(line_cursor, 1, &next_token)) - return -EINVAL; - - switch (*line_cursor) { - case 'd': - is_a_hash_range = 0; - break; - case 'h': - is_a_hash_range = 1; - break; - default: - fprintf(stderr, "Invalid entry kind %c in section %d\n", - *line_cursor, section_count_); - return -EINVAL; - } - line_cursor = next_token; - - /* Range base address. */ - value = get_hex_value(line_cursor, &next_token); - if (value < 0) - return -EINVAL; - parsed_range->base_addr = value; - - /* Range size. */ - line_cursor = next_token; - value = get_hex_value(line_cursor, &next_token); - if (value < 0) - return -EINVAL; - parsed_range->range_size = value; - - if (!next_token && is_a_hash_range) { - fprintf(stderr, "Missing hash in section %d\n", section_count_); - return -EINVAL; - } - - if (next_token && !is_a_hash_range) { - fprintf(stderr, "Unexpected data in section %d\n", - section_count_); - return -EINVAL; - } - - parsed_range->variant_count = 0; - if (!is_a_hash_range) - return 0; /* No more input for dump ranges. */ - - node = parsed_range->variants; - do { /* While line is not over. */ - char c; - int i = 0; - - line_cursor = next_token; - next_token = strchr(line_cursor, ':'); - if (next_token) - *next_token++ = '\0'; - if (strlen(line_cursor) != (2 * sizeof(*node))) { - fprintf(stderr, - "Invalid hash %zd size %zd in section %d\n", - parsed_range->variant_count + 1, - strlen(line_cursor), section_count_); - return -EINVAL; - } - - while ((c = *line_cursor++) != 0) { - uint8_t nibble; - - if (!isxdigit(c)) { - fprintf(stderr, - "Invalid hash %zd value in section %d\n", - parsed_range->variant_count + 1, - section_count_); - return -EINVAL; - } - - if (c <= '9') - nibble = c - '0'; - else if (c >= 'a') - nibble = c - 'a' + 10; - else - nibble = c - 'A' + 10; - - if (i & 1) - node->expected_result[i / 2] |= nibble; - else - node->expected_result[i / 2] = nibble << 4; - - i++; - } - - node++; - parsed_range->variant_count++; - - } while (next_token); - - return 0; -} - -int parser_get_next_range(struct addr_range **range) -{ - char next_line[1000]; /* Should be enough for the largest descriptor. */ - ssize_t entry_size; - struct addr_range *new_range; - int rv; - - /* - * We come here after hash descriptor database file was opened and the - * current board's section has been found. Just in case check if the - * file has been opened. - */ - if (!hash_file_ || !range) - return -EIO; - - *range = NULL; - do { - entry_size = get_next_line(next_line, sizeof(next_line)); - if (entry_size < 0) - return entry_size; - } while (!entry_size); /* Skip empty lines. */ - - if (entry_size == 4) /* Next board's entry must have been reached. */ - return -ENODATA; - - /* This sure will be enough to fit parsed structure contents. */ - new_range = malloc(sizeof(*new_range) + entry_size); - if (!new_range) { - fprintf(stderr, "Failed to allocate %zd bytes\n", - sizeof(*new_range) + entry_size); - return -ENOMEM; - } - - /* This must be a new descriptor section, lets parse it. */ - rv = parse_range(next_line, entry_size, new_range); - - if (rv) { - free(new_range); - return rv; - } - - if (new_range->variant_count) { - /* - * A new range was found, if this is the first hash range we - * encountered, save its dimensions for future reference. - * - * If this is not the first one - verify that it has the same - * number of hash variants as all previous hash blocks. - */ - if (!variant_count) { - variant_count = new_range->variant_count; - } else if (variant_count != new_range->variant_count) { - fprintf(stderr, - "Unexpected number of variants in section %d\n", - section_count_); - free(new_range); - return -EINVAL; - } - } - - *range = new_range; - return 0; - -} - -int parser_find_board(const char *hash_file_name, const char *board_id) -{ - char next_line[1000]; /* Should be enough for the largest descriptor. */ - ssize_t id_len = strlen(board_id); - - if (!hash_file_) { - hash_file_ = fopen(hash_file_name, "r"); - if (!hash_file_) { - fprintf(stderr, "Error:%s can not open file '%s'\n", - strerror(errno), hash_file_name); - return errno; - } - } - - while (1) { - ssize_t entry_size; - - entry_size = get_next_line(next_line, sizeof(next_line)); - if (entry_size < 0) { - return entry_size; - } - - if ((entry_size == id_len) && - !memcmp(next_line, board_id, id_len)) { - variant_count = 0; - return 0; - } - } - - return -ENODATA; -} - -void parser_done(void) -{ - if (!hash_file_) - return; - - fclose(hash_file_); - hash_file_ = NULL; -} - -#ifdef TEST_PARSER -int main(int argc, char **argv) -{ - const char *board_name = "QZUX"; - char next_line[1000]; /* Should be enough for the largest descriptor. */ - int rv; - int count; - - if (argc < 2) { - fprintf(stderr, "Name of the file to parse is required.\n"); - return -1; - } - - if (parser_find_board(argv[1], board_name)) { - fprintf(stderr, "Board %s NOT found\n", board_name); - return -1; - } - - count = 0; - do { - struct addr_range *range; - - rv = parser_get_next_range(&range); - count++; - printf("Section %d, rv %d\n", count, rv); - free(range); /* Freeing NULL is OK. */ - - } while (rv != -ENODATA); - - return 0; -} -#endif diff --git a/extra/usb_updater/desc_parser.h b/extra/usb_updater/desc_parser.h deleted file mode 100644 index faa80d1a63..0000000000 --- a/extra/usb_updater/desc_parser.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2018 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. - */ -#ifndef __EXTRA_USB_UPDATER_DESC_PARSER_H -#define __EXTRA_USB_UPDATER_DESC_PARSER_H - -#include <stddef.h> -#include <stdint.h> - -struct result_node { - uint8_t expected_result[32]; -}; - -enum range_type_t { - NOT_A_RANGE, - AP_RANGE, - EC_RANGE, - EC_GANG_RANGE, -}; - -struct addr_range { - enum range_type_t range_type; - uint32_t base_addr; - uint32_t range_size; - size_t variant_count; /* Set to zero for dump ranges. */ - struct result_node variants[0]; -}; - -/* Board description retrieval API includes the following functions. */ - -/* - * In the given hash database file find board by its ID. Return zero on - * success, or OS error of error. In particular ENODATA is returned if the - * section for the required board ID is not found in the file. - */ -int parser_find_board(const char *hash_file_name, const char board_id[4]); - -/* - * Find next range for the previousely defined board, parse it into the - * addr_range structure and return pointer to the parsed structure to the - * caller, set pointer to NULL if no more entries are available or in case of - * error. - * - * Caller of this function is responsible for returning memory allocated for - * the entry. - * - * Return value set to zero on success, or to OS error if one occurs. EIO is - * used if an attmept to get next range is made before hash database file was - * opened and board entry in it was found. - */ -int parser_get_next_range(struct addr_range **range); - -/* Close the hash database file. */ -void parser_done(void); - -#endif // __EXTRA_USB_UPDATER_DESC_PARSER_H diff --git a/extra/usb_updater/ecusb b/extra/usb_updater/ecusb deleted file mode 120000 index c06ee0f51b..0000000000 --- a/extra/usb_updater/ecusb +++ /dev/null @@ -1 +0,0 @@ -../tigertool/ecusb/
\ No newline at end of file diff --git a/extra/usb_updater/fw_update.py b/extra/usb_updater/fw_update.py deleted file mode 100755 index 0d7a570fc3..0000000000 --- a/extra/usb_updater/fw_update.py +++ /dev/null @@ -1,426 +0,0 @@ -#!/usr/bin/env python -# Copyright 2016 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. -# -# 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. - -from __future__ import print_function - -import argparse -import array -import json -import os -import struct -import sys -import time -from pprint import pprint -import usb - - -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 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 - - 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") - -def main(): - global debug - args = parser.parse_args() - - - 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) - - p = Supdate() - p.load_board(brdfile) - p.connect_usb(serialname=serial) - p.load_file(binfile) - - # 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() - - # Finalize - log("Done. Finalizing.") - p.stop() - -if __name__ == "__main__": - main() - - diff --git a/extra/usb_updater/sample_descriptor b/extra/usb_updater/sample_descriptor deleted file mode 100644 index 1566e9e2e1..0000000000 --- a/extra/usb_updater/sample_descriptor +++ /dev/null @@ -1,87 +0,0 @@ -# Copyright 2018 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. -# -# Hash descriptor database file consists of sections for various Chrome OS -# boards. Each board description section starts with a line of 4 characters -# which is the board ID (the same as the board's RLZ code). -# -# Each board description section includes variable number of range -# descriptor entries, each entry consisting of semicolon separated fields: -# -# {a|e|g}:{h|d}:base_addr:size[:value[:value[:value...]]]] -# -# Where -# -# - the first sindgle character field defines the way the range is accessed: -# a - AP flash -# e - EC flash -# g - EC flash requiring gang programming mode -# - the second single character field defines the range type -# h - Cr50 returns the hash of the range -# d - Cr50 returns actual contents of the range (hex dump) -# - the third and and forth fields are base address and size of the range -# - ranges of type 'h' include one or more values for the hash of the range. -# -# Descriptor entries can be split along multiple lines. Each entry is -# terminated by an empty line. Board description section is completed when -# another board ID or end of file is encountered. -# -# All values are expressed in hex. Repeating empty lines and lines starting -# with '#' are ignored. -# - -QZUX - -# 1: Valid hash section. -a:h:0:10000: -756c41b90ac9aa23a6c98ce13549dccd72e0a83f8537eb834d9cfc3d12bf3503: -336c41b90ac9aa23a6c98ce13549dccd72e0a83f8537eb834d9cfc3d12bf3503: -446c41b90ac9aa23a6c98ce13549dccd72e0a83f8537eb834d9cfc3d12bf3503 - -# 2: Valid dump section. -a:d:10:10 - -# 3: Valid hash section. -e:h:0:100: -55d262badc1116520a7ae1d3fda380c0382b4b87f0db10de6495053ba3aadb87: -444442badc1116520a7ae1d3fda380c0382b4b87f0db10de6495053ba3aadb87: -443322badc1116520a7ae1d3fda380c0382b4b87f0db10de6495053ba3aadb87 - -# 4: Invalid dump section (includes hash) -a:d:20:10:55d262badc1116520a7ae1d3fda380c0382b4b87f0db10de6495053ba3aadb87 - -# 5: Invalid hash section (does not include hash) -e:h:0:100: - -# 6: Another invalid hash section (does not include hash) -e:h:0:100: - -# extra empty lines - - -# 7: Invalid hash section (hash too short) -e:h:0:100: -55d262badc1116520a7ae1d3fda380c0382b4b87f0db10de6495053ba3aadb8 - -# 8: Invalid hash section (hash too long) -a:h:0:10000: -756c41b90ac9aa23a6c98ce13549dccd72e0a83f8537eb834d9cfc3d12bf35034: -336c41b90ac9aa23a6c98ce13549dccd72e0a83f8537eb834d9cfc3d12bf3503 - -# 9: Invalid hash section (hash includes non-hex value) -a:h:0:10000: -756c41b90ac9aa23a6c98ce13549dccd7xe0a83f8537eb834d9cfc3d12bf3503: -336c41b90ac9aa23a6c98ce13549dccd72e0a83f8537eb834d9cfc3d12bf3505 - -# 10: Invalid hash section (hash does not include 3 variants) -a:h:0:10000: -756c41b90ac9aa23a6c98ce13549dccd75e0a83f8537eb834d9cfc3d12bf3503: -336c41b90ac9aa23a6c98ce13549dccd72e0a83f8537eb834d9cfc3d12bf3505 - -# 11: Invalid dump section (size includes non hex character) -a:d:10:10x - -ABCD - -a:d:10:10 diff --git a/extra/usb_updater/servo_micro.json b/extra/usb_updater/servo_micro.json deleted file mode 100644 index 71b1fd25dc..0000000000 --- a/extra/usb_updater/servo_micro.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "Comment": "This file describes the updateable sections of the flash.", - "board": "servo_micro", - "vid": "0x18d1", - "pid": "0x501a", - "console": "3", - "Comment on flash": "This is the base address of writeable flash", - "flash": "0x8000000", - "Comment on region format": "name: [baseoffset, length]", - "regions": { - "RW": ["0x10000", "0x10000"], - "PSTATE": ["0xf000", "0x1000"], - "RO": ["0x0000", "0xf000"] - } -} diff --git a/extra/usb_updater/servo_updater.py b/extra/usb_updater/servo_updater.py deleted file mode 100755 index 4dff264182..0000000000 --- a/extra/usb_updater/servo_updater.py +++ /dev/null @@ -1,456 +0,0 @@ -#!/usr/bin/env python -# Copyright 2016 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. -# -# 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. - -from __future__ import print_function - -import argparse -import errno -import os -import re -import subprocess -import time -import tempfile - -import json - -import fw_update -import ecusb.tiny_servo_common as c -from ecusb import tiny_servod - -class ServoUpdaterException(Exception): - """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' - -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] - -# 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' - -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 -# there are issues. e.g. if the all channels are the same, we want to let the -# 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'] - -DEFAULT_BASE_PATH = '/usr/' -TEST_IMAGE_BASE_PATH = '/usr/local/' - -COMMON_PATH = 'share/servo_updater' - -FIRMWARE_DIR = "firmware/" -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)) - -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() - -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)) - -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. - - 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 is "ro": - cmd = "reboot" - else: - cmd = "sysjump %s" % region - - tinys.pty._issue_cmd(cmd) - - tinys.close() - time.sleep(2) - tinys.reinitialize() - - 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'. - - Args: - tinys: TinyServod object - - Returns: - detected version number - - Commands are: - # > version - # ... - # Build: tigertail_v1.1.6749-74d1a312e - """ - cmd = '\r\nversion\r\n' - regex = 'Build:\s+(\S+)[\r\n]+' - - results = tinys.pty._issue_cmd_get_results(cmd, [regex])[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) - -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 - -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 - -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 - 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\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 - 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: - 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: - 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 =====") - -if __name__ == "__main__": - main() diff --git a/extra/usb_updater/servo_v4.json b/extra/usb_updater/servo_v4.json deleted file mode 100644 index e041f56b68..0000000000 --- a/extra/usb_updater/servo_v4.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "Comment": "This file describes the updateable sections of the flash.", - "board": "servo_v4", - "vid": "0x18d1", - "pid": "0x501b", - "console": "0", - "Comment on flash": "This is the base address of writeable flash", - "flash": "0x8000000", - "Comment on region format": "name: [baseoffset, length]", - "regions": { - "RW": ["0x10000", "0x10000"], - "PSTATE": ["0xf000", "0x1000"], - "RO": ["0x0000", "0xf000"] - } -} diff --git a/extra/usb_updater/servo_v4p1.json b/extra/usb_updater/servo_v4p1.json deleted file mode 100644 index 46efbf24ad..0000000000 --- a/extra/usb_updater/servo_v4p1.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "Comment": "This file describes the updateable sections of the flash.", - "board": "servo_v4p1", - "vid": "0x18d1", - "pid": "0x520d", - "console": "0", - "Comment on flash": "This is the base address of writeable flash", - "flash": "0x8000000", - "Comment on region format": "name: [baseoffset, length]", - "regions": { - "RW": ["0x10000", "0x10000"], - "PSTATE": ["0xf000", "0x1000"], - "RO": ["0x0000", "0xf000"] - } -} diff --git a/extra/usb_updater/sweetberry.json b/extra/usb_updater/sweetberry.json deleted file mode 100644 index 6b70d19fad..0000000000 --- a/extra/usb_updater/sweetberry.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "Comment": "This file describes the updateable sections of the flash.", - "board": "sweetberry", - "vid": "0x18d1", - "pid": "0x5020", - "console": "0", - "Comment on flash": "This is the base address of writeable flash", - "flash": "0x8000000", - "Comment on region format": "name: [baseoffset, length]", - "regions": { - "RW": ["0x40000", "0x40000"], - "RO": ["0x0000", "0x40000"] - } -} diff --git a/extra/usb_updater/usb_updater2.c b/extra/usb_updater/usb_updater2.c deleted file mode 100644 index 12ee1615fc..0000000000 --- a/extra/usb_updater/usb_updater2.c +++ /dev/null @@ -1,1244 +0,0 @@ -/* - * Copyright 2017 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. - */ - -#include <asm/byteorder.h> -#include <endian.h> -#include <fcntl.h> -#include <getopt.h> -#include <libusb.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/stat.h> -#include <sys/types.h> -#include <time.h> -#include <unistd.h> - -#include <fmap.h> - -#ifndef __packed -#define __packed __attribute__((packed)) -#endif - -#include "compile_time_macros.h" -#include "misc_util.h" -#include "usb_descriptor.h" -#include "update_fw.h" -#include "vb21_struct.h" - -#ifdef DEBUG -#define debug printf -#else -#define debug(fmt, args...) -#endif - -/* - * This file contains the source code of a Linux application used to update - * EC device firmware (common code only, gsctool takes care of cr50). - */ - -#define VID USB_VID_GOOGLE -#define PID 0x5022 -#define SUBCLASS USB_SUBCLASS_GOOGLE_UPDATE -#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. */ -}; - -struct usb_endpoint { - struct libusb_device_handle *devh; - uint8_t ep_num; - int chunk_len; -}; - -struct transfer_descriptor { - /* - * offsets of section available for update (not currently active). - */ - uint32_t offset; - - struct usb_endpoint uep; -}; - -/* Information about the target */ -static struct first_response_pdu targ; - -static uint16_t protocol_version; -static uint16_t header_type; -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'}, - {}, -}; - -/* Release USB device and return error to the OS. */ -static void shut_down(struct usb_endpoint *uep) -{ - libusb_close(uep->devh); - libusb_exit(NULL); - exit(update_error); -} - -static void usage(int errs) -{ - printf("\nUsage: %s [options] <binary image>\n" - "\n" - "This updates EC firmware over USB (common code EC, no cr50).\n" - "The required argument is the full RO+RW image.\n" - "\n" - "Options:\n" - "\n" - " -b,--binvers Report versions of image's " - "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" - " -g,--tp_debug <hex data> Touchpad debug command\n" - " -h,--help Show this message\n" - " -j,--jump_to_rw Tell EC to jump to RW\n" - " -l,--follow_log Get console log\n" - " -p,--tp_update file Update touchpad FW\n" - " -r,--reboot Tell EC to reboot\n" - " -s,--stay_in_ro Tell EC to stay in RO\n" - " -S,--serial Device serial number\n" - " -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); - - exit(errs ? update_error : noop); -} - -static void str2hex(const char *str, uint8_t *data, int *len) -{ - int i; - int slen = strlen(str); - - if (slen/2 > *len) { - fprintf(stderr, "Hex string too long.\n"); - exit(update_error); - } - - if (slen % 2 != 0) { - fprintf(stderr, "Hex string length not a multiple of 2.\n"); - exit(update_error); - } - - for (i = 0, *len = 0; i < slen; i += 2, (*len)++) { - char *end; - char tmp[3]; - - tmp[0] = str[i]; - tmp[1] = str[i+1]; - tmp[2] = 0; - - data[*len] = strtol(tmp, &end, 16); - - if (*end != 0) { - fprintf(stderr, "Invalid hex string.\n"); - exit(update_error); - } - } -} - -static void hexdump(const uint8_t *data, int len) -{ - int i; - - for (i = 0; i < len; i++) { - printf("%02x", data[i]); - if ((i % 16) == 15) - printf("\n"); - } - - if ((len % 16) != 0) - printf("\n"); -} - -static void dump_touchpad_info(const uint8_t *data, int len) -{ - const struct touchpad_info *info = (const struct touchpad_info *)data; - - if (len != sizeof(struct touchpad_info)) { - fprintf(stderr, "Hex string length is not %zu", - sizeof(struct touchpad_info)); - hexdump(data, len); - return; - } - - printf("\n"); - printf("status: 0x%02x\n", info->status); - printf("vendor: 0x%04x\n", info->vendor); - printf("fw_address: 0x%08x\n", info->fw_address); - printf("fw_size: 0x%08x\n", info->fw_size); - - printf("allowed_fw_hash:\n"); - hexdump(info->allowed_fw_hash, sizeof(info->allowed_fw_hash)); - - switch (info->vendor) { - case 0x04f3: /* ELAN */ - case 0x0483: /* ST */ - printf("id: 0x%04x\n", info->elan.id); - printf("fw_version: 0x%04x\n", info->elan.fw_version); - printf("fw_fw_checksum: 0x%04x\n", info->elan.fw_checksum); - break; - default: - fprintf(stderr, "Unknown vendor, vendor specific data:\n"); - hexdump((const uint8_t *)&info->elan, sizeof(info->elan)); - break; - } -} - -/* Read file into buffer */ -static uint8_t *get_file_or_die(const char *filename, size_t *len_ptr) -{ - FILE *fp; - struct stat st; - uint8_t *data; - size_t len; - - fp = fopen(filename, "rb"); - if (!fp) { - perror(filename); - exit(update_error); - } - if (fstat(fileno(fp), &st)) { - perror("stat"); - exit(update_error); - } - - len = st.st_size; - - data = malloc(len); - if (!data) { - perror("malloc"); - exit(update_error); - } - - if (fread(data, st.st_size, 1, fp) != 1) { - perror("fread"); - exit(update_error); - } - - fclose(fp); - - *len_ptr = len; - return data; -} - -#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 - * valid response could be shortef than allotted memory, the 'rxed_count' - * pointer, if provided along with 'allow_less' lets the caller know how mavy - * 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) -{ - - int r, actual; - - /* Send data out */ - if (outbuf && outlen) { - actual = 0; - r = libusb_bulk_transfer(uep->devh, uep->ep_num, - outbuf, outlen, - &actual, 2000); - if (r < 0) { - USB_ERROR("libusb_bulk_transfer", r); - exit(update_error); - } - if (actual != outlen) { - fprintf(stderr, "%s:%d, only sent %d/%d bytes\n", - __FILE__, __LINE__, actual, outlen); - shut_down(uep); - } - } - - /* Read reply back */ - if (inbuf && inlen) { - - actual = 0; - 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); - } - if ((actual != inlen) && !allow_less) { - fprintf(stderr, "%s:%d, only received %d/%d bytes\n", - __FILE__, __LINE__, actual, inlen); - hexdump(inbuf, actual); - shut_down(uep); - } - - if (rxed_count) - *rxed_count = actual; - } -} - -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); -} - -/* Return 0 on error, since it's never gonna be EP 0 */ -static int find_endpoint(const struct libusb_interface_descriptor *iface, - struct usb_endpoint *uep) -{ - const struct libusb_endpoint_descriptor *ep; - - if (iface->bInterfaceClass == 255 && - iface->bInterfaceSubClass == SUBCLASS && - iface->bInterfaceProtocol == PROTOCOL && - iface->bNumEndpoints) { - ep = &iface->endpoint[0]; - uep->ep_num = ep->bEndpointAddress & 0x7f; - uep->chunk_len = ep->wMaxPacketSize; - return 1; - } - - return 0; -} - -/* Return -1 on error */ -static int find_interface(struct usb_endpoint *uep) -{ - int iface_num = -1; - int r, i, j; - struct libusb_device *dev; - struct libusb_config_descriptor *conf = 0; - const struct libusb_interface *iface0; - const struct libusb_interface_descriptor *iface; - - dev = libusb_get_device(uep->devh); - r = libusb_get_active_config_descriptor(dev, &conf); - if (r < 0) { - USB_ERROR("libusb_get_active_config_descriptor", r); - goto out; - } - - for (i = 0; i < conf->bNumInterfaces; i++) { - iface0 = &conf->interface[i]; - for (j = 0; j < iface0->num_altsetting; j++) { - iface = &iface0->altsetting[j]; - if (find_endpoint(iface, uep)) { - iface_num = i; - goto out; - } - } - } - -out: - libusb_free_config_descriptor(conf); - return iface_num; -} - -/* Returns true if parsed. */ -static int parse_vidpid(const char *input, uint16_t *vid_ptr, uint16_t *pid_ptr) -{ - char *copy, *s, *e = 0; - - copy = strdup(input); - - s = strchr(copy, ':'); - if (!s) - return 0; - *s++ = '\0'; - - *vid_ptr = (uint16_t) strtoull(copy, &e, 16); - if (!*optarg || (e && *e)) - return 0; - - *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) -{ - struct libusb_device_descriptor desc; - libusb_device_handle *handle = NULL; - char sn[256]; - int ret; - int match = 1; - int snvalid = 0; - - ret = libusb_get_device_descriptor(dev, &desc); - if (ret < 0) - return NULL; - - ret = libusb_open(dev, &handle); - - if (ret != LIBUSB_SUCCESS) - return NULL; - - if (desc.iSerialNumber) { - ret = libusb_get_string_descriptor_ascii(handle, - desc.iSerialNumber, (unsigned char *)sn, sizeof(sn)); - if (ret > 0) - snvalid = 1; - } - - if (vid != 0 && vid != desc.idVendor) - match = 0; - if (pid != 0 && pid != desc.idProduct) - match = 0; - if (serialno != NULL && (!snvalid || strstr(sn, serialno) == NULL)) - match = 0; - - if (match) - return handle; - - libusb_close(handle); - return NULL; -} - -static void usb_findit(uint16_t vid, uint16_t pid, - char *serialno, struct usb_endpoint *uep) -{ - int iface_num, r, i; - libusb_device **devs; - libusb_device_handle *devh = NULL; - ssize_t count; - - memset(uep, 0, sizeof(*uep)); - - r = libusb_init(NULL); - if (r < 0) { - USB_ERROR("libusb_init", r); - exit(update_error); - } - - count = libusb_get_device_list(NULL, &devs); - if (count < 0) - return; - - for (i = 0; devs[i]; i++) { - devh = check_device(devs[i], vid, pid, serialno); - if (devh) { - printf("Found device.\n"); - break; - } - } - - libusb_free_device_list(devs, 1); - - if (!devh) { - fprintf(stderr, "Can't find device\n"); - exit(update_error); - } - - uep->devh = devh; - - iface_num = find_interface(uep); - if (iface_num < 0) { - fprintf(stderr, "USB FW update not supported by that device\n"); - shut_down(uep); - } - if (!uep->chunk_len) { - fprintf(stderr, "wMaxPacketSize isn't valid\n"); - shut_down(uep); - } - - 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); - if (r < 0) { - USB_ERROR("libusb_claim_interface", r); - shut_down(uep); - } - - printf("READY\n-------\n"); -} - -static int transfer_block(struct usb_endpoint *uep, - struct update_frame_header *ufh, - uint8_t *transfer_data_ptr, size_t payload_size) -{ - size_t transfer_size; - uint32_t reply; - int actual; - int r; - - /* First send the header. */ - xfer(uep, ufh, sizeof(*ufh), NULL, 0, 0); - - /* Now send the block, chunk by chunk. */ - for (transfer_size = 0; transfer_size < payload_size;) { - int chunk_size; - - chunk_size = MIN(uep->chunk_len, payload_size - transfer_size); - xfer(uep, transfer_data_ptr, chunk_size, NULL, 0, 0); - transfer_data_ptr += chunk_size; - transfer_size += chunk_size; - } - - /* Now get the reply. */ - 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"); - return r; - } - USB_ERROR("libusb_bulk_transfer", r); - shut_down(uep); - } - - reply = *((uint8_t *)&reply); - if (reply) { - fprintf(stderr, "Error: status %#x\n", reply); - exit(update_error); - } - - return 0; -} - -/** - * Transfer an image section (typically RW or RO). - * - * td - transfer descriptor to use to communicate with the target - * data_ptr - pointer at the section base in the image - * section_addr - address of the section in the target memory space - * 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, - uint8_t smart_update) -{ - /* - * Actually, we can skip trailing chunks of 0xff, as the entire - * section space must be erased before the update is attempted. - * - * FIXME: We can be smarter than this and skip blocks within the image. - */ - if (smart_update) - while (data_len && (data_ptr[data_len - 1] == 0xff)) - data_len--; - - printf("sending 0x%zx bytes to %#x\n", data_len, section_addr); - while (data_len) { - size_t payload_size; - uint32_t block_base; - int max_retries; - - /* prepare the header to prepend to the block. */ - payload_size = MIN(data_len, targ.common.maximum_pdu_size); - - block_base = htobe32(section_addr); - - struct update_frame_header ufh; - - ufh.block_size = htobe32(payload_size + - 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)) - break; - - if (!max_retries) { - fprintf(stderr, - "Failed to transfer block, %zd to go\n", - data_len); - exit(update_error); - } - data_len -= payload_size; - data_ptr += payload_size; - section_addr += payload_size; - } -} - -/* - * Each RO or RW section of the new image can be in one of the following - * 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. - */ -}; - -/* This array describes all sections of the new image. */ -static struct { - const char *name; - uint32_t offset; - uint32_t size; - enum upgrade_status ustatus; - char version[32]; - int32_t rollback; - uint32_t key_version; -} sections[] = { - {"RO"}, - {"RW"} -}; - -static const struct fmap_area *fmap_find_area_or_die(const struct fmap *fmap, - const char *name) -{ - const struct fmap_area *fmaparea; - - fmaparea = fmap_find_area(fmap, name); - if (!fmaparea) { - fprintf(stderr, "Cannot find FMAP area %s\n", name); - exit(update_error); - } - - return fmaparea; -} - -/* - * Scan the new image and retrieve versions of all sections. - */ -static void fetch_header_versions(const uint8_t *image, size_t len) -{ - const struct fmap *fmap; - const struct fmap_area *fmaparea; - long int offset; - size_t i; - - offset = fmap_find(image, len); - if (offset < 0) { - fprintf(stderr, "Cannot find FMAP in image\n"); - exit(update_error); - } - fmap = (const struct fmap *)(image+offset); - - /* FIXME: validate fmap struct more than this? */ - if (fmap->size != len) { - fprintf(stderr, "Mismatch between FMAP size and image size\n"); - exit(update_error); - } - - for (i = 0; i < ARRAY_SIZE(sections); i++) { - const char *fmap_name; - const char *fmap_fwid_name; - const char *fmap_rollback_name = NULL; - const char *fmap_key_name = NULL; - - if (!strcmp(sections[i].name, "RO")) { - fmap_name = "EC_RO"; - fmap_fwid_name = "RO_FRID"; - } else if (!strcmp(sections[i].name, "RW")) { - fmap_name = "EC_RW"; - fmap_fwid_name = "RW_FWID"; - fmap_rollback_name = "RW_RBVER"; - /* - * Key version comes from key RO (RW signature does not - * contain the key version. - */ - fmap_key_name = "KEY_RO"; - } else { - fprintf(stderr, "Invalid section name\n"); - exit(update_error); - } - - fmaparea = fmap_find_area_or_die(fmap, fmap_name); - - /* FIXME: endianness? */ - sections[i].offset = fmaparea->offset; - sections[i].size = fmaparea->size; - - fmaparea = fmap_find_area_or_die(fmap, fmap_fwid_name); - - if (fmaparea->size != sizeof(sections[i].version)) { - fprintf(stderr, "Invalid fwid size\n"); - exit(update_error); - } - 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, - sizeof(sections[i].rollback)); - } - - sections[i].key_version = -1; - if (fmap_key_name) { - fmaparea = fmap_find_area(fmap, fmap_key_name); - if (fmaparea) { - const struct vb21_packed_key *key = - (const void *)(image+fmaparea->offset); - sections[i].key_version = key->key_version; - } - } - } -} - -static int show_headers_versions(const void *image) -{ - size_t i; - - 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); - } - return 0; -} - -/* - * Pick sections to transfer based on information retrieved from the target, - * the new image, and the protocol version the target is running. - */ -static void pick_sections(struct transfer_descriptor *td) -{ - size_t i; - - for (i = 0; i < ARRAY_SIZE(sections); i++) { - uint32_t offset = sections[i].offset; - - /* Skip currently active section. */ - if (offset != td->offset) - continue; - - sections[i].ustatus = needed; - } -} - -static void setup_connection(struct transfer_descriptor *td) -{ - size_t rxed_size; - size_t i; - uint32_t error_code; - - /* - * Need to be backwards compatible, communicate with targets running - * different protocol versions. - */ - union { - struct first_response_pdu rpdu; - uint32_t legacy_resp; - } start_resp; - - /* Send start request. */ - printf("start\n"); - - struct update_frame_header ufh; - uint8_t inbuf[td->uep.chunk_len]; - 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)) { - 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); - - /* We got something. Check for errors in response */ - if (rxed_size < 8) { - fprintf(stderr, "Unexpected response size %zd: ", rxed_size); - for (i = 0; i < rxed_size; i++) - fprintf(stderr, " %02x", ((uint8_t *)&start_resp)[i]); - fprintf(stderr, "\n"); - exit(update_error); - } - - protocol_version = be16toh(start_resp.rpdu.protocol_version); - if (protocol_version < 5 || protocol_version > 6) { - fprintf(stderr, "Unsupported protocol version %d\n", - protocol_version); - exit(update_error); - } - - header_type = be16toh(start_resp.rpdu.header_type); - - printf("target running protocol version %d (type %d)\n", - protocol_version, header_type); - if (header_type != UPDATE_HEADER_TYPE_COMMON) { - fprintf(stderr, "Unsupported header type %d\n", - header_type); - exit(update_error); - } - - error_code = be32toh(start_resp.rpdu.return_value); - - if (error_code) { - fprintf(stderr, "Target reporting error %d\n", error_code); - shut_down(&td->uep); - exit(update_error); - } - - td->offset = be32toh(start_resp.rpdu.common.offset); - memcpy(targ.common.version, 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 = - be32toh(start_resp.rpdu.common.flash_protection); - targ.common.min_rollback = be32toh(start_resp.rpdu.common.min_rollback); - targ.common.key_version = be32toh(start_resp.rpdu.common.key_version); - - printf("maximum PDU size: %d\n", targ.common.maximum_pdu_size); - printf("Flash protection status: %04x\n", targ.common.flash_protection); - printf("version: %32s\n", targ.common.version); - printf("key_version: %d\n", targ.common.key_version); - printf("min_rollback: %d\n", targ.common.min_rollback); - printf("offset: writable at %#x\n", td->offset); - - pick_sections(td); -} - -/* - * Channel TPM extension/vendor command over USB. The payload of the USB frame - * in this case consists of the 2 byte subcommand code concatenated with the - * command body. The caller needs to indicate if a response is expected, and - * 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) -{ - 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; - - ufh = malloc(usb_msg_size); - if (!ufh) { - printf("%s: failed to allocate %zd bytes\n", - __func__, usb_msg_size); - return -1; - } - - ufh->block_size = htobe32(usb_msg_size); - ufh->cmd.block_digest = 0; - ufh->cmd.block_base = htobe32(UPDATE_EXTRA_CMD); - frame_ptr = (uint16_t *)(ufh + 1); - *frame_ptr = htobe16(subcommand); - - if (body_size) - memcpy(frame_ptr + 1, cmd_body, body_size); - - xfer(uep, ufh, usb_msg_size, resp, resp_size ? *resp_size : 0, - allow_less); - - free(ufh); - return 0; -} - -/* - * Indicate to the target that update image transfer has been completed. Upon - * receiveing of this message the target state machine transitions into the - * 'rx_idle' state. The host may send an extension command to reset the target - * after this. - */ -static void send_done(struct usb_endpoint *uep) -{ - uint32_t out; - - /* Send stop request, ignoring reply. */ - out = htobe32(UPDATE_DONE); - xfer(uep, &out, sizeof(out), &out, 1, 0); -} - -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) -{ - send_done(&td->uep); - - 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) -{ - 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); - num_txed_sections++; - } - - /* - * Move USB receiver sate machine to idle state so that vendor - * commands can be processed later, if any. - */ - send_done(&td->uep); - - if (!num_txed_sections) - printf("nothing to do\n"); - else - printf("-------\nupdate complete\n"); - return num_txed_sections; -} - -static void generate_reset_request(struct transfer_descriptor *td) -{ - size_t response_size; - uint8_t response; - uint16_t subcommand; - uint8_t command_body[2]; /* Max command body size. */ - size_t command_body_size; - - if (protocol_version < 6) { - /* - * Send a second stop request, which should reboot - * without replying. - */ - send_done(&td->uep); - /* Nothing we can do over /dev/tpm0 running versions below 6. */ - return; - } - - /* - * If the user explicitly wants it, request post reset instead of - * immediate reset. In this case next time the target reboots, the h1 - * will reboot as well, and will consider running the uploaded code. - * - * In case target RW version is 19 or above, to reset the target the - * host is supposed to send the command to enable the uploaded image - * disabled by default. - * - * Otherwise the immediate reset command would suffice. - */ - /* Most common case. */ - 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); - - printf("reboot not triggered\n"); -} - -static void get_random(uint8_t *data, int len) -{ - FILE *fp; - int i = 0; - - fp = fopen("/dev/random", "rb"); - if (!fp) { - perror("Can't open /dev/random"); - exit(update_error); - } - - while (i < len) { - int ret = fread(data+i, len-i, 1, fp); - - if (ret < 0) { - perror("fread"); - exit(update_error); - } - - i += ret; - } - - fclose(fp); -} - -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 */ - .tv_sec = 0, - .tv_nsec = 100l * 1000l * 1000l, - }; - - send_done(&td->uep); - - 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); - - while (1) { - response_size = 64; - ext_cmd_over_usb(&td->uep, - UPDATE_EXTRA_CMD_CONSOLE_READ_NEXT, - payload, sizeof(payload), - response, &response_size, 1); - if (response[0] == 0) - break; - /* make sure it's null-terminated. */ - response[response_size - 1] = 0; - printf("%s", (const char *)response); - } - nanosleep(&sleep_duration, NULL); - } -} - -int main(int argc, char *argv[]) -{ - struct transfer_descriptor td; - int errorcnt; - uint8_t *data = 0; - size_t data_len = 0; - uint16_t vid = VID, pid = PID; - char *serialno = NULL; - int i; - size_t j; - int transferred_sections = 0; - int binary_vers = 0; - int show_fw_ver = 0; - int no_reset_request = 0; - int touchpad_update = 0; - int extra_command = -1; - uint8_t extra_command_data[50]; - int extra_command_data_len = 0; - uint8_t extra_command_answer[64]; - int extra_command_answer_len = 1; - - progname = strrchr(argv[0], '/'); - if (progname) - progname++; - else - progname = argv[0]; - - /* Usb transfer - default mode. */ - memset(&td, 0, sizeof(td)); - - errorcnt = 0; - opterr = 0; /* quiet, you */ - while ((i = getopt_long(argc, argv, short_opts, long_opts, 0)) != -1) { - switch (i) { - case 'b': - binary_vers = 1; - break; - case 'd': - if (!parse_vidpid(optarg, &vid, &pid)) { - printf("Invalid argument: \"%s\"\n", optarg); - errorcnt++; - } - break; - case 'e': - get_random(extra_command_data, 32); - extra_command_data_len = 32; - extra_command = UPDATE_EXTRA_CMD_INJECT_ENTROPY; - break; - case 'f': - show_fw_ver = 1; - break; - case 'g': - extra_command = UPDATE_EXTRA_CMD_TOUCHPAD_DEBUG; - /* Maximum length. */ - extra_command_data_len = 50; - str2hex(optarg, - extra_command_data, &extra_command_data_len); - hexdump(extra_command_data, extra_command_data_len); - extra_command_answer_len = 64; - break; - case 'h': - usage(errorcnt); - break; - case 'j': - extra_command = UPDATE_EXTRA_CMD_JUMP_TO_RW; - break; - case 'l': - extra_command = UPDATE_EXTRA_CMD_CONSOLE_READ_INIT; - break; - case 'n': - no_reset_request = 1; - break; - case 'p': - 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]); - - break; - case 'r': - extra_command = UPDATE_EXTRA_CMD_IMMEDIATE_RESET; - break; - case 's': - extra_command = UPDATE_EXTRA_CMD_STAY_IN_RO; - break; - case 'S': - serialno = optarg; - break; - case 't': - extra_command = UPDATE_EXTRA_CMD_TOUCHPAD_INFO; - extra_command_answer_len = - sizeof(struct touchpad_info); - break; - case 'u': - extra_command = UPDATE_EXTRA_CMD_UNLOCK_ROLLBACK; - break; - case 'w': - extra_command = UPDATE_EXTRA_CMD_UNLOCK_RW; - break; - case 0: /* auto-handled option */ - break; - case '?': - if (optopt) - printf("Unrecognized option: -%c\n", optopt); - else - printf("Unrecognized option: %s\n", - argv[optind - 1]); - errorcnt++; - break; - case ':': - printf("Missing argument to %s\n", argv[optind - 1]); - errorcnt++; - break; - default: - printf("Internal error at %s:%d\n", __FILE__, __LINE__); - exit(update_error); - } - } - - if (errorcnt) - usage(errorcnt); - - if (!show_fw_ver && extra_command == -1 && !touchpad_update) { - if (optind >= argc) { - fprintf(stderr, - "\nERROR: Missing required <binary image>\n\n"); - usage(1); - } - - data = get_file_or_die(argv[optind], &data_len); - printf("read %zd(%#zx) bytes from %s\n", - data_len, data_len, argv[optind]); - - fetch_header_versions(data, data_len); - - if (binary_vers) - exit(show_headers_versions(data)); - } else { - if (optind < argc) - printf("Ignoring binary image %s\n", argv[optind]); - } - - usb_findit(vid, pid, serialno, &td.uep); - - setup_connection(&td); - - if (show_fw_ver) { - printf("Current versions:\n"); - printf("Writable %32s\n", targ.common.version); - } - - if (data) { - if (touchpad_update) { - transfer_section(&td, - data, - 0x80000000, - data_len, 0); - free(data); - - send_done(&td.uep); - } else { - transferred_sections = transfer_image(&td, - data, data_len); - free(data); - - if (transferred_sections && !no_reset_request) - generate_reset_request(&td); - } - } 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); - - 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: - hexdump(extra_command_answer, extra_command_answer_len); - break; - } - } - - libusb_close(td.uep.devh); - libusb_exit(NULL); - - if (!transferred_sections) - return noop; - /* - * We should indicate if RO update was not done because of the - * insufficient RW version. - */ - for (j = 0; j < ARRAY_SIZE(sections); j++) - if (sections[j].ustatus == not_possible) { - /* This will allow scripting repeat attempts. */ - printf("Failed to update RO, run the command again\n"); - return rw_updated; - } - - printf("image updated\n"); - return all_updated; -} |