| #!/usr/bin/python |
| # Copyright (c) 2010 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. |
| |
| '''Test utility for verifying ChromeOS firmware.''' |
| |
| import datetime |
| import functools |
| import getopt |
| import os |
| import re |
| import shutil |
| import sys |
| import tempfile |
| import traceback |
| |
| import cgpt_handler |
| import cgpt_state |
| import chromeos_interface |
| import flashrom_handler |
| import kernel_handler |
| import saft_flashrom_util |
| import tpm_handler |
| |
| # |
| # We need to know the names of two files: |
| # |
| # - the SAFT shell script used by the upstart script (/etc/init/saft.conf) to |
| # determine if SAFT is in progress |
| # |
| # - the name of the log file where the upstart script redirects its output. |
| # |
| # These file names are defined in the upstart script as <NAME>=<VALUE>, one |
| # per line, using names below: |
| |
| # SAFT shell script |
| SAFT_SCRIPT_VAR_NAME = 'SAFT_SCRIPT' |
| |
| # Upstart log file |
| CONF_LOG_VAR_NAME = 'SAFT_LOG' |
| |
| # This is a template used to populate the SAFT shell script which |
| # /etc/init/saft.conf is looking for. |
| SAFT_SCRIPT_TEMPLATE = ''' |
| #!/bin/sh |
| # Copyright (c) 2010 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. |
| |
| # Do not edit, this file was autogenerated. |
| |
| # The only parameter is the name of the Flash block device. The root main root |
| # file system is presumed to be /dev/<flash_block_dev>3. |
| # |
| # TODO(vbendeb): instead of using the hardcoded device name look for block |
| # labeled C-KEYFOB |
| |
| set -e |
| |
| saft_dev="/dev/${1}3" |
| root_dev=$(rootdev -s) |
| |
| if [ "${saft_dev}" = "${root_dev}" ]; then |
| mount_point="" |
| else |
| mount_point='/tmp/saft_device' |
| mkdir "${mount_point}" |
| mount "${saft_dev}" "${mount_point}" |
| fi |
| |
| cd "${mount_point}%s" |
| |
| # Start our own X server to make it easier to control the screen |
| X_PORT=1 |
| X :"${X_PORT}" -config saft.xorg.conf vt02 > /dev/null 2>&1 & |
| xpid="$!" |
| initctl stop update-engine # Prevent messing with cgpt attributes |
| export DISPLAY=":${X_PORT}.0" |
| ./%s --next_step |
| kill "${xpid}" |
| ''' |
| |
| # Subdirectory to keep the state of this test over reboots. Created in the |
| # /var on the USB flash drive the test is running from. |
| STATE_SUBDIR = '.fw_test' |
| |
| # Files storing SAFT state over reboots, located in state_dir defined below. |
| LOG_FILE = 'fw_test_log.txt' # Test log. |
| STEP_FILE = 'fw_test_step' # The next test step number. |
| FW_BACKUP_FILE = 'flashrom.bak' # Preserved original flashrom contents. |
| FW_COPY_FILE = 'flashrom.new' # A copy of the flashrom contents being tested. |
| FWID_BACKUP_FILE = 'fwid.bak' # FWID reported by the original firmware. |
| FWID_NEW_FILE = 'fwid.new' # FWID reported by the firmware being tested. |
| |
| # The list of shell executables necessary for this program to work. |
| REQUIRED_PROGRAMS = ''' |
| cgpt crossystem blkid flashrom readlink rootdev vbutil_firmware vbutil_kernel |
| mosys |
| ''' |
| |
| |
| FLASHROM_HANDLER = flashrom_handler.FlashromHandler() |
| CHROS_IF = chromeos_interface.ChromeOSInterface(__name__ != '__main__') |
| BASE_STORAGE_DEVICE = CHROS_IF.get_root_dev() |
| cgpt_st = cgpt_state.CgptState('COMPLETE', CHROS_IF, BASE_STORAGE_DEVICE) |
| |
| def allow_multiple_section_input(image_operator): |
| @functools.wraps(image_operator) |
| def wrapper(self, section): |
| if type(section) is not tuple: |
| image_operator(self, section) |
| else: |
| for sec in section: |
| image_operator(self, sec) |
| return wrapper |
| |
| class FwError(Exception): |
| '''A class to encapsulate this module specific exceptions.''' |
| pass |
| |
| |
| def RetriveSaftConfDefinion(name): |
| '''Find a name definition in the SAFT upstart script. |
| |
| This function scans the /etc/init/saft.conf for <name>=<value> lines. and |
| if found - returns the <value> string. |
| |
| It will throw an IOError exception in case the upstart script is not |
| present or FwError() in case <name> definition is not in the file. |
| ''' |
| pattern = name + '=' |
| conf_file = '/etc/init/saft.conf' |
| |
| for line in open(conf_file, 'r').readlines(): |
| line = line.strip() |
| if not line.startswith(pattern): |
| continue |
| pieces = line.split('=') |
| if len(pieces) != 2 or pieces[0] != name: |
| continue |
| return pieces[1].strip('\'"') |
| |
| # If we did not find it - something is very wrong. |
| raise FwError('name %s not found in %s' % (name, conf_file)) |
| |
| |
| class FirmwareTest(object): |
| '''An object to represent a Firmware Semi Automated test (SAFT). |
| |
| It follows the steps as enumerated in test_state_sequence below, |
| restarting the target after each step. The first time around the |
| init_fw_test() method must be called to set up the persistent environment. |
| All following invocations should initialize the object (using .init()) and |
| the invoke the next_step() method. |
| ''' |
| |
| def __init__(self): |
| '''Object initializer, does nothing to make mocking easier.''' |
| self.mydir = None |
| self.base_partition = None |
| self.chros_if = None |
| self.progname = None |
| self.test_state_sequence = None |
| self.kern_handler = None |
| self.step_failed = False |
| self.window = None |
| self.tpm_handler = None |
| self.root_dev = None |
| |
| def _verify_fw_id(self, compare_to_file): |
| '''Verify if the current firmware ID matches the contents a file. |
| |
| compare_to_file - a string, name of the file in the state directory. |
| ''' |
| old_fwid = open( |
| self.chros_if.state_dir_file(compare_to_file), 'r').read() |
| now_fwid = self.chros_if.cs.fwid |
| return old_fwid == now_fwid |
| |
| def _get_step(self): |
| '''Set the current value of SAFT step number.''' |
| step_file = self.chros_if.state_dir_file(STEP_FILE) |
| step = open(step_file, 'r').read().strip() |
| return int(step) |
| |
| def _set_step(self, step): |
| '''Set the SAFT step number to control the next test pass.''' |
| step_file = self.chros_if.state_dir_file(STEP_FILE) |
| open(step_file, 'w').write('%d' % step) |
| |
| def _handle_saft_script(self, install): |
| '''Install or remove the SAFT shell script. |
| |
| On SAFT initialization, this function is invoked to install the shell |
| script which the upstart script /etc/init/saft.conf will be passing |
| control to after each restart. The presence of the shell script is an |
| indication that SAFT is in progress. |
| |
| The shell script is expected to be installed in the stateful partition |
| of the flash drive. When invoked, the shell script completes setting up |
| the environment and in turn invokes this program passing it --next_step |
| as the command line parameter. |
| |
| When SAFT finishes all steps, this function is called to remove the |
| shell script so that next time the traget restarts it does not try to |
| execute SAFT anymore. |
| ''' |
| |
| if not self.chros_if.target_hosted(): |
| print 'bypassing upstart management' |
| return |
| |
| label_pattern = re.compile('/dev/(.+): LABEL="C-STATE"') |
| tmp_dir = tempfile.mkdtemp() |
| |
| # Find out our file name off the root of the device |
| mount_point = (self.chros_if.run_shell_command_get_output( |
| 'df %s' % self.mydir)[-1]).split()[-1] |
| this_dir = self.mydir[len(mount_point):] |
| |
| for line in self.chros_if.run_shell_command_get_output('blkid'): |
| match = label_pattern.search(line) |
| if not match: |
| continue |
| dev = match.groups(0)[0] |
| mount_point = self.chros_if.get_writeable_mount_point( |
| '/dev/' + dev, tmp_dir) |
| script_name = RetriveSaftConfDefinion(SAFT_SCRIPT_VAR_NAME) |
| |
| # Find location of the script in the currently mounted file |
| # system. |
| file_name = os.path.join(mount_point, script_name.lstrip('/')) |
| self.chros_if.log('Handling ' + file_name) |
| if install: |
| saft_script = open(file_name, 'w') |
| saft_script.write(SAFT_SCRIPT_TEMPLATE % ( |
| this_dir, self.progname)) |
| saft_script.close() |
| os.chmod(file_name, 0700) |
| else: |
| os.unlink(file_name) |
| if mount_point.startswith(tmp_dir): |
| self.chros_if.run_shell_command('umount %s' % tmp_dir) |
| |
| os.rmdir(tmp_dir) |
| |
| def _check_runtime_env(self): |
| '''Ensure that the script is running in proper environment. |
| |
| This involves checking that the script is running off a removable |
| device, configuring proper file names for logging, etc. |
| ''' |
| line = self.chros_if.run_shell_command_get_output( |
| 'df %s' % self.mydir)[1] |
| |
| self.base_partition = line.split()[0] |
| if self.base_partition == '/dev/root': |
| self.base_partition = self.chros_if.get_root_part() |
| |
| if not self.chros_if.is_removable_device(self.base_partition): |
| raise FwError( |
| 'This test must run off a removable device, not %s' |
| % self.base_partition) |
| |
| env_root = '/var' |
| state_fs = '%s1' % self.base_partition[:-1] |
| |
| # is state file system mounted? |
| for line in self.chros_if.run_shell_command_get_output('mount'): |
| if line.startswith('%s on ' % state_fs): |
| mount_point = line.split()[2] |
| state_root = mount_point + env_root |
| break |
| else: |
| tmp_dir = tempfile.mkdtemp() |
| self.chros_if.run_shell_command('mount %s %s' % (state_fs, tmp_dir)) |
| state_root = '%s%s' % (tmp_dir, env_root) |
| |
| self.chros_if.init(os.path.join(state_root, STATE_SUBDIR), LOG_FILE) |
| |
| |
| def init(self, progname, chros_if, |
| kern_handler=None, state_sequence=None, tpm_handler=None): |
| '''Initialize the Firmware self test instance. |
| |
| progname - a string, name of this program as it was invoked. |
| |
| chros_if - an object of ChromeOSInterface type to be initialized and |
| used by this instance of FirmwareTest. |
| |
| kern_handler - an object providing SAFT with services manipulating |
| kernel images. Unittest does not provide this object. |
| |
| test_state_sequence - a tuple of three-tuples driving test execution, |
| see description below. Unittest does not provide this |
| sequence. |
| ''' |
| real_name = os.path.realpath(progname) |
| self.mydir = os.path.dirname(real_name) |
| self.progname = os.path.basename(real_name) |
| self.chros_if = chros_if |
| self._check_runtime_env() |
| self.test_state_sequence = state_sequence |
| if kern_handler: |
| self.kern_handler = kern_handler |
| self.kern_handler.init(self.chros_if) |
| if tpm_handler: |
| self.tpm_handler = tpm_handler |
| self.tpm_handler.init(self.chros_if) |
| |
| if self.tpm_handler: |
| if not self.chros_if.is_removable_device(self.base_partition): |
| # On each non USB flash rooted start confirm that kernel and |
| # BIOS versions and TPM setting match. |
| vers_a = FLASHROM_HANDLER.get_version('a') |
| vers_b = FLASHROM_HANDLER.get_version('b') |
| if not self.tpm_handler.fw_version_good(vers_a, vers_b): |
| raise FwError('TPM firmware version mismatch (%d %d %s)' % ( |
| vers_a, vers_b, self.base_partition)) |
| if self.kern_handler: |
| vers_a = self.kern_handler.get_version('a') |
| vers_b = self.kern_handler.get_version('b') |
| if not self.tpm_handler.kernel_version_good(vers_a, vers_b): |
| raise FwError('TPM kernel version mismatch (%d %d %s)' % ( |
| vers_a, vers_b, self.base_partition)) |
| |
| def set_try_fw_b(self): |
| '''Request running firmware B on the next restart.''' |
| self.chros_if.log('Requesting restart with FW B') |
| self.chros_if.cs.fwb_tries = 1 |
| |
| def request_recovery_boot(self): |
| '''Request running in recovery mode on the restart.''' |
| self.chros_if.log('Requesting restart in recovery mode') |
| self.chros_if.cs.request_recovery() |
| |
| |
| @allow_multiple_section_input |
| def restore_firmware(self, section): |
| '''Restore the previously corrupted firmware section signature.''' |
| self.chros_if.log('Restoring firmware %s' % section) |
| FLASHROM_HANDLER.restore_firmware(section) |
| |
| @allow_multiple_section_input |
| def corrupt_firmware(self, section): |
| '''Corrupt the requested firmware section signature.''' |
| self.chros_if.log('Corrupting firmware %s' % section) |
| FLASHROM_HANDLER.corrupt_firmware(section) |
| |
| @allow_multiple_section_input |
| def corrupt_kernel(self, section): |
| '''Corrupt the requested kernel section.''' |
| self.chros_if.log('Corrupting kernel %s' % section) |
| self.kern_handler.corrupt_kernel(section) |
| |
| @allow_multiple_section_input |
| def restore_kernel(self, section): |
| '''restore the requested kernel section.''' |
| self.chros_if.log('restoring kernel %s' % section) |
| self.kern_handler.restore_kernel(section) |
| |
| def cgpt_test_start(self): |
| '''Set up CGPT test sequence.''' |
| cgpth = cgpt_handler.CgptHandler(self.chros_if) |
| cgpth.read_device_info(BASE_STORAGE_DEVICE) |
| |
| # Set gpt attributes for kernels A and B such that after reset it comes |
| # back up using kernel A, with tries A set to 14 (decremented) and |
| # other A and B attributes unchanged. |
| properties = {'successful': 0, |
| 'tries': 15, |
| 'priority': 10, |
| } |
| cgpth.set_partition(BASE_STORAGE_DEVICE, 'KERN-A', properties) |
| properties['priority'] = 9 |
| cgpth.set_partition(BASE_STORAGE_DEVICE, 'KERN-B', properties) |
| |
| def cgpt_test_1(self): |
| '''CGPT test checking the attributes settings by the firmware.''' |
| expected_cgpt = { |
| 'KERN-A' : {'priority': 10, 'tries': 14, 'successful': 0}, |
| 'KERN-B' : {'priority': 9, 'tries': 15, 'successful': 0} |
| } |
| cgpth = cgpt_handler.CgptHandler(self.chros_if) |
| cgpth.read_device_info(BASE_STORAGE_DEVICE) |
| for part, state in expected_cgpt.iteritems(): |
| props = cgpth.get_partition(BASE_STORAGE_DEVICE, part) |
| for prop, value in state.iteritems(): |
| if value != props[prop]: |
| self.chros_if.log('wrong partition %s value' % part) |
| self.chros_if.log(cgpth.dump_partition(BASE_STORAGE_DEVICE, |
| part)) |
| self.step_failed = True |
| break |
| |
| def move_kernel_backward(self, section): |
| '''Decrement kernel version for the requested section.''' |
| new_version = self.kern_handler.get_version(section) - 1 |
| self.chros_if.log( |
| 'setting kernel section %s version to %d' % (section, new_version)) |
| self.kern_handler.set_version(section, new_version) |
| |
| def move_firmware_backward(self, section): |
| '''Decrement firmware version for the requested section.''' |
| new_version = FLASHROM_HANDLER.get_section_version(section) - 1 |
| flags = FLASHROM_HANDLER.get_section_flags(section) |
| self.chros_if.log( |
| 'setting firmware section %s version to %d' % ( |
| section, new_version)) |
| FLASHROM_HANDLER.set_section_version(section, new_version, flags, |
| write_through=True) |
| |
| def jump_kernels_forward(self): |
| '''Add two to both kernels' versions. |
| |
| This compensates for the previous decrement and increases the version |
| number by one as compared to the original state (at the start of the |
| test). |
| ''' |
| for section in ('a', 'b'): |
| new_version = self.kern_handler.get_version(section) + 2 |
| self.chros_if.log( |
| 'setting section %s version to %d' % (section, new_version)) |
| self.kern_handler.set_version(section, new_version) |
| |
| def jump_firmwares_forward(self): |
| '''Add two to both firmwares' versions. |
| |
| This compensates for the previous decrement and increases the version |
| number by one as compared to the original state (at the start of the |
| test). |
| ''' |
| for section in ('a', 'b'): |
| new_version = FLASHROM_HANDLER.get_section_version(section) + 2 |
| flags = FLASHROM_HANDLER.get_section_flags(section) |
| self.chros_if.log( |
| 'setting firmware section %s version to %d' % ( |
| section, new_version)) |
| FLASHROM_HANDLER.set_section_version(section, new_version, flags, |
| write_through=True) |
| |
| def terminate_tpm_tests(self): |
| '''Restore TPM and kernel states. |
| |
| Move kernel and firmware versions to where they were before SAFT |
| started. Set the TPM's values of kernel and firmware versions to the |
| lowest of the two kernels/firmwares available on the system. Prevent |
| future TPM write accesses by restoring the upstart file. |
| ''' |
| new_tpm_kern_version = 0xffff |
| new_tpm_fw_version = 0xffff |
| for section in ('a', 'b'): |
| new_version = self.kern_handler.get_version(section) - 1 |
| self.chros_if.log( |
| 'setting kernel section %s version to %d' % ( |
| section, new_version)) |
| self.kern_handler.set_version(section, new_version) |
| new_tpm_kern_version = min(new_version, new_tpm_kern_version) |
| |
| new_version = FLASHROM_HANDLER.get_section_version(section) - 1 |
| flags = FLASHROM_HANDLER.get_section_flags(section) |
| self.chros_if.log( |
| 'setting firmware section %s version to %d' % ( |
| section, new_version)) |
| FLASHROM_HANDLER.set_section_version(section, new_version, flags, |
| write_throught=True) |
| new_tpm_fw_version = min(new_version, new_tpm_fw_version) |
| |
| self.tpm_handler.set_kernel_version(new_tpm_kern_version) |
| self.tpm_handler.set_fw_version(new_tpm_fw_version) |
| self.tpm_handler.disable_write_access() |
| |
| def revert_firmware(self): |
| '''Restore firmware to the image backed up when SAFT started.''' |
| self.chros_if.log('restoring original firmware image') |
| self.chros_if.run_shell_command( |
| 'flashrom -w %s' % self.chros_if.state_dir_file(FW_BACKUP_FILE)) |
| |
| def new_fw_image(self, image_file = None): |
| FLASHROM_HANDLER.new_image(image_file) |
| FLASHROM_HANDLER.verify_image() |
| if self.tpm_handler: |
| vers_a = FLASHROM_HANDLER.get_section_version('a') |
| vers_b = FLASHROM_HANDLER.get_section_version('b') |
| if not self.tpm_handler.fw_version_good(vers_a, vers_b): |
| raise FwError('TPM firmware version mismatch') |
| |
| def prepare_tpm_tests(self): |
| '''Prepare TPM for testing. |
| |
| Enable write access on the next reboot, and roll back the kernel we |
| are running now (the other kernel is expected to be used on the next |
| restart). |
| ''' |
| mount_point = self.chros_if.run_shell_command_get_output( |
| 'df %s' % self.mydir)[-1].split()[-1] |
| cfg_file = os.path.join(mount_point, 'etc/init/tcsd.conf') |
| self.tpm_handler.enable_write_access(cfg_file) |
| self.move_kernel_backward('a') |
| |
| def init_fw_test(self, opt_dictionary, chros_if): |
| '''Prepare firmware test context. |
| |
| This function tries creating the state directory for the fw test and |
| initializes the test state machine. |
| |
| Return |
| True on success |
| False on any failure or if the directory already exists |
| ''' |
| chros_if.init_environment() |
| chros_if.log('Automated firmware test log generated on %s' % ( |
| datetime.datetime.strftime( |
| datetime.datetime.now(), '%b %d %Y'))) |
| chros_if.log('Original boot state %s' % chros_if.boot_state_vector()) |
| self.chros_if = chros_if |
| fw_image = opt_dictionary['image_file'] |
| self.new_fw_image() |
| FLASHROM_HANDLER.dump_whole( |
| self.chros_if.state_dir_file(FW_BACKUP_FILE)) |
| self.new_fw_image(fw_image) |
| self._handle_saft_script(True) |
| open(self.chros_if.state_dir_file(FWID_BACKUP_FILE), 'w' |
| ).write(self.chros_if.cs.fwid) |
| shutil.copyfile(fw_image, self.chros_if.state_dir_file(FW_COPY_FILE)) |
| |
| self._set_step(0) |
| cgpt_st.set_step(0) |
| |
| def next_step(self): |
| '''Function to execute a single SAFT step. |
| |
| This function is running after each reboot. It determines the current |
| step the SAFT is on, executes the appropriate action, increments the |
| step value and then restats the machine. |
| ''' |
| |
| this_step = self._get_step() |
| self.chros_if.log('\nStarting step %d' % this_step) |
| |
| # Import when needed, because otherwise running test generates a |
| # warning exception about the unavailable display. |
| window = __import__('window') |
| self.window = window.GraphThread() |
| |
| if this_step == 0: |
| open(self.chros_if.state_dir_file(FWID_NEW_FILE), 'w' |
| ).write(self.chros_if.cs.fwid) |
| |
| if self._verify_fw_id(FWID_BACKUP_FILE): |
| # we expected FWID to change, but it did not - have the firmware |
| # been even replaced? |
| self.chros_if.log('New firmware - old FWID') |
| self.finish_saft(False) |
| test_state_tuple = self.test_state_sequence[this_step] |
| expected_vector = test_state_tuple[0] |
| action = test_state_tuple[1] |
| |
| # Display cgpt sub step if the action is a cgpt test |
| step_display = "SAFT's performing step #%d" |
| step_tuple = (this_step,) |
| if action == cgpt_st.test_loop: |
| step_display += " (cgpt test step #%d)" |
| step_tuple += (cgpt_st.get_step(),) |
| self.window.display_text(step_display % step_tuple) |
| |
| boot_vector = self.chros_if.boot_state_vector() |
| self.chros_if.log('Rebooted into state %s' % boot_vector) |
| |
| conf_log_file = RetriveSaftConfDefinion(CONF_LOG_VAR_NAME) |
| |
| if os.path.exists(conf_log_file): |
| contents = open(conf_log_file).read().rstrip() |
| if len(contents): |
| self.chros_if.log('startup log contents:') |
| self.chros_if.log(contents) |
| |
| if not self.chros_if.cmp_boot_vector(boot_vector, expected_vector): |
| self.chros_if.log('Error: Wrong boot vector, %s was expected' |
| % expected_vector) |
| self.finish_saft(False) |
| if this_step == (len(self.test_state_sequence) - 1): |
| self.finish_saft(True) |
| if action and not self._verify_fw_id(FWID_NEW_FILE): |
| self.chros_if.log('Error: Wrong FWID value') |
| self.finish_saft(False) |
| |
| inc_step = 1 |
| if action: |
| FLASHROM_HANDLER.new_image() |
| if action == cgpt_st.test_loop: |
| inc_step = action() |
| elif len(test_state_tuple) > 2: |
| self.chros_if.log('calling %s with parameter %s' % ( |
| str(action), str(test_state_tuple[2]))) |
| action(test_state_tuple[2]) |
| else: |
| self.chros_if.log('calling %s' % str(action)) |
| action() |
| if self.step_failed: |
| self.finish_saft(False) |
| |
| self._set_step(this_step + inc_step) |
| self.chros_if.run_shell_command('reboot') |
| |
| def finish_saft(self, success): |
| '''SAFT completed, restore the environment and expose the results. |
| |
| Delete the startup script from the removable device, copy the log to |
| '/var' directory on the current drive and exit. |
| |
| Input: |
| success - a Boolean set to True if the test has passed through all |
| steps. Affects placing of the last line 'we are done' in the log. |
| ''' |
| |
| if success and not self._verify_fw_id(FWID_BACKUP_FILE): |
| self.chros_if.log( |
| 'Error: Failed to restore to original firmware') |
| success = False |
| self.chros_if.log('Removing upstart scripts') |
| self._handle_saft_script(False) |
| if success: |
| self.chros_if.log('we are done!') |
| # Have chros_if.shutdown move the log into the '/var' directory to |
| # make it easier to see SAFT results. |
| self.chros_if.shut_down(os.path.join('/var', LOG_FILE)) |
| |
| self.window.stop() |
| sys.exit(0) |
| |
| # Firmware self test instance controlling this module. |
| FST = FirmwareTest() |
| |
| # This is a tuple of tuples controlling the SAFT state machine. The states are |
| # expected to be passed strictly in order. The states used to be identified by |
| # the contents of BINF.[012] files in the sys fs ACPI directory. Now they are |
| # derived from the crossystem output to match previously reported states. |
| # |
| # The first element of each component tuple is the expected state of the |
| # machine. |
| # |
| # The second element of the component tuples is the action to take to |
| # advance the test. The action is a function to call. The last line has |
| # action set to None, which indicates to the state machine that the test |
| # is over. |
| # |
| # The third component, if present, is the parameter to pass to the action |
| # function. |
| |
| TEST_STATE_SEQUENCE = ( |
| ('1:1:1:0:3', FST.set_try_fw_b), # Step 0 |
| ('1:2:1:0:3', None), |
| ('1:1:1:0:3', FST.corrupt_firmware, 'a'), |
| ('1:2:1:0:3', FST.restore_firmware, 'a'), |
| ('1:1:1:0:3', FST.corrupt_firmware, ('a', 'b')), |
| ('5:0:1:1:3', FST.restore_firmware, ('a', 'b')), # Step 5 |
| ('1:1:1:0:3', FST.corrupt_kernel, 'a'), |
| ('1:1:1:0:5', FST.corrupt_kernel, 'b'), |
| ('6:0:1:1:3', FST.restore_kernel, ('a', 'b')), |
| ('1:1:1:0:3', FST.request_recovery_boot), |
| ('8:0:1:1:3', FST.prepare_tpm_tests), # Step 10 |
| ('1:1:1:0:5', FST.move_kernel_backward, 'b'), |
| ('6:0:1:1:3', FST.jump_kernels_forward), |
| ('1:1:1:0:3', FST.move_firmware_backward, 'a'), |
| ('1:2:1:0:3', FST.move_firmware_backward, 'b'), |
| ('5:0:1:1:3', FST.jump_firmwares_forward), # Step 15 |
| ('1:1:1:0:3', FST.request_recovery_boot), |
| ('8:0:1:1:3', FST.terminate_tpm_tests), |
| ('*:*:*:*:*', cgpt_st.test_loop), |
| ('1:1:1:0:3', FST.revert_firmware), |
| ('1:1:1:0:3', None), # Step 20 |
| ) |
| |
| |
| # The string below serves two purposes: |
| # |
| # - spell out the usage string for this program |
| # |
| # - provide text for parsing to derive command line flags. The text is split |
| # to words, then the words which have -- in them are stripped off the |
| # leading/traling brackets and dashes and used to prepare inut parameters |
| # for getopt. This is the only way to pass the parameters to getopt, this |
| # enforces that each accepted parameter is mentioned in the usage() output. |
| USAGE_STRING = ''' |
| [--image_file=<firmware_image_file>] [--pub_key=<file>] [--next_step] |
| |
| The program can be invoked in two modes. |
| |
| When invoked for the first time, most of the parameters are required to set |
| up the test context and start it. |
| |
| Specifying --next_step means that the program is being invoked by the |
| restarted system, all the context is expected to be available. No other |
| parameters are required or expected in that case. |
| ''' |
| |
| |
| def usage(msg='', retv=0): |
| '''Print error message (if any), usage string and exit. |
| |
| Depending on the passed in return value use stdout (if retv is 0) or |
| stderr (if otherwise). |
| ''' |
| progname = os.path.basename(sys.argv[0]) |
| if retv: |
| ofile = sys.stderr |
| else: |
| ofile = sys.stdout |
| if msg: |
| print >> ofile, '%s: %s' % (progname, msg) |
| CHROS_IF.log(msg) |
| print >> ofile, 'usage: %s %s' % (progname, USAGE_STRING.strip()) |
| sys.exit(retv) |
| |
| |
| def get_options_set(): |
| '''Generate the list of command line options accepted by this program. |
| |
| This function derives the set of accepted command line options from |
| usage_string. The string is split into words, all words starting with -- are |
| considered accepted command line parameters. The presence of an '=' sign in |
| the word,means that the command line parameter requires a value. |
| |
| Returns a list of strings suitable for use by the getopt module. |
| ''' |
| |
| drop_tail = re.compile('=.*$') |
| option_set = [] |
| items = (' '.join(USAGE_STRING.split('\n'))).split(' ') |
| for item in items: |
| if '--' in item: |
| if item.startswith("'"): |
| continue |
| option = drop_tail.sub('=', item.strip('-[]')) |
| if option not in option_set: |
| option_set.append(option) |
| return option_set |
| |
| |
| |
| def main(argv): |
| '''Process command line options and invoke the proper test entry point.''' |
| (opts, params) = getopt.gnu_getopt(argv[1:], '', get_options_set()) |
| if params: |
| raise FwError('unrecognized parameters: %s' % ' '.join(params)) |
| |
| opt_dictionary = {} |
| for (name, value) in opts: |
| opt_dictionary[name.lstrip('-')] = value |
| |
| FST.init(argv[0], CHROS_IF, |
| kernel_handler.KernelHandler(), TEST_STATE_SEQUENCE, |
| tpm_handler.TpmHandler()) |
| |
| FLASHROM_HANDLER.init(saft_flashrom_util, CHROS_IF, |
| opt_dictionary.get('pub_key')) |
| if 'next_step' in opt_dictionary: |
| if len(opt_dictionary) != 1: |
| usage('--next_step (when specified) must be the only parameter', 1) |
| try: |
| FST.next_step() |
| except SystemExit: |
| pass # No real exception, this is just an exit (for whatever |
| # reason), gtk window already closed. |
| except: |
| # Whatever error that might be, gtk window must be shut. |
| if FST.window: |
| FST.window.stop() |
| |
| # Make sure exception information is saved in the log. |
| exc_type, exc_info, exc_trace = sys.exc_info() |
| print 'exception type:', str(exc_type) |
| print 'exception info:', str(exc_info) |
| traceback.print_tb(exc_trace) |
| sys.exit(0) |
| |
| # check if all executables are available |
| missing_execs = [] |
| for prog in REQUIRED_PROGRAMS.split(): |
| if not prog: |
| continue # ignore line breaks |
| if not CHROS_IF.exec_exists(prog): |
| missing_execs.append(prog) |
| if missing_execs: |
| usage('Program(s) %s not found in PATH' % ' '.join(missing_execs), 1) |
| |
| if 'image_file' not in opt_dictionary: |
| usage(retv=1) |
| |
| FST.init_fw_test(opt_dictionary, CHROS_IF) |
| |
| if not FLASHROM_HANDLER.firmware_sections_equal(): |
| # This is a temporary measure needed to address the fact that the |
| # development BIOS image is built with 'normal' firmware in section B |
| # and 'development' firmware in section A. SAFT operation requires |
| # 'normal' flavor in both sections. Note that this fix does not affect |
| # the file of the new image, only the flashrom contents. |
| CHROS_IF.log('modify firmware A to match B') |
| FLASHROM_HANDLER.copy_from_to('b', 'a') |
| CHROS_IF.log('program the new image') |
| FLASHROM_HANDLER.write_whole() |
| CHROS_IF.log('restart') |
| CHROS_IF.run_shell_command('reboot') |
| return 0 |
| |
| |
| if __name__ == '__main__': |
| try: |
| main(sys.argv) |
| except (getopt.GetoptError, ImportError): |
| usage(sys.exc_info()[1], 1) |
| except (FwError, flashrom_handler.FlashromHandlerError): |
| MSG = 'Error: %s' % str(sys.exc_info()[1]) |
| print MSG |
| CHROS_IF.log(MSG) |
| sys.exit(1) |
| |
| sys.exit(0) |