blob: 1a5f137b65f32eab712707fa858496a625083d21 [file] [log] [blame]
Tammo Spalink9a96b8a2012-04-03 11:10:41 +08001#!/usr/bin/python
Tammo Spalink01e11722012-07-24 10:17:54 -07002# pylint: disable=E1101
Tammo Spalink9a96b8a2012-04-03 11:10:41 +08003#
4# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
5# Use of this source code is governed by a BSD-style license that can be
6# found in the LICENSE file.
7
8"""Google Factory Tool.
9
10This tool is indended to be used on factory assembly lines. It
11provides all of the Google required test functionality and must be run
12on each device as part of the assembly process.
13"""
14
15
16import logging
17import os
Jon Salz65266432012-07-30 19:02:49 +080018import pipes
Tammo Spalink9a96b8a2012-04-03 11:10:41 +080019import re
20import sys
Hung-Te Lin6bd16472012-06-20 16:26:47 +080021import time
Tammo Spalink9a96b8a2012-04-03 11:10:41 +080022
Tammo Spalink8fab5312012-05-28 18:33:30 +080023from tempfile import gettempdir, NamedTemporaryFile
Tammo Spalink86a61c62012-05-25 15:10:35 +080024
Tammo Spalinka40293e2012-07-04 14:58:56 +080025import factory_common # pylint: disable=W0611
Tammo Spalink9a96b8a2012-04-03 11:10:41 +080026
Tammo Spalinka40293e2012-07-04 14:58:56 +080027from cros.factory.common import Error, ParseKeyValueData, SetupLogging, Shell
28from cros.factory.common import YamlRead, YamlWrite
Tammo Spalink01e11722012-07-24 10:17:54 -070029from cros.factory.gooftool import crosfw
30from cros.factory.gooftool import report_upload
31from cros.factory.gooftool.bmpblk import unpack_bmpblock
Tammo Spalink95c43732012-07-25 15:57:14 -070032from cros.factory.gooftool.probe import Probe, PROBABLE_COMPONENT_CLASSES
Tammo Spalink01e11722012-07-24 10:17:54 -070033from cros.factory.gooftool.vpd_data import KNOWN_VPD_FIELD_DATA
Tammo Spalinka40293e2012-07-04 14:58:56 +080034from cros.factory.hacked_argparse import CmdArg, Command, ParseCmdline
35from cros.factory.hacked_argparse import verbosity_cmd_arg
Tammo Spalink01e11722012-07-24 10:17:54 -070036from cros.factory.hwdb import hwid_tool
Jon Salz83591782012-06-26 11:09:58 +080037from cros.factory.event_log import EventLog, EVENT_LOG_DIR
38from cros.factory.event_log import TimedUuid
cychiang7fe09372012-07-04 14:31:23 +080039from cros.factory.test.factory import FACTORY_LOG_PATH
Tammo Spalink86a61c62012-05-25 15:10:35 +080040
Tammo Spalink5c699832012-07-03 17:50:39 +080041
Tammo Spalink86a61c62012-05-25 15:10:35 +080042# Use a global event log, so that only a single log is created when
43# gooftool is called programmatically.
44_event_log = EventLog('gooftool')
45
Tammo Spalink9a96b8a2012-04-03 11:10:41 +080046
47def GetPrimaryDevicePath(partition=None):
48 def IsFixed(dev):
49 sysfs_path = '/sys/block/%s/removable' % dev
50 return (os.path.exists(sysfs_path) and
51 open(sysfs_path).read().strip() == '0')
52 alpha_re = re.compile(r'^/dev/([a-zA-Z]+)[0-9]+$')
53 alnum_re = re.compile(r'^/dev/([a-zA-Z]+[0-9]+)p[0-9]+$')
cychiangde1dee22012-05-22 09:42:09 +080054 matched_alnum = False
55 dev_set = set()
56 for path in Shell('cgpt find -t rootfs').stdout.strip().split():
57 for dev in alpha_re.findall(path):
58 if IsFixed(dev):
59 dev_set.add(dev)
60 matched_alnum = False
61 for dev in alnum_re.findall(path):
62 if IsFixed(dev):
63 dev_set.add(dev)
64 matched_alnum = True
Tammo Spalink9a96b8a2012-04-03 11:10:41 +080065 if len(dev_set) != 1:
66 raise Error('zero or multiple primary devs: %s' % dev_set)
67 dev_path = os.path.join('/dev', dev_set.pop())
68 if partition is None:
69 return dev_path
cychiangde1dee22012-05-22 09:42:09 +080070 fmt_str = '%sp%d' if matched_alnum else '%s%d'
Tammo Spalink9a96b8a2012-04-03 11:10:41 +080071 return fmt_str % (dev_path, partition)
72
73
74def GetReleaseRootPartitionPath():
75 return GetPrimaryDevicePath(5)
76
77
78def GetReleaseKernelPartitionPath():
79 return GetPrimaryDevicePath(4)
80
81
82def FindScript(script_name):
Hung-Te Linb7313ca2012-07-31 15:27:57 +080083 # __file__ is in /usr/local/factory/py/gooftool/gooftool.py
84 # scripts should be in /usr/local/factory/sh/*
85 factory_base = os.path.realpath(os.path.join(
86 os.path.dirname(os.path.realpath(__file__)), '..', '..'))
87 script_path = os.path.join(factory_base, 'sh', script_name)
Tammo Spalink9a96b8a2012-04-03 11:10:41 +080088 if not os.path.exists(script_path):
89 raise Error('Needed script %s does not exist.' % script_path)
90 return script_path
91
92
Tammo Spalink86a61c62012-05-25 15:10:35 +080093def ReadVpd(fw_image_file, kind):
94 raw_vpd_data = Shell('vpd -i %s -l -f %s' % (kind, fw_image_file)).stdout
Tammo Spalink9a96b8a2012-04-03 11:10:41 +080095 return ParseKeyValueData('"(.*)"="(.*)"$', raw_vpd_data)
96
97
Tammo Spalink86a61c62012-05-25 15:10:35 +080098def ReadRoVpd(fw_image_file):
99 return ReadVpd(fw_image_file, 'RO_VPD')
100
101
102def ReadRwVpd(fw_image_file):
103 return ReadVpd(fw_image_file, 'RW_VPD')
104
105
Tammo Spalink5c699832012-07-03 17:50:39 +0800106# TODO(tammo): Replace calls to sys.exit with raise Exit, and maybe
107# treat that specially (as a smoot exit, as opposed to the more
108# verbose output for generic Error).
109
110
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800111@Command('write_hwid',
112 CmdArg('hwid', metavar='HWID', help='HWID string'))
113def WriteHwid(options):
114 """Write specified HWID value into the system BB."""
Tammo Spalink95c43732012-07-25 15:57:14 -0700115 logging.info('writing hwid string %r', options.hwid)
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800116 main_fw = crosfw.LoadMainFirmware()
117 Shell('gbb_utility --set --hwid="%s" "%s"' %
118 (options.hwid, main_fw.GetFileName()))
119 main_fw.Write(sections=['GBB'])
Tammo Spalink86a61c62012-05-25 15:10:35 +0800120 _event_log.Log('write_hwid', hwid=options.hwid)
Tammo Spalink95c43732012-07-25 15:57:14 -0700121 print 'Wrote HWID: %r' % options.hwid
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800122
123
Tammo Spalink8fab5312012-05-28 18:33:30 +0800124_hwdb_path_cmd_arg = CmdArg(
125 '--hwdb_path', metavar='PATH',
126 default=hwid_tool.DEFAULT_HWID_DATA_PATH,
127 help='Path to the HWID database.')
128
129
Tammo Spalink95c43732012-07-25 15:57:14 -0700130_hwid_status_list_cmd_arg = CmdArg(
131 '--status', nargs='*', default=['supported'],
132 help='allow only HWIDs with these status values')
133
134
135@Command('best_match_hwids',
Tammo Spalink8fab5312012-05-28 18:33:30 +0800136 _hwdb_path_cmd_arg,
137 CmdArg('-b', '--board', metavar='BOARD',
Tammo Spalink95c43732012-07-25 15:57:14 -0700138 help='optional BOARD name, needed only if data is present '
139 'for more than one'),
Tammo Spalink8fab5312012-05-28 18:33:30 +0800140 CmdArg('--bom', metavar='BOM', help='BOM name'),
141 CmdArg('--variant', metavar='VARIANT', help='VARIANT code'),
Tammo Spalink5c699832012-07-03 17:50:39 +0800142 CmdArg('--optimistic', action='store_true',
143 help='do not probe; assume singletons match'),
Tammo Spalink95c43732012-07-25 15:57:14 -0700144 CmdArg('--comps', nargs='*', default=[],
145 help='list of canonical component names'),
146 CmdArg('--missing', nargs='*', default=[],
147 help='list component classes to be assumed missing'),
Tammo Spalink5c699832012-07-03 17:50:39 +0800148 CmdArg('--status', nargs='*', default=['supported'],
Tammo Spalink95c43732012-07-25 15:57:14 -0700149 help='consider only HWIDs within this list of status values'))
150def BestMatchHwids(options):
Tammo Spalink8fab5312012-05-28 18:33:30 +0800151 """Determine a list of possible HWIDs using provided args and probeing.
152
153 VOLATILE can always be determined by probing. To get a unique
154 result, VARIANT must be specified for all cases where the matching
155 BOM has more than one associated variant code, otherwise all HWID
156 variants will be returned. Both VARIANT and BOM information can
Tammo Spalink95c43732012-07-25 15:57:14 -0700157 alternatively be specified using the --stdin_comps argument, which
158 allows specifying a list of canonical names (one per line) on stdin,
159 one per line. Based on what is known from BOM and stdin_comps,
160 determine a list of components to probe for, and use those probe
161 results to resolve a list of matching HWIDs. If no boms,
162 components, or variant codes are specified, then a list of all HWIDs
163 that match probable components will be returned.
Tammo Spalink8fab5312012-05-28 18:33:30 +0800164
165 Returns (on stdout): A list of HWIDs that match the available probe
166 results and argument contraints, one per line.
167 """
Tammo Spalink5c699832012-07-03 17:50:39 +0800168 map(hwid_tool.Validate.Status, options.status)
Tammo Spalink95c43732012-07-25 15:57:14 -0700169 hw_db = hwid_tool.HardwareDb(options.hwdb_path)
Tammo Spalink5c699832012-07-03 17:50:39 +0800170 comp_db = hw_db.comp_db
Tammo Spalink3a7e9022012-06-27 14:08:40 +0800171 device = hw_db.GetDevice(options.board)
Tammo Spalink5c699832012-07-03 17:50:39 +0800172 component_spec = hwid_tool.ComponentSpec.New()
Tammo Spalink8fab5312012-05-28 18:33:30 +0800173 if options.bom:
Tammo Spalink5c699832012-07-03 17:50:39 +0800174 device.BomExists(options.bom)
Tammo Spalink01e11722012-07-24 10:17:54 -0700175 component_spec = hwid_tool.CombineComponentSpecs(
Tammo Spalink5c699832012-07-03 17:50:39 +0800176 component_spec, device.boms[options.bom].primary)
Tammo Spalink8fab5312012-05-28 18:33:30 +0800177 if options.variant:
Tammo Spalink5c699832012-07-03 17:50:39 +0800178 device.VariantExists(options.variant)
179 variant_spec = device.variants[options.variant]
Tammo Spalink01e11722012-07-24 10:17:54 -0700180 if hwid_tool.ComponentSpecsConflict(component_spec, variant_spec):
Tammo Spalink95c43732012-07-25 15:57:14 -0700181 sys.exit('ERROR: multiple specifications for these components:\n%s'
182 % YamlWrite(sorted(ComponentSpecClasses(component_spec) &
183 ComponentSpecClasses(variant_spec))))
Tammo Spalink01e11722012-07-24 10:17:54 -0700184 component_spec = hwid_tool.CombineComponentSpecs(
185 component_spec, variant_spec)
Tammo Spalink95c43732012-07-25 15:57:14 -0700186 if options.comps or options.missing:
187 map(comp_db.CompExists, options.comps)
188 map(comp_db.CompClassExists, options.missing)
189 extra_comp_spec = comp_db.CreateComponentSpec(
190 components=options.comps,
191 missing=options.missing)
192 print 'cmdline asserted components:\n%s' % extra_comp_spec.Encode()
193 if hwid_tool.ComponentSpecsConflict(component_spec, extra_comp_spec):
194 sys.exit('ERROR: multiple specifications for these components:\n%s'
195 % YamlWrite(sorted(ComponentSpecClasses(component_spec) &
196 ComponentSpecClasses(extra_comp_spec))))
197 component_spec = hwid_tool.CombineComponentSpecs(
198 component_spec, extra_comp_spec)
Tammo Spalink01e11722012-07-24 10:17:54 -0700199 spec_classes = hwid_tool.ComponentSpecClasses(component_spec)
Tammo Spalink95c43732012-07-25 15:57:14 -0700200 missing_classes = set(comp_db.all_comp_classes) - spec_classes
Tammo Spalink5c699832012-07-03 17:50:39 +0800201 if missing_classes and not options.optimistic:
Tammo Spalink95c43732012-07-25 15:57:14 -0700202 non_probable_missing = missing_classes - PROBABLE_COMPONENT_CLASSES
203 if non_probable_missing:
204 sys.exit('FAILURE: these classes are necessary, were not specified '
205 'as inputs, and cannot be probed for:\n%s' %
206 YamlWrite(list(non_probable_missing)))
207 print 'probing for missing classes:'
208 print YamlWrite(list(missing_classes))
209 probe_results = Probe(target_comp_classes=list(missing_classes),
210 probe_volatile=False, probe_initial_config=False)
211 cooked_components = comp_db.MatchComponentProbeValues(
212 probe_results.found_probe_value_map)
213 if cooked_components.unmatched:
214 sys.exit('ERROR: some probed components are unrecognized:\n%s'
215 % YamlWrite(cooked_components.unmatched))
216 probed_comp_spec = comp_db.CreateComponentSpec(
217 components=cooked_components.matched,
218 missing=probe_results.missing_component_classes)
219 component_spec = hwid_tool.CombineComponentSpecs(
220 component_spec, probed_comp_spec)
221 print YamlWrite({'component data used for matching': {
222 'missing component classes': component_spec.classes_missing,
223 'found components': component_spec.components}})
224 component_data = hwid_tool.ComponentData(
225 extant_components=hwid_tool.ComponentSpecCompClassMap(
226 component_spec).keys(),
227 classes_missing=component_spec.classes_missing)
228 match_tree = device.BuildMatchTree(component_data)
229 if not match_tree:
230 sys.exit('FAILURE: NO matching BOMs found')
231 print 'potential BOMs/VARIANTs:'
232 potential_variants = set()
233 potential_volatiles = set()
234 for bom_name, variant_tree in match_tree.items():
235 print ' BOM: %-8s VARIANTS: %s' % (
236 bom_name, ', '.join(sorted(variant_tree)))
237 for variant_code in variant_tree:
238 potential_variants.add(variant_code)
239 for volatile_code in device.volatiles:
240 status = device.GetHwidStatus(bom_name, variant_code, volatile_code)
241 if status in options.status:
242 potential_volatiles.add(volatile_code)
243 print ''
244 if len(potential_variants) == 0:
245 sys.exit('FAILURE: no matching VARIANTs found')
246 if len(potential_volatiles) == 0:
247 sys.exit('FAILURE: no VOLATILEs found for potential matching BOMs/VARIANTS '
248 '(with specified status)')
249 if options.optimistic and len(potential_volatiles) == 1:
250 print ('MATCHING HWID: %s' %
251 device.FmtHwid(bom_name, variant_code, potential_volatiles.pop()))
Tammo Spalink5c699832012-07-03 17:50:39 +0800252 return
Tammo Spalink95c43732012-07-25 15:57:14 -0700253 print ('probing VOLATILEs to resolve potential matches: %s\n' %
254 ', '.join(sorted(potential_volatiles)))
255 vol_probe_results = Probe(
256 target_comp_classes=[],
257 probe_volatile=True,
258 probe_initial_config=False)
259 cooked_volatiles = device.MatchVolatileValues(
260 vol_probe_results.found_volatile_values)
261 match_tree = device.BuildMatchTree(
262 component_data, cooked_volatiles.matched_tags)
263 matched_hwids = device.GetMatchTreeHwids(match_tree)
264 if matched_hwids:
265 for hwid in matched_hwids:
266 print 'MATCHING HWID: %s' % hwid
267 return
268 print 'exact HWID matching failed, but the following BOMs match: %s' % (
269 ', '.join(sorted(match_tree)))
270 if options.optimistic and len(match_tree) == 1:
271 bom_name = set(match_tree).pop()
Tammo Spalink5c699832012-07-03 17:50:39 +0800272 bom = device.boms[bom_name]
Tammo Spalink95c43732012-07-25 15:57:14 -0700273 variant_matches = match_tree[bom_name]
Tammo Spalink5c699832012-07-03 17:50:39 +0800274 if len(variant_matches) == 1:
275 var_code = set(variant_matches).pop()
276 elif len(bom.variants) == 1:
277 var_code = set(bom.variants).pop()
278 else:
Tammo Spalink95c43732012-07-25 15:57:14 -0700279 sys.exit('FAILURE: NO matching HWIDs found; optimistic matching failed '
280 'because there were too many variants to choose from for BOM %r'
281 % bom_name)
282 hwids = [device.FmtHwid(bom_name, var_code, vol_code)
283 for vol_code in device.volatiles
284 if device.GetHwidStatus(bom_name, var_code, vol_code)
285 in options.status]
286 for hwid in hwids:
287 print 'MATCHING HWID: %s' % hwid
Tammo Spalink5c699832012-07-03 17:50:39 +0800288 return
289 else:
Tammo Spalink95c43732012-07-25 15:57:14 -0700290 print ('optimistic matching not attempted because either it was '
291 'not requested, or because the number of BOMs was <> 1\n')
292 sys.exit('FAILURE: NO matching HWIDs found')
Tammo Spalink8fab5312012-05-28 18:33:30 +0800293
294
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800295@Command('probe',
296 CmdArg('--comps', nargs='*',
297 help='List of keys from the component_db registry.'),
298 CmdArg('--no_vol', action='store_true',
299 help='Do not probe volatile data.'),
300 CmdArg('--no_ic', action='store_true',
301 help='Do not probe initial_config data.'))
302def RunProbe(options):
303 """Print yaml-formatted breakdown of probed device properties."""
Tammo Spalink01e11722012-07-24 10:17:54 -0700304 probe_results = Probe(target_comp_classes=options.comps,
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800305 probe_volatile=not options.no_vol,
306 probe_initial_config=not options.no_ic)
Tammo Spalink3a7e9022012-06-27 14:08:40 +0800307 print probe_results.Encode()
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800308
309
Tammo Spalink214caf42012-05-28 10:45:00 +0800310@Command('verify_components',
311 _hwdb_path_cmd_arg,
Tammo Spalink5c699832012-07-03 17:50:39 +0800312 CmdArg('target_comps', nargs='*'))
Tammo Spalink214caf42012-05-28 10:45:00 +0800313def VerifyComponents(options):
314 """Verify that probable components all match entries in the component_db.
315
Tammo Spalink5c699832012-07-03 17:50:39 +0800316 Probe for each component class in the target_comps and verify
Tammo Spalink214caf42012-05-28 10:45:00 +0800317 that a corresponding match exists in the component_db -- make sure
318 that these components are present, that they have been approved, but
319 do not check against any specific BOM/HWID configurations.
320 """
Tammo Spalink5c699832012-07-03 17:50:39 +0800321 comp_db = hwid_tool.HardwareDb(options.hwdb_path).comp_db
322 if not options.target_comps:
323 sys.exit('ERROR: no target component classes specified; possible choices:\n'
324 + '\n '.join(sorted(comp_db.components)))
325 for comp_class in options.target_comps:
326 if comp_class not in comp_db.components:
327 sys.exit('ERROR: specified component class %r does not exist'
Tammo Spalink214caf42012-05-28 10:45:00 +0800328 ' in the component DB.' % comp_class)
Tammo Spalink01e11722012-07-24 10:17:54 -0700329 probe_results = Probe(target_comp_classes=options.target_comps,
Tammo Spalink214caf42012-05-28 10:45:00 +0800330 probe_volatile=False, probe_initial_config=False)
Tammo Spalink214caf42012-05-28 10:45:00 +0800331 errors = []
332 matches = []
Tammo Spalink5c699832012-07-03 17:50:39 +0800333 for comp_class in sorted(options.target_comps):
Tammo Spalink214caf42012-05-28 10:45:00 +0800334 probe_val = probe_results.found_components.get(comp_class, None)
335 if probe_val is not None:
Tammo Spalink5c699832012-07-03 17:50:39 +0800336 comp_name = comp_db.result_name_map.get(probe_val, None)
Tammo Spalink214caf42012-05-28 10:45:00 +0800337 if comp_name is not None:
338 matches.append(comp_name)
339 else:
340 errors.append('unsupported %r component found with probe result'
341 ' %r (no matching name in the component DB)' %
342 (comp_class, probe_val))
343 else:
344 errors.append('missing %r component' % comp_class)
345 if errors:
346 print '\n'.join(errors)
347 sys.exit('component verification FAILURE')
348 else:
349 print 'component verification SUCCESS'
350 print 'found components:\n %s' % '\n '.join(matches)
351
352
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800353@Command('verify_hwid',
Tammo Spalink95c43732012-07-25 15:57:14 -0700354 _hwid_status_list_cmd_arg,
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800355 _hwdb_path_cmd_arg)
356def VerifyHwid(options):
357 """Verify system HWID properties match probed device properties.
358
359 First probe components, volatile and initial_config parameters for
360 the DUT. Then use the available device data to produce a list of
361 candidate HWIDs. Then verify the HWID from the DUT is present in
362 that list. Then verify that the DUT initial config values match
363 those specified for its HWID. Finally, verify that VPD contains all
364 the necessary fields as specified by the board data, and when
365 possible verify that values are legitimate.
366 """
Tammo Spalink5c699832012-07-03 17:50:39 +0800367 def VerifyVpd(ro_vpd_keys):
368 ro_vpd = ReadRoVpd(main_fw_file)
369 for key in ro_vpd_keys:
370 if key not in ro_vpd:
371 sys.exit('Missing required VPD field: %s' % key)
Tammo Spalink01e11722012-07-24 10:17:54 -0700372 known_valid_values = KNOWN_VPD_FIELD_DATA.get(key, None)
Tammo Spalink5c699832012-07-03 17:50:39 +0800373 value = ro_vpd[key]
374 if known_valid_values is not None and value not in known_valid_values:
375 sys.exit('Invalid VPD entry : key %r, value %r' % (key, value))
376 rw_vpd = ReadRwVpd(main_fw_file)
377 _event_log.Log('vpd', ro_vpd=ro_vpd, rw_vpd=rw_vpd)
378 map(hwid_tool.Validate.Status, options.status)
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800379 main_fw_file = crosfw.LoadMainFirmware().GetFileName()
380 gbb_result = Shell('gbb_utility -g --hwid %s' % main_fw_file).stdout
Tammo Spalink95c43732012-07-25 15:57:14 -0700381 hwid_str = re.findall(r'hardware_id:(.*)', gbb_result)[0].strip()
382 hwid = hwid_tool.ParseHwid(hwid_str)
Tammo Spalink5c699832012-07-03 17:50:39 +0800383 hw_db = hwid_tool.HardwareDb(options.hwdb_path)
Tammo Spalink95c43732012-07-25 15:57:14 -0700384 print 'Verifying system HWID: %r\n' % hwid.hwid
385 device = hw_db.GetDevice(hwid.board)
386 hwid_status = device.GetHwidStatus(hwid.bom, hwid.variant, hwid.volatile)
387 if hwid_status not in options.status:
Tammo Spalink5c699832012-07-03 17:50:39 +0800388 sys.exit('HWID status must be one of [%s], found %r' %
Tammo Spalink95c43732012-07-25 15:57:14 -0700389 (', '.join(options.status, hwid_status)))
390 probe_results = Probe()
391 cooked_components = hw_db.comp_db.MatchComponentProbeValues(
392 probe_results.found_probe_value_map)
393 cooked_volatiles = device.MatchVolatileValues(
394 probe_results.found_volatile_values)
395 cooked_initial_configs = device.MatchInitialConfigValues(
396 probe_results.initial_configs)
397 component_data = hwid_tool.ComponentData(
398 extant_components=cooked_components.matched,
399 classes_missing=probe_results.missing_component_classes)
400 match_tree = device.BuildMatchTree(
401 component_data, cooked_volatiles.matched_tags)
402 matched_hwids = device.GetMatchTreeHwids(match_tree)
403 print 'HWID status: %s\n' % hwid_status
404 print 'probed system components:'
405 print YamlWrite(cooked_components.__dict__)
406 print 'missing component classes:'
407 print YamlWrite(probe_results.missing_component_classes)
408 print 'probed volatiles:'
409 print YamlWrite(cooked_volatiles.__dict__)
410 print 'probed initial_configs:'
411 print YamlWrite(cooked_initial_configs)
412 print 'hwid match tree:'
413 print YamlWrite(match_tree)
Tammo Spalink86a61c62012-05-25 15:10:35 +0800414 _event_log.Log(
Tammo Spalink5c699832012-07-03 17:50:39 +0800415 'probe',
Tammo Spalink95c43732012-07-25 15:57:14 -0700416 found_components=cooked_components.__dict__,
417 missing_component_classes=probe_results.missing_component_classes,
418 volatiles=cooked_volatiles.__dict__,
419 initial_configs=cooked_initial_configs)
420 if hwid.hwid not in matched_hwids:
Tammo Spalink5c699832012-07-03 17:50:39 +0800421 err_msg = 'HWID verification FAILED.\n'
Tammo Spalink95c43732012-07-25 15:57:14 -0700422 if cooked_components.unmatched:
Tammo Spalink5c699832012-07-03 17:50:39 +0800423 sys.exit(err_msg + 'some components could not be indentified:\n%s' %
Tammo Spalink95c43732012-07-25 15:57:14 -0700424 YamlWrite(cooked_components.unmatched))
425 if not match_tree:
Tammo Spalink5c699832012-07-03 17:50:39 +0800426 sys.exit(err_msg + 'no matching boms were found for components:\n%s' %
Tammo Spalink95c43732012-07-25 15:57:14 -0700427 component_data.Encode())
428 if hwid.bom not in match_tree:
Tammo Spalink5c699832012-07-03 17:50:39 +0800429 sys.exit(err_msg + 'matching boms [%s] do not include target bom %r' %
Tammo Spalink95c43732012-07-25 15:57:14 -0700430 (', '.join(sorted(match_tree)), hwid.bom))
431 err_msg += 'target bom %r matches components' % hwid.bom
432 if hwid.bom not in device.IntersectBomsAndInitialConfigs(
433 cooked_initial_configs):
Tammo Spalink5c699832012-07-03 17:50:39 +0800434 sys.exit(err_msg + ', but failed initial config verification')
Tammo Spalink95c43732012-07-25 15:57:14 -0700435 matched_variants = match_tree.get(hwid.bom, {})
436 if hwid.variant not in matched_variants:
Tammo Spalink5c699832012-07-03 17:50:39 +0800437 sys.exit(err_msg + ', but target variant_code %r did not match' %
Tammo Spalink95c43732012-07-25 15:57:14 -0700438 hwid.variant)
439 matched_volatiles = matched_variants.get(hwid.variant, {})
440 if hwid.volatile not in matched_volatiles:
Tammo Spalink5c699832012-07-03 17:50:39 +0800441 sys.exit(err_msg + ', but target volatile_code %r did not match' %
Tammo Spalink95c43732012-07-25 15:57:14 -0700442 hwid.volatile)
443 found_status = matched_volatiles.get(hwid.volatile, None)
Tammo Spalink5c699832012-07-03 17:50:39 +0800444 sys.exit(err_msg + ', but hwid status %r was unacceptable' % found_status)
Tammo Spalink95c43732012-07-25 15:57:14 -0700445 VerifyVpd(device.vpd_ro_fields)
Tammo Spalink5c699832012-07-03 17:50:39 +0800446 _event_log.Log('verified_hwid', hwid=hwid)
Tammo Spalink95c43732012-07-25 15:57:14 -0700447 print 'Verification SUCCESS!'
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800448
449
450@Command('verify_keys')
Tammo Spalink01e11722012-07-24 10:17:54 -0700451def VerifyKeys(options): # pylint: disable=W0613
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800452 """Verify keys in firmware and SSD match."""
Tammo Spalink461ddce2012-05-10 19:28:55 +0800453 script = FindScript('verify_keys.sh')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800454 kernel_device = GetReleaseKernelPartitionPath()
455 main_fw_file = crosfw.LoadMainFirmware().GetFileName()
456 result = Shell('%s %s %s' % (script, kernel_device, main_fw_file))
457 if not result.success:
458 raise Error, '%r failed, stderr: %r' % (script, result.stderr)
459
460
461@Command('set_fw_bitmap_locale')
Tammo Spalink01e11722012-07-24 10:17:54 -0700462def SetFirmwareBitmapLocale(options): # pylint: disable=W0613
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800463 """Use VPD locale value to set firmware bitmap default language."""
464 image_file = crosfw.LoadMainFirmware().GetFileName()
465 locale = ReadRoVpd(image_file).get('initial_locale', None)
466 if locale is None:
467 raise Error, 'Missing initial_locale VPD.'
468 bitmap_locales = []
469 with NamedTemporaryFile() as f:
470 Shell('gbb_utility -g --bmpfv=%s %s' % (f.name, image_file))
Tammo Spalink01e11722012-07-24 10:17:54 -0700471 bmpblk_data = unpack_bmpblock(f.read())
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800472 bitmap_locales = bmpblk_data.get('locales', bitmap_locales)
473 # Some locale values are just a language code and others are a
474 # hyphen-separated language code and country code pair. We care
475 # only about the language code part.
476 language_code = locale.partition('-')[0]
477 if language_code not in bitmap_locales:
478 raise Error, ('Firmware bitmaps do not contain support for the specified '
479 'initial locale language %r' % language_code)
480 else:
481 locale_index = bitmap_locales.index(language_code)
482 logging.info('Firmware bitmap initial locale set to %d (%s).',
483 locale_index, bitmap_locales[locale_index])
484 Shell('crossystem loc_idx=%d' % locale_index)
485
486
487@Command('verify_system_time')
Tammo Spalink01e11722012-07-24 10:17:54 -0700488def VerifySystemTime(options): # pylint: disable=W0613
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800489 """Verify system time is later than release filesystem creation time."""
Tammo Spalink461ddce2012-05-10 19:28:55 +0800490 script = FindScript('verify_system_time.sh')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800491 rootfs_device = GetReleaseRootPartitionPath()
492 result = Shell('%s %s' % (script, rootfs_device))
493 if not result.success:
494 raise Error, '%r failed, stderr: %r' % (script, result.stderr)
495
496
497@Command('verify_rootfs')
Tammo Spalink01e11722012-07-24 10:17:54 -0700498def VerifyRootFs(options): # pylint: disable=W0613
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800499 """Verify rootfs on SSD is valid by checking hash."""
Tammo Spalink461ddce2012-05-10 19:28:55 +0800500 script = FindScript('verify_rootfs.sh')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800501 rootfs_device = GetReleaseRootPartitionPath()
502 result = Shell('%s %s' % (script, rootfs_device))
503 if not result.success:
504 raise Error, '%r failed, stderr: %r' % (script, result.stderr)
505
506
507@Command('verify_switch_wp')
Tammo Spalink01e11722012-07-24 10:17:54 -0700508def VerifyWpSwitch(options): # pylint: disable=W0613
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800509 """Verify hardware write protection switch is enabled."""
510 if Shell('crossystem wpsw_cur').stdout.strip() != '1':
Hung-Te Lin6d827542012-07-19 11:50:41 +0800511 raise Error, 'write protection switch is disabled'
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800512
513
514@Command('verify_switch_dev')
Tammo Spalink01e11722012-07-24 10:17:54 -0700515def VerifyDevSwitch(options): # pylint: disable=W0613
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800516 """Verify developer switch is disabled."""
Hung-Te Lind7d34722012-07-26 16:48:35 +0800517 VBSD_HONOR_VIRT_DEV_SWITCH = 0x400
518 flags = int(Shell('crossystem vdat_flags').stdout.strip(), 0)
519 if (flags & VBSD_HONOR_VIRT_DEV_SWITCH) != 0:
520 # System is using virtual developer switch. That will be handled in
521 # prepare_wipe.sh by setting "crossystem disable_dev_request=1" -- although
522 # we can't verify that until next reboot, because the real values are stored
523 # in TPM.
524 logging.warn('VerifyDevSwitch: No physical switch.')
525 _event_log.Log('switch_dev', type='virtual switch')
526 return
527 if Shell('crossystem devsw_cur').stdout.strip() != '0':
528 raise Error, 'developer mode is not disabled'
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800529
530
531@Command('write_protect')
Tammo Spalink01e11722012-07-24 10:17:54 -0700532def EnableFwWp(options): # pylint: disable=W0613
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800533 """Enable then verify firmware write protection."""
534
Hung-Te Linb21c6682012-08-01 13:53:57 +0800535 def CalculateLegacyRange(fw_type, length, section_data,
536 section_name):
Hung-Te Lin7ea39e82012-07-31 18:39:33 +0800537 ro_size = length / 2
538 ro_a = int(section_data[0] / ro_size)
539 ro_b = int((section_data[0] + section_data[1] - 1) / ro_size)
540 if ro_a != ro_b:
541 raise Error("%s firmware section %s has illegal size" %
Hung-Te Linb21c6682012-08-01 13:53:57 +0800542 (fw_type, section_name))
Hung-Te Lin7ea39e82012-07-31 18:39:33 +0800543 ro_offset = ro_a * ro_size
544 return (ro_offset, ro_size)
545
546 def WriteProtect(fw_file_path, fw_type, legacy_section):
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800547 """Calculate protection size, then invoke flashrom.
548
549 Our supported chips only allow write protecting half their total
550 size, so we parition the flash chipset space accordingly.
551 """
552 raw_image = open(fw_file_path, 'rb').read()
Hung-Te Lin7ea39e82012-07-31 18:39:33 +0800553 wp_section = 'WP_RO'
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800554 image = crosfw.FirmwareImage(raw_image)
Hung-Te Lin7ea39e82012-07-31 18:39:33 +0800555 if image.has_section(wp_section):
556 section_data = image.get_section_area(wp_section)
557 ro_offset = section_data[0]
558 ro_size = section_data[1]
559 elif image.has_section(legacy_section):
560 section_data = image.get_section_area(legacy_section)
561 (ro_offset, ro_size) = CalculateLegacyRange(
Hung-Te Linb21c6682012-08-01 13:53:57 +0800562 fw_type, len(raw_image), section_data, legacy_section)
Hung-Te Lin7ea39e82012-07-31 18:39:33 +0800563 else:
564 raise Error('could not find %s firmware section %s or %s' %
565 (fw_type, wp_section, legacy_section))
566
567 logging.debug('write protecting %s [off=%x size=%x]', fw_type,
568 ro_offset, ro_size)
569 crosfw.Flashrom(fw_type).EnableWriteProtection(ro_offset, ro_size)
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800570
571 WriteProtect(crosfw.LoadMainFirmware().GetFileName(), 'main', 'RO_SECTION')
Tammo Spalink86a61c62012-05-25 15:10:35 +0800572 _event_log.Log('wp', fw='main')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800573 ec_fw_file = crosfw.LoadEcFirmware().GetFileName()
574 if ec_fw_file is not None:
575 WriteProtect(ec_fw_file, 'ec', 'EC_RO')
Tammo Spalink86a61c62012-05-25 15:10:35 +0800576 _event_log.Log('wp', fw='ec')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800577 else:
578 logging.warning('EC not write protected (seems there is no EC flash).')
579
580
581@Command('clear_gbb_flags')
Tammo Spalink01e11722012-07-24 10:17:54 -0700582def ClearGbbFlags(options): # pylint: disable=W0613
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800583 """Zero out the GBB flags, in preparation for transition to release state.
584
585 No GBB flags are set in release/shipping state, but they are useful
586 for factory/development. See "gbb_utility --flags" for details.
587 """
Tammo Spalink461ddce2012-05-10 19:28:55 +0800588 script = FindScript('clear_gbb_flags.sh')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800589 result = Shell(script)
590 if not result.success:
591 raise Error, '%r failed, stderr: %r' % (script, result.stderr)
Tammo Spalink86a61c62012-05-25 15:10:35 +0800592 _event_log.Log('clear_gbb_flags')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800593
594
595@Command('prepare_wipe',
596 CmdArg('--fast', action='store_true',
597 help='use non-secure but faster wipe method.'))
598def PrepareWipe(options):
599 """Prepare system for transition to release state in next reboot."""
Tammo Spalink461ddce2012-05-10 19:28:55 +0800600 script = FindScript('prepare_wipe.sh')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800601 tag = 'fast' if options.fast else ''
602 rootfs_device = GetReleaseRootPartitionPath()
603 result = Shell('FACTORY_WIPE_TAGS=%s %s %s' % (tag, script, rootfs_device))
604 if not result.success:
605 raise Error, '%r failed, stderr: %r' % (script, result.stderr)
606
607
608@Command('verify',
Hung-Te Lin6d827542012-07-19 11:50:41 +0800609 CmdArg('--no_write_protect', action='store_true',
610 help='Do not check write protection switch state.'),
Tammo Spalink95c43732012-07-25 15:57:14 -0700611 _hwid_status_list_cmd_arg,
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800612 _hwdb_path_cmd_arg)
613def Verify(options):
614 """Verifies if whole factory process is ready for finalization.
615
616 This routine performs all the necessary checks to make sure the
617 device is ready to be finalized, but does not modify state. These
618 checks include dev switch, firmware write protection switch, hwid,
619 system time, keys, and root file system.
620 """
Hung-Te Lin6d827542012-07-19 11:50:41 +0800621 if not options.no_write_protect:
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800622 VerifyWpSwitch({})
Hung-Te Lin6d827542012-07-19 11:50:41 +0800623 VerifyDevSwitch({})
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800624 VerifyHwid(options)
625 VerifySystemTime({})
626 VerifyKeys({})
627 VerifyRootFs({})
628
629
Tammo Spalink86a61c62012-05-25 15:10:35 +0800630@Command('log_system_details')
Tammo Spalink01e11722012-07-24 10:17:54 -0700631def LogSystemDetails(options): # pylint: disable=W0613
Tammo Spalink86a61c62012-05-25 15:10:35 +0800632 """Write miscellaneous system details to the event log."""
633 raw_cs_data = Shell('crossystem').stdout.strip().splitlines()
634 # The crossytem output contains many lines like:
635 # 'key = value # description'
636 # Use regexps to pull out the key-value pairs and build a dict.
637 cs_data = dict((k, v.strip()) for k, v in
638 map(lambda x: re.findall(r'\A(\S+)\s+=\s+(.*)#.*\Z', x)[0],
639 raw_cs_data))
640 _event_log.Log(
641 'system_details',
642 platform_name=Shell('mosys platform name').stdout.strip(),
643 crossystem=cs_data,
644 modem_status=Shell('modem status').stdout.splitlines(),
645 ec_wp_status=Shell(
646 'flashrom -p internal:bus=lpc --get-size 2>/dev/null && '
647 'flashrom -p internal:bus=lpc --wp-status || '
648 'echo "EC is not available."').stdout,
649 bios_wp_status = Shell(
650 'flashrom -p internal:bus=spi --wp-status').stdout)
651
652
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800653_upload_method_cmd_arg = CmdArg(
654 '--upload_method', metavar='METHOD:PARAM',
655 help=('How to perform the upload. METHOD should be one of '
656 '{ftp, shopfloor, curl, cpfe, custom}.'))
Jon Salz65266432012-07-30 19:02:49 +0800657_add_file_cmd_arg = CmdArg(
658 '--add_file', metavar='FILE', action='append',
659 help='Extra file to include in report (must be an absolute path)')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800660
661@Command('upload_report',
Jon Salz65266432012-07-30 19:02:49 +0800662 _upload_method_cmd_arg,
663 _add_file_cmd_arg)
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800664def UploadReport(options):
665 """Create and a report containing key device details."""
Hung-Te Lin6bd16472012-06-20 16:26:47 +0800666 def NormalizeAsFileName(token):
667 return re.sub(r'\W+', '', token).strip()
Tammo Spalink86a61c62012-05-25 15:10:35 +0800668 ro_vpd = ReadRoVpd(crosfw.LoadMainFirmware().GetFileName())
669 device_sn = ro_vpd.get('serial_number', None)
670 if device_sn is None:
671 logging.warning('RO_VPD missing device serial number')
672 device_sn = 'MISSING_SN_' + TimedUuid()
Hung-Te Lin6bd16472012-06-20 16:26:47 +0800673 target_name = '%s_%s.tbz2' % (time.strftime('%Y%m%dT%H%M%SZ', time.gmtime()),
674 NormalizeAsFileName(device_sn))
Tammo Spalink86a61c62012-05-25 15:10:35 +0800675 target_path = os.path.join(gettempdir(), target_name)
676 # Intentionally ignoring dotfiles in EVENT_LOG_DIR.
677 tar_cmd = 'cd %s ; tar cjf %s *' % (EVENT_LOG_DIR, target_path)
678 tar_cmd += ' --add-file %s' % FACTORY_LOG_PATH
Jon Salz65266432012-07-30 19:02:49 +0800679 if options.add_file:
680 for f in options.add_file:
681 # Require absolute paths since the tar command may change the
682 # directory.
683 if not f.startswith('/'):
684 raise Error('Not an absolute path: %s' % f)
685 if not os.path.exists(f):
686 raise Error('File does not exist: %s' % f)
687 tar_cmd += ' --add-file %s' % pipes.quote(f)
Tammo Spalink86a61c62012-05-25 15:10:35 +0800688 cmd_result = Shell(tar_cmd)
689 if not cmd_result.success:
690 raise Error('unable to tar event logs, cmd %r failed, stderr: %r' %
691 (tar_cmd, cmd_result.stderr))
692 if options.upload_method is None or options.upload_method == 'none':
693 logging.warning('REPORT UPLOAD SKIPPED (report left at %s)', target_path)
694 return
695 method, param = options.upload_method.split(':', 1)
696 if method == 'shopfloor':
697 report_upload.ShopFloorUpload(target_path, param)
698 elif method == 'ftp':
Jay Kim360c1dd2012-06-25 10:58:11 -0700699 report_upload.FtpUpload(target_path, 'ftp:' + param)
Tammo Spalink86a61c62012-05-25 15:10:35 +0800700 elif method == 'ftps':
701 report_upload.CurlUrlUpload(target_path, '--ftp-ssl-reqd ftp:%s' % param)
702 elif method == 'cpfe':
703 report_upload.CpfeUpload(target_path, param)
704 else:
705 raise Error('unknown report upload method %r', method)
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800706
707
708@Command('finalize',
Hung-Te Lin6d827542012-07-19 11:50:41 +0800709 CmdArg('--no_write_protect', action='store_true',
710 help='Do not enable firmware write protection.'),
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800711 CmdArg('--fast', action='store_true',
712 help='use non-secure but faster wipe method.'),
713 _hwdb_path_cmd_arg,
Tammo Spalink95c43732012-07-25 15:57:14 -0700714 _hwid_status_list_cmd_arg,
Jon Salz65266432012-07-30 19:02:49 +0800715 _upload_method_cmd_arg,
716 _add_file_cmd_arg)
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800717def Finalize(options):
718 """Verify system readiness and trigger transition into release state.
719
Hung-Te Lin6d827542012-07-19 11:50:41 +0800720 This routine first verifies system state (see verify command), modifies
721 firmware bitmaps to match locale, and then clears all of the factory-friendly
722 flags from the GBB. If everything is fine, it enables firmware write
723 protection (cannot rollback after this stage), uploads system logs & reports,
724 and sets the necessary boot flags to cause wipe of the factory image on the
725 next boot.
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800726 """
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800727 Verify(options)
728 SetFirmwareBitmapLocale({})
Hung-Te Lin6d827542012-07-19 11:50:41 +0800729 ClearGbbFlags({})
730 if options.no_write_protect:
731 logging.warn('WARNING: Firmware Write Protection is SKIPPED.')
732 _event_log.Log('wp', fw='both', status='skipped')
733 else:
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800734 EnableFwWp({})
Jon Salza0f58e02012-05-29 19:33:39 +0800735 LogSystemDetails(options)
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800736 UploadReport(options)
737 PrepareWipe(options)
738
739
740def Main():
741 """Run sub-command specified by the command line args."""
742 options = ParseCmdline(
743 'Perform Google required factory tests.',
744 CmdArg('-l', '--log', metavar='PATH',
745 help='Write logs to this file.'),
Tammo Spalink8fab5312012-05-28 18:33:30 +0800746 verbosity_cmd_arg)
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800747 SetupLogging(options.verbosity, options.log)
748 logging.debug('gooftool options: %s', repr(options))
749 try:
750 logging.debug('GOOFTOOL command %r', options.command_name)
751 options.command(options)
752 logging.info('GOOFTOOL command %r SUCCESS', options.command_name)
753 except Error, e:
754 logging.exception(e)
755 sys.exit('GOOFTOOL command %r ERROR: %s' % (options.command_name, e))
756 except Exception, e:
757 logging.exception(e)
758 sys.exit('UNCAUGHT RUNTIME EXCEPTION %s' % e)
759
760
761if __name__ == '__main__':
762 Main()