blob: 77de019e2e13e3e4d201b5e77b602cf753dfa781 [file] [log] [blame] [edit]
#!/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())