| #!/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. |
| |
| """ |
| gooftool: Google Factory Tool, providing all Google Required Test |
| functionality. |
| """ |
| |
| import copy |
| import glob |
| import optparse |
| import os |
| import sys |
| |
| ######################################################################## |
| # cros/gft modules |
| |
| import flashrom_util |
| import gft_common |
| import gft_fwhash |
| import gft_hwcomp |
| import gft_report |
| import gft_upload |
| import gft_wpfw |
| import gpio |
| |
| from gft_common import WarningMsg, VerboseMsg, DebugMsg, ErrorMsg, ErrorDie |
| |
| #######################################################################r |
| # global variables and cached objects |
| |
| g_options = None |
| g_args = None |
| |
| # cache for instance of HardwareComponents to speed up |
| g_hwcomp = None |
| |
| ######################################################################## |
| # factory utilities |
| |
| |
| def InitializeHardwareComponents(do_probe=True): |
| """ Initializes (and try to reuse the instance) a hardware components |
| detection object. |
| Returns the initialized (or cached) HardwareComponents object. |
| """ |
| |
| global g_hwcomp |
| if g_hwcomp: |
| if do_probe: |
| g_hwcomp.initialize() |
| return g_hwcomp |
| g_hwcomp = gft_hwcomp.HardwareComponents(verbose=g_options.verbose) |
| if do_probe: |
| g_hwcomp.initialize() |
| return g_hwcomp |
| |
| |
| def FindComponentsDatabases(db_path_pattern): |
| """ Finds files by pattern for component list databases. """ |
| files = glob.glob(db_path_pattern) |
| return files |
| |
| |
| def VerifySwitch(name, value): |
| """ Verifies if hardware switches are in correct states. """ |
| my_gpio = gpio.Gpio() |
| my_gpio.setup() |
| try: |
| DebugMsg('reading GPIO %s (expected %s)' % (name, value)) |
| if value != my_gpio.read(name): |
| VerboseMsg('VerifySwitch: %s is not in proper state.' % name) |
| return False |
| except: |
| ErrorDie('VerifySwitch: cannot read: ' + name) |
| return True |
| |
| |
| def MatchComponentsDatabases(db_path): |
| """Tries to match current system to known qualified component lists. |
| |
| Args: |
| db_path: path (or pattern) to the component list database files. |
| |
| Returns: |
| A list of matched component lists in dict form: { db_file: matched_list }. |
| """ |
| if not db_path: |
| ErrorDie('VerifyComponents: need --db_path.') |
| db_list = FindComponentsDatabases(db_path) |
| hwcomp = InitializeHardwareComponents() |
| if not hwcomp: |
| ErrorDie('VerifyComponents: failed to detect system configuration.') |
| matched_list = {} |
| for db in db_list: |
| VerboseMsg('MatchComponentsDatabases: Matching current system by %s' % db) |
| (matched, failure) = hwcomp.match_current_system(db) |
| if failure: |
| DebugMsg('Unmatched for %s:%s\n' % (db, hwcomp.pformat(failure))) |
| else: |
| matched_list[db] = matched |
| VerboseMsg('Matched: %s' % db) |
| return matched_list |
| |
| |
| def EnableWriteProtect(target, image): |
| """ Enables and verifies firmware write protection. """ |
| return gft_wpfw.EnableWriteProtect(target, image) |
| |
| |
| def WriteGBB(db_file): |
| """ Writes a prepared GBB data to system main firwmare. """ |
| flashrom = flashrom_util.flashrom_util(verbose_msg=VerboseMsg, |
| exception_type=gft_common.GFTError) |
| flashrom.select_target('bios') |
| image_file = gft_common.GetTemporaryFileName('wgbb') |
| if not flashrom.read_whole_to_file(image_file): |
| ErrorDie('WriteGBB: failed to read current firmware.') |
| DebugMsg('WriteGBB: updating with %s' % db_file) |
| if not gft_fwhash.UpdateGBB(image_file, db_file, in_place=True): |
| ErrorDie('WriteGBB: failed to update GBB.') |
| |
| # Latest flashrom supports minimal writing, so we don't need to care about |
| # managing partial writing sections here. Simply provide the whole image. |
| with open(image_file) as image: |
| image_data = image.read() |
| if not flashrom.write_whole(image_data): |
| ErrorDie('WriteGBB: failed to flash firmware to system.') |
| os.remove(image_file) |
| return True |
| |
| |
| def GFTLogCommand(f): |
| """Decorator for commands to log the results.""" |
| def wrapper(*args, **kw): |
| try: |
| DebugMsg('@Starting to execute command: %s.' % f.__name__) |
| result = f(*args, **kw) |
| DebugMsg('@Executed command: %s. Result: %s' % |
| (f.__name__, "SUCCESS" |
| if result is not False else "NOT SUCCESS")) |
| return result |
| except gft_common.GFTError, e: |
| DebugMsg('@Executed command: %s. Result: ERROR - %s' % |
| (f.__name__, e.value)) |
| raise |
| except: |
| DebugMsg('@Executed command: %s. Result: FAILED.' % f.__name__) |
| raise |
| return wrapper |
| |
| |
| ######################################################################## |
| # standard procedure |
| # (function names are used for self-reflection to command line parameters) |
| |
| @GFTLogCommand |
| def probe(): |
| """ Probes current system HWID by matching to components database files. """ |
| if not g_options.db_path: |
| ErrorDie('probe: need --db_path.') |
| db_list = FindComponentsDatabases(g_options.db_path) |
| hwcomp = InitializeHardwareComponents(do_probe=True) |
| matched_hwids = 0 |
| matched = {} |
| |
| # Don't care about GBB elements when probing |
| ignored_cids = ['hash_ro_firmware', 'part_id_hwqual', 'version_rw_firmware'] |
| for db in db_list: |
| VerboseMsg('probe: Matching current system by %s' % db) |
| (matched, failure) = hwcomp.match_current_system(db, |
| ignored_cids=ignored_cids) |
| if not failure: |
| print 'Probed: %s' % db |
| matched_hwids = matched_hwids + 1 |
| else: |
| WarningMsg('Unmatched for %s:%s' % (db, hwcomp.pformat(failure))) |
| |
| # TODO(hungte) we may need to decide a best match during the search |
| WarningMsg('Current System:%s' % hwcomp.pformat(matched)) |
| return (True if matched_hwids > 0 else False) |
| |
| |
| @GFTLogCommand |
| def write_gbb(): |
| """ Writes system GBB data by assigned components database. """ |
| if not g_options.write_gbb: |
| ErrorDie('write_gbb: need a valid component list file.') |
| return WriteGBB(g_options.write_gbb) |
| |
| |
| @GFTLogCommand |
| def verify_hwid(): |
| """ Verifies if HWID is matched to the qualified component list. """ |
| hwcomp = InitializeHardwareComponents() |
| matched_list = MatchComponentsDatabases(g_options.db_path) |
| matched = len(matched_list) |
| if matched > 0: |
| DebugMsg('Current System:%s\n' % hwcomp.pformat(matched_list.values()[0])) |
| if matched != 1: |
| ErrorDie('verify_hwid: failed to match exact one HWID (found %d).' % |
| matched) |
| matched_filename = matched_list.keys()[0] |
| matched_data = matched_list[matched_filename] |
| matched_hwid = matched_data['part_id_hwqual'][0] |
| print 'Verified: %s (%s)' % (matched_filename, matched_hwid) |
| return True |
| |
| |
| @GFTLogCommand |
| def verify_keys(): |
| """ Verifies if the keys in firmware and SSD are matched. """ |
| hwcomp = InitializeHardwareComponents(do_probe=False) |
| script = os.path.join(os.path.split(sys.argv[0])[0], 'gft_verify_keys.sh') |
| if not os.path.exists(script): |
| ErrorDie('verify_keys: cannot find script to verify.') |
| # TODO(hungte) replace the hard-coded SSD device name by some detection rules |
| # like "rootdev -s | sed 's/2$/4/", to support ARM platforms. |
| kernel_device = '/dev/sda4' |
| script_command = '%s %s %s' % (script, kernel_device, |
| hwcomp.load_main_firmware()) |
| try: |
| gft_common.SystemOutput(script_command) |
| except gft_common.GFTError, e: |
| ErrorDie('verify_keys: FAILED.\n%s' % e.value) |
| return True |
| |
| |
| @GFTLogCommand |
| def verify_switch_wp(): |
| """ Verifies if hardware write protection switch is enabled. """ |
| if VerifySwitch('write_protect', 1): |
| return True |
| ErrorDie('verify_switch_wp: failed') |
| |
| |
| @GFTLogCommand |
| def verify_switch_dev(): |
| """ Verifies if developer switch is disabled. """ |
| if VerifySwitch('developer_switch', 0): |
| return True |
| ErrorDie('verify_switch_dev: failed') |
| |
| |
| @GFTLogCommand |
| def create_report(): |
| """ Creates the report for uploading. """ |
| # decide log format |
| suffix = '.log' |
| if g_options.report_format == 'gz': |
| text_armed = False |
| suffix = suffix + '.gz' |
| elif g_options.report_format == 'base64': |
| text_armed = True |
| else: |
| ErrorDie('create_report: invalid format: %s' % g_options.report_format) |
| |
| # TODO(hungte) if matching is still too slow, we can try to reused previous |
| # cached results. |
| matched = MatchComponentsDatabases(g_options.db_path) |
| |
| if not len(matched) == 1: |
| ErrorDie('create_report: HWID is not singularly matched to database.') |
| vpd_source = InitializeHardwareComponents(do_probe=False).load_main_firmware() |
| report = gft_report.CreateReport(sys.argv, |
| matched.values()[0], |
| verbose_log_path=g_options.log_path, |
| vpd_source=vpd_source, |
| verbose=g_options.debug) |
| |
| # Printing this to log may double the log files... |
| if g_options.verbose: |
| sys.stdout.flush() |
| reduced = copy.deepcopy(report) |
| reduced['verbose_log'] = ['...(not listed, please check report)...'] |
| sys.stderr.write("Report Detail:\n%s\n" % gft_report.FormatReport(reduced)) |
| |
| data = gft_report.EncodeReport(report, text_armed=text_armed) |
| if not g_options.report_path: |
| # create one in temporary folder |
| g_options.report_path = gft_common.GetTemporaryFileName(prefix='gftrpt_', |
| suffix=suffix) |
| |
| gft_common.WriteFile(g_options.report_path, data) |
| report_saved_message = 'Report saved in: %s' % g_options.report_path |
| # To help scripting, the message must be printed to console and logged. |
| print report_saved_message |
| gft_common.Log(report_saved_message) |
| return True |
| |
| |
| @GFTLogCommand |
| def upload_report(): |
| """ Uploads a report for tracking the factory process. """ |
| report_path = g_options.report_path |
| if not (report_path and os.path.exists(report_path)): |
| ErrorDie('upload_report: need an existing report file ' |
| '(--report_path or --create_report)') |
| if not g_options.upload_method: |
| ErrorDie('upload_report: need proper --upload_method.') |
| if not gft_upload.Upload(report_path, g_options.upload_method): |
| ErrorDie('upload_report: failed to upload.') |
| return True |
| |
| |
| @GFTLogCommand |
| def wpfw(): |
| """ Enables and verifies firmware write protection. """ |
| if g_options.debug_dryrun_wpfw: |
| ErrorMsg('wpfw is by-passed. This device CANNOT be qualified.') |
| return True |
| hwcomp = InitializeHardwareComponents(do_probe=False) |
| read_binary = gft_common.ReadBinaryFile |
| target_set = [('ec', hwcomp.load_ec_firmware), |
| ('bios', hwcomp.load_main_firmware)] |
| # TODO(hungte) chrome-os-partner:3023 We need a cleaner way for EC detection, |
| # for examing using crossystem. Currently we only know there is no EC on arm. |
| with os.popen("uname -m") as uname_process: |
| arch = uname_process.read().strip() |
| if arch.startswith('arm'): |
| VerboseMsg('wpfw: ARM platform detected, ignore EC.') |
| assert target_set[0][0] == 'ec' |
| target_set = target_set[1:] |
| assert len(target_set) > 0 |
| for target_name, loader_api in target_set: |
| if not EnableWriteProtect(target_name, read_binary(loader_api())): |
| ErrorDie('wpfw: failed to enable firmware write protection.') |
| return True |
| |
| |
| @GFTLogCommand |
| def prepare_wipe(): |
| """ Prepares system to reboot for transitioning to release state. """ |
| if g_options.debug_dryrun_prepare_wipe: |
| ErrorMsg('prepare_wipe is by-passed. This device CANNOT be qualified.') |
| return True |
| wipe_script = os.path.join(os.path.split(sys.argv[0])[0], |
| 'gft_prepare_wipe.sh') |
| if not os.path.exists(wipe_script): |
| ErrorDie('prepare_wipe: cannot find script to prepare for wiping.') |
| wipe_method_map = { |
| 'fast': 'fast', |
| 'secure': '', |
| } |
| method = g_options.wipe_method |
| if method not in wipe_method_map: |
| ErrorDie('prepare_wipe: unknown wipe method: %s' % method) |
| tag = wipe_method_map[method] |
| command = 'FACTORY_WIPE_TAGS=%s %s' % (tag, wipe_script) |
| DebugMsg('prepare_wipe: execute command: %s' % command) |
| return_code = os.system(command) |
| if return_code != 0: |
| ErrorDie('prepare_wipe: failed(%s) in calling preparation script' |
| % return_code) |
| return True |
| |
| |
| @GFTLogCommand |
| def verify(): |
| """ Verifies if whole factory process is ready for finalization. """ |
| if (verify_switch_dev() and |
| verify_switch_wp() and |
| verify_hwid() and |
| verify_keys()): |
| return True |
| ErrorDie('verify: failed.') |
| |
| |
| @GFTLogCommand |
| def finalize(): |
| """ Finalize all factory tests and transit system into release state. """ |
| if (verify() and |
| wpfw() and |
| create_report() and |
| upload_report() and |
| prepare_wipe()): |
| return True |
| ErrorDie('finalization: failed.') |
| |
| |
| ######################################################################## |
| # console command line options |
| |
| |
| def ConsoleParser(): |
| """ command line option parser """ |
| parser = optparse.OptionParser() |
| |
| # Message control |
| parser.add_option('--debug', action='store_true', |
| help='provide debug messages') |
| parser.add_option('--verbose', action='store_true', default=False, |
| help='provide verbose messages') |
| parser.add_option('--log_path', metavar='PATH', |
| default=gft_common.DEFAULT_CONSOLE_LOG_PATH, |
| help='use the given path for verbose logs.') |
| |
| # Debugging |
| parser.add_option('--debug_dryrun_wpfw', action='store_true', |
| help='run --wpfw in dry-run mode (debugging only)') |
| parser.add_option('--debug_dryrun_prepare_wipe', action='store_true', |
| help='run --prepare_wipe in dry-run mode (debugging only)') |
| |
| # Configuration |
| parser.add_option('--config', metavar='PATH', |
| help='path to the config file (TBD)') |
| parser.add_option('--db_path', metavar='PATH', |
| help='path to the HWID component list databases') |
| |
| # HWID, GBB, and hardware components validation |
| parser.add_option('--probe', action='store_true', |
| help='generates list of probed component list ' |
| 'and matched HWID') |
| parser.add_option('--write_gbb', metavar='comp_db', |
| help='updates system GBB from a component database') |
| parser.add_option('--verify_hwid', action='store_true', |
| help='verifies if the HWID matches current system') |
| parser.add_option('--verify_keys', action='store_true', |
| help='verifies if the firmware and SSD keys match') |
| |
| # Hardware Switches |
| parser.add_option('--verify_switch_wp', action='store_true', |
| help='checks if write protection switch is enabled') |
| parser.add_option('--verify_switch_dev', action='store_true', |
| help='checks if developer switch is disabled') |
| |
| # Report Creation |
| parser.add_option('--create_report', action='store_true', |
| help='create the report for: vpd, hwid, date, ...') |
| parser.add_option('--report_path', metavar='PATH', |
| help='override the path for report file') |
| parser.add_option('--report_format', metavar='FORMAT', default='gz', |
| help='format of the generated report (see gft_report.py)') |
| |
| # Report Uploading |
| parser.add_option('--upload_report', action='store_true', |
| help='upload the data generated by --create_report') |
| parser.add_option('--upload_method', metavar='METHOD:PARAM', |
| help='assign the upload method (see gft_upload).') |
| |
| # Write Protection Wiping, and Finalization |
| parser.add_option('--wpfw', action='store_true', |
| help='enable and verify firmware write protection') |
| parser.add_option('--prepare_wipe', action='store_true', |
| help='prepare for wiping factory tests and ' |
| 'transit to release mode in next reboot') |
| parser.add_option('--wipe_method', metavar='METHOD', default='secure', |
| help='assign the wipe method (secure/fast).') |
| parser.add_option('--verify', action='store_true', |
| help='runs: --verify_switch_wp, --verify_switch_dev, ' |
| '--verify_hwid, --verify_keys') |
| parser.add_option('--finalize', action='store_true', |
| help='runs: --verify, --wpfw, ' |
| '--create_report, --upload_report, --prepare_wipe') |
| return parser |
| |
| |
| ######################################################################## |
| # main entry |
| |
| |
| @gft_common.GFTConsole |
| def _main(): |
| """ main entry """ |
| global g_options, g_args |
| parser = ConsoleParser() |
| (g_options, g_args) = parser.parse_args() |
| if g_args: |
| parser.error('Un-expected parameter(s): %s\n' % ' '.join(g_args)) |
| |
| if g_options.debug: |
| g_options.verbose = True |
| gft_common.SetDebugLevel(g_options.debug) |
| gft_common.SetVerboseLevel(g_options.verbose, g_options.log_path) |
| DebugMsg('options: ' + repr(g_options) + '\nargs: ' + repr(g_args), log=False) |
| |
| # execute commands by order. |
| |
| executed_commands = 0 |
| valid_commands = [ |
| 'probe', |
| 'write_gbb', |
| 'verify_hwid', |
| 'verify_keys', |
| 'verify_switch_wp', |
| 'verify_switch_dev', |
| 'create_report', |
| 'upload_report', |
| 'wpfw', |
| 'prepare_wipe', |
| 'verify', |
| 'finalize', |
| ] |
| return_value = 0 |
| for command in valid_commands: |
| if not getattr(g_options, command): |
| continue |
| DebugMsg('COMMAND: ' + command, log=False) |
| if not globals()[command](): |
| return_value = 1 |
| executed_commands = executed_commands + 1 |
| |
| if executed_commands == 0: |
| parser.print_help() |
| return return_value |
| |
| |
| if __name__ == '__main__': |
| sys.exit(_main()) |