blob: 17b729ef7fc6788119e0b4c3fa06f87952c2ef41 [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
Tammo Spalink394e4492012-08-01 10:20:30 -070028from cros.factory.common import 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'
Tammo Spalink394e4492012-08-01 10:20:30 -0700182 % YamlWrite(sorted(
183 hwid_tool.ComponentSpecClasses(component_spec) &
184 hwid_tool.ComponentSpecClasses(variant_spec))))
Tammo Spalink01e11722012-07-24 10:17:54 -0700185 component_spec = hwid_tool.CombineComponentSpecs(
186 component_spec, variant_spec)
Tammo Spalink95c43732012-07-25 15:57:14 -0700187 if options.comps or options.missing:
188 map(comp_db.CompExists, options.comps)
189 map(comp_db.CompClassExists, options.missing)
190 extra_comp_spec = comp_db.CreateComponentSpec(
191 components=options.comps,
192 missing=options.missing)
193 print 'cmdline asserted components:\n%s' % extra_comp_spec.Encode()
194 if hwid_tool.ComponentSpecsConflict(component_spec, extra_comp_spec):
195 sys.exit('ERROR: multiple specifications for these components:\n%s'
Tammo Spalink394e4492012-08-01 10:20:30 -0700196 % YamlWrite(sorted(
197 hwid_tool.ComponentSpecClasses(component_spec) &
198 hwid_tool.ComponentSpecClasses(extra_comp_spec))))
Tammo Spalink95c43732012-07-25 15:57:14 -0700199 component_spec = hwid_tool.CombineComponentSpecs(
200 component_spec, extra_comp_spec)
Tammo Spalink01e11722012-07-24 10:17:54 -0700201 spec_classes = hwid_tool.ComponentSpecClasses(component_spec)
Tammo Spalink95c43732012-07-25 15:57:14 -0700202 missing_classes = set(comp_db.all_comp_classes) - spec_classes
Tammo Spalink5c699832012-07-03 17:50:39 +0800203 if missing_classes and not options.optimistic:
Tammo Spalink95c43732012-07-25 15:57:14 -0700204 non_probable_missing = missing_classes - PROBABLE_COMPONENT_CLASSES
205 if non_probable_missing:
206 sys.exit('FAILURE: these classes are necessary, were not specified '
207 'as inputs, and cannot be probed for:\n%s' %
208 YamlWrite(list(non_probable_missing)))
209 print 'probing for missing classes:'
210 print YamlWrite(list(missing_classes))
211 probe_results = Probe(target_comp_classes=list(missing_classes),
212 probe_volatile=False, probe_initial_config=False)
213 cooked_components = comp_db.MatchComponentProbeValues(
214 probe_results.found_probe_value_map)
215 if cooked_components.unmatched:
216 sys.exit('ERROR: some probed components are unrecognized:\n%s'
217 % YamlWrite(cooked_components.unmatched))
218 probed_comp_spec = comp_db.CreateComponentSpec(
219 components=cooked_components.matched,
220 missing=probe_results.missing_component_classes)
221 component_spec = hwid_tool.CombineComponentSpecs(
222 component_spec, probed_comp_spec)
223 print YamlWrite({'component data used for matching': {
224 'missing component classes': component_spec.classes_missing,
225 'found components': component_spec.components}})
226 component_data = hwid_tool.ComponentData(
227 extant_components=hwid_tool.ComponentSpecCompClassMap(
228 component_spec).keys(),
229 classes_missing=component_spec.classes_missing)
230 match_tree = device.BuildMatchTree(component_data)
231 if not match_tree:
232 sys.exit('FAILURE: NO matching BOMs found')
233 print 'potential BOMs/VARIANTs:'
234 potential_variants = set()
235 potential_volatiles = set()
236 for bom_name, variant_tree in match_tree.items():
237 print ' BOM: %-8s VARIANTS: %s' % (
238 bom_name, ', '.join(sorted(variant_tree)))
239 for variant_code in variant_tree:
240 potential_variants.add(variant_code)
241 for volatile_code in device.volatiles:
242 status = device.GetHwidStatus(bom_name, variant_code, volatile_code)
243 if status in options.status:
244 potential_volatiles.add(volatile_code)
245 print ''
246 if len(potential_variants) == 0:
247 sys.exit('FAILURE: no matching VARIANTs found')
248 if len(potential_volatiles) == 0:
249 sys.exit('FAILURE: no VOLATILEs found for potential matching BOMs/VARIANTS '
250 '(with specified status)')
Tammo Spalink394e4492012-08-01 10:20:30 -0700251 if (options.optimistic and
252 len(match_tree) == 1 and
253 len(potential_variants) == 1 and
254 len(potential_volatiles) == 1):
255 print ('MATCHING HWID: %s' % device.FmtHwid(match_tree.keys().pop(),
256 potential_variants.pop(),
257 potential_volatiles.pop()))
Tammo Spalink5c699832012-07-03 17:50:39 +0800258 return
Tammo Spalink95c43732012-07-25 15:57:14 -0700259 print ('probing VOLATILEs to resolve potential matches: %s\n' %
260 ', '.join(sorted(potential_volatiles)))
261 vol_probe_results = Probe(
262 target_comp_classes=[],
263 probe_volatile=True,
264 probe_initial_config=False)
265 cooked_volatiles = device.MatchVolatileValues(
266 vol_probe_results.found_volatile_values)
267 match_tree = device.BuildMatchTree(
268 component_data, cooked_volatiles.matched_tags)
269 matched_hwids = device.GetMatchTreeHwids(match_tree)
270 if matched_hwids:
271 for hwid in matched_hwids:
272 print 'MATCHING HWID: %s' % hwid
273 return
274 print 'exact HWID matching failed, but the following BOMs match: %s' % (
275 ', '.join(sorted(match_tree)))
276 if options.optimistic and len(match_tree) == 1:
277 bom_name = set(match_tree).pop()
Tammo Spalink5c699832012-07-03 17:50:39 +0800278 bom = device.boms[bom_name]
Tammo Spalink95c43732012-07-25 15:57:14 -0700279 variant_matches = match_tree[bom_name]
Tammo Spalink5c699832012-07-03 17:50:39 +0800280 if len(variant_matches) == 1:
281 var_code = set(variant_matches).pop()
282 elif len(bom.variants) == 1:
283 var_code = set(bom.variants).pop()
284 else:
Tammo Spalink95c43732012-07-25 15:57:14 -0700285 sys.exit('FAILURE: NO matching HWIDs found; optimistic matching failed '
286 'because there were too many variants to choose from for BOM %r'
287 % bom_name)
288 hwids = [device.FmtHwid(bom_name, var_code, vol_code)
289 for vol_code in device.volatiles
290 if device.GetHwidStatus(bom_name, var_code, vol_code)
291 in options.status]
292 for hwid in hwids:
293 print 'MATCHING HWID: %s' % hwid
Tammo Spalink5c699832012-07-03 17:50:39 +0800294 return
295 else:
Tammo Spalink95c43732012-07-25 15:57:14 -0700296 print ('optimistic matching not attempted because either it was '
297 'not requested, or because the number of BOMs was <> 1\n')
298 sys.exit('FAILURE: NO matching HWIDs found')
Tammo Spalink8fab5312012-05-28 18:33:30 +0800299
300
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800301@Command('probe',
302 CmdArg('--comps', nargs='*',
303 help='List of keys from the component_db registry.'),
304 CmdArg('--no_vol', action='store_true',
305 help='Do not probe volatile data.'),
306 CmdArg('--no_ic', action='store_true',
307 help='Do not probe initial_config data.'))
308def RunProbe(options):
309 """Print yaml-formatted breakdown of probed device properties."""
Tammo Spalink01e11722012-07-24 10:17:54 -0700310 probe_results = Probe(target_comp_classes=options.comps,
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800311 probe_volatile=not options.no_vol,
312 probe_initial_config=not options.no_ic)
Tammo Spalink3a7e9022012-06-27 14:08:40 +0800313 print probe_results.Encode()
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800314
315
Tammo Spalink214caf42012-05-28 10:45:00 +0800316@Command('verify_components',
317 _hwdb_path_cmd_arg,
Tammo Spalink5c699832012-07-03 17:50:39 +0800318 CmdArg('target_comps', nargs='*'))
Tammo Spalink214caf42012-05-28 10:45:00 +0800319def VerifyComponents(options):
320 """Verify that probable components all match entries in the component_db.
321
Tammo Spalink5c699832012-07-03 17:50:39 +0800322 Probe for each component class in the target_comps and verify
Tammo Spalink214caf42012-05-28 10:45:00 +0800323 that a corresponding match exists in the component_db -- make sure
324 that these components are present, that they have been approved, but
325 do not check against any specific BOM/HWID configurations.
326 """
Tammo Spalink5c699832012-07-03 17:50:39 +0800327 comp_db = hwid_tool.HardwareDb(options.hwdb_path).comp_db
328 if not options.target_comps:
329 sys.exit('ERROR: no target component classes specified; possible choices:\n'
330 + '\n '.join(sorted(comp_db.components)))
331 for comp_class in options.target_comps:
332 if comp_class not in comp_db.components:
333 sys.exit('ERROR: specified component class %r does not exist'
Tammo Spalink214caf42012-05-28 10:45:00 +0800334 ' in the component DB.' % comp_class)
Tammo Spalink01e11722012-07-24 10:17:54 -0700335 probe_results = Probe(target_comp_classes=options.target_comps,
Tammo Spalink214caf42012-05-28 10:45:00 +0800336 probe_volatile=False, probe_initial_config=False)
Tammo Spalink214caf42012-05-28 10:45:00 +0800337 errors = []
338 matches = []
Tammo Spalink5c699832012-07-03 17:50:39 +0800339 for comp_class in sorted(options.target_comps):
Tammo Spalink214caf42012-05-28 10:45:00 +0800340 probe_val = probe_results.found_components.get(comp_class, None)
341 if probe_val is not None:
Tammo Spalink5c699832012-07-03 17:50:39 +0800342 comp_name = comp_db.result_name_map.get(probe_val, None)
Tammo Spalink214caf42012-05-28 10:45:00 +0800343 if comp_name is not None:
344 matches.append(comp_name)
345 else:
346 errors.append('unsupported %r component found with probe result'
347 ' %r (no matching name in the component DB)' %
348 (comp_class, probe_val))
349 else:
350 errors.append('missing %r component' % comp_class)
351 if errors:
352 print '\n'.join(errors)
353 sys.exit('component verification FAILURE')
354 else:
355 print 'component verification SUCCESS'
356 print 'found components:\n %s' % '\n '.join(matches)
357
358
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800359@Command('verify_hwid',
Tammo Spalink95c43732012-07-25 15:57:14 -0700360 _hwid_status_list_cmd_arg,
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800361 _hwdb_path_cmd_arg)
362def VerifyHwid(options):
363 """Verify system HWID properties match probed device properties.
364
365 First probe components, volatile and initial_config parameters for
366 the DUT. Then use the available device data to produce a list of
367 candidate HWIDs. Then verify the HWID from the DUT is present in
368 that list. Then verify that the DUT initial config values match
369 those specified for its HWID. Finally, verify that VPD contains all
370 the necessary fields as specified by the board data, and when
371 possible verify that values are legitimate.
372 """
Tammo Spalink5c699832012-07-03 17:50:39 +0800373 def VerifyVpd(ro_vpd_keys):
374 ro_vpd = ReadRoVpd(main_fw_file)
375 for key in ro_vpd_keys:
376 if key not in ro_vpd:
377 sys.exit('Missing required VPD field: %s' % key)
Tammo Spalink01e11722012-07-24 10:17:54 -0700378 known_valid_values = KNOWN_VPD_FIELD_DATA.get(key, None)
Tammo Spalink5c699832012-07-03 17:50:39 +0800379 value = ro_vpd[key]
380 if known_valid_values is not None and value not in known_valid_values:
381 sys.exit('Invalid VPD entry : key %r, value %r' % (key, value))
382 rw_vpd = ReadRwVpd(main_fw_file)
383 _event_log.Log('vpd', ro_vpd=ro_vpd, rw_vpd=rw_vpd)
384 map(hwid_tool.Validate.Status, options.status)
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800385 main_fw_file = crosfw.LoadMainFirmware().GetFileName()
386 gbb_result = Shell('gbb_utility -g --hwid %s' % main_fw_file).stdout
Tammo Spalink95c43732012-07-25 15:57:14 -0700387 hwid_str = re.findall(r'hardware_id:(.*)', gbb_result)[0].strip()
388 hwid = hwid_tool.ParseHwid(hwid_str)
Tammo Spalink5c699832012-07-03 17:50:39 +0800389 hw_db = hwid_tool.HardwareDb(options.hwdb_path)
Tammo Spalink95c43732012-07-25 15:57:14 -0700390 print 'Verifying system HWID: %r\n' % hwid.hwid
391 device = hw_db.GetDevice(hwid.board)
392 hwid_status = device.GetHwidStatus(hwid.bom, hwid.variant, hwid.volatile)
393 if hwid_status not in options.status:
Tammo Spalink5c699832012-07-03 17:50:39 +0800394 sys.exit('HWID status must be one of [%s], found %r' %
Tammo Spalink95c43732012-07-25 15:57:14 -0700395 (', '.join(options.status, hwid_status)))
396 probe_results = Probe()
397 cooked_components = hw_db.comp_db.MatchComponentProbeValues(
398 probe_results.found_probe_value_map)
399 cooked_volatiles = device.MatchVolatileValues(
400 probe_results.found_volatile_values)
401 cooked_initial_configs = device.MatchInitialConfigValues(
402 probe_results.initial_configs)
403 component_data = hwid_tool.ComponentData(
404 extant_components=cooked_components.matched,
405 classes_missing=probe_results.missing_component_classes)
406 match_tree = device.BuildMatchTree(
407 component_data, cooked_volatiles.matched_tags)
408 matched_hwids = device.GetMatchTreeHwids(match_tree)
409 print 'HWID status: %s\n' % hwid_status
410 print 'probed system components:'
411 print YamlWrite(cooked_components.__dict__)
412 print 'missing component classes:'
413 print YamlWrite(probe_results.missing_component_classes)
414 print 'probed volatiles:'
415 print YamlWrite(cooked_volatiles.__dict__)
416 print 'probed initial_configs:'
417 print YamlWrite(cooked_initial_configs)
418 print 'hwid match tree:'
419 print YamlWrite(match_tree)
Tammo Spalink86a61c62012-05-25 15:10:35 +0800420 _event_log.Log(
Tammo Spalink5c699832012-07-03 17:50:39 +0800421 'probe',
Tammo Spalink95c43732012-07-25 15:57:14 -0700422 found_components=cooked_components.__dict__,
423 missing_component_classes=probe_results.missing_component_classes,
424 volatiles=cooked_volatiles.__dict__,
425 initial_configs=cooked_initial_configs)
426 if hwid.hwid not in matched_hwids:
Tammo Spalink5c699832012-07-03 17:50:39 +0800427 err_msg = 'HWID verification FAILED.\n'
Tammo Spalink95c43732012-07-25 15:57:14 -0700428 if cooked_components.unmatched:
Tammo Spalink5c699832012-07-03 17:50:39 +0800429 sys.exit(err_msg + 'some components could not be indentified:\n%s' %
Tammo Spalink95c43732012-07-25 15:57:14 -0700430 YamlWrite(cooked_components.unmatched))
431 if not match_tree:
Tammo Spalink5c699832012-07-03 17:50:39 +0800432 sys.exit(err_msg + 'no matching boms were found for components:\n%s' %
Tammo Spalink95c43732012-07-25 15:57:14 -0700433 component_data.Encode())
434 if hwid.bom not in match_tree:
Tammo Spalink5c699832012-07-03 17:50:39 +0800435 sys.exit(err_msg + 'matching boms [%s] do not include target bom %r' %
Tammo Spalink95c43732012-07-25 15:57:14 -0700436 (', '.join(sorted(match_tree)), hwid.bom))
437 err_msg += 'target bom %r matches components' % hwid.bom
438 if hwid.bom not in device.IntersectBomsAndInitialConfigs(
439 cooked_initial_configs):
Tammo Spalink5c699832012-07-03 17:50:39 +0800440 sys.exit(err_msg + ', but failed initial config verification')
Tammo Spalink95c43732012-07-25 15:57:14 -0700441 matched_variants = match_tree.get(hwid.bom, {})
442 if hwid.variant not in matched_variants:
Tammo Spalink5c699832012-07-03 17:50:39 +0800443 sys.exit(err_msg + ', but target variant_code %r did not match' %
Tammo Spalink95c43732012-07-25 15:57:14 -0700444 hwid.variant)
445 matched_volatiles = matched_variants.get(hwid.variant, {})
446 if hwid.volatile not in matched_volatiles:
Tammo Spalink5c699832012-07-03 17:50:39 +0800447 sys.exit(err_msg + ', but target volatile_code %r did not match' %
Tammo Spalink95c43732012-07-25 15:57:14 -0700448 hwid.volatile)
449 found_status = matched_volatiles.get(hwid.volatile, None)
Tammo Spalink5c699832012-07-03 17:50:39 +0800450 sys.exit(err_msg + ', but hwid status %r was unacceptable' % found_status)
Tammo Spalink95c43732012-07-25 15:57:14 -0700451 VerifyVpd(device.vpd_ro_fields)
Tammo Spalink5c699832012-07-03 17:50:39 +0800452 _event_log.Log('verified_hwid', hwid=hwid)
Tammo Spalink95c43732012-07-25 15:57:14 -0700453 print 'Verification SUCCESS!'
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800454
455
456@Command('verify_keys')
Tammo Spalink01e11722012-07-24 10:17:54 -0700457def VerifyKeys(options): # pylint: disable=W0613
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800458 """Verify keys in firmware and SSD match."""
Tammo Spalink461ddce2012-05-10 19:28:55 +0800459 script = FindScript('verify_keys.sh')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800460 kernel_device = GetReleaseKernelPartitionPath()
461 main_fw_file = crosfw.LoadMainFirmware().GetFileName()
462 result = Shell('%s %s %s' % (script, kernel_device, main_fw_file))
463 if not result.success:
464 raise Error, '%r failed, stderr: %r' % (script, result.stderr)
465
466
467@Command('set_fw_bitmap_locale')
Tammo Spalink01e11722012-07-24 10:17:54 -0700468def SetFirmwareBitmapLocale(options): # pylint: disable=W0613
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800469 """Use VPD locale value to set firmware bitmap default language."""
470 image_file = crosfw.LoadMainFirmware().GetFileName()
471 locale = ReadRoVpd(image_file).get('initial_locale', None)
472 if locale is None:
473 raise Error, 'Missing initial_locale VPD.'
474 bitmap_locales = []
475 with NamedTemporaryFile() as f:
476 Shell('gbb_utility -g --bmpfv=%s %s' % (f.name, image_file))
Tammo Spalink01e11722012-07-24 10:17:54 -0700477 bmpblk_data = unpack_bmpblock(f.read())
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800478 bitmap_locales = bmpblk_data.get('locales', bitmap_locales)
479 # Some locale values are just a language code and others are a
480 # hyphen-separated language code and country code pair. We care
481 # only about the language code part.
482 language_code = locale.partition('-')[0]
483 if language_code not in bitmap_locales:
484 raise Error, ('Firmware bitmaps do not contain support for the specified '
485 'initial locale language %r' % language_code)
486 else:
487 locale_index = bitmap_locales.index(language_code)
488 logging.info('Firmware bitmap initial locale set to %d (%s).',
489 locale_index, bitmap_locales[locale_index])
490 Shell('crossystem loc_idx=%d' % locale_index)
491
492
493@Command('verify_system_time')
Tammo Spalink01e11722012-07-24 10:17:54 -0700494def VerifySystemTime(options): # pylint: disable=W0613
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800495 """Verify system time is later than release filesystem creation time."""
Tammo Spalink461ddce2012-05-10 19:28:55 +0800496 script = FindScript('verify_system_time.sh')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800497 rootfs_device = GetReleaseRootPartitionPath()
498 result = Shell('%s %s' % (script, rootfs_device))
499 if not result.success:
500 raise Error, '%r failed, stderr: %r' % (script, result.stderr)
501
502
503@Command('verify_rootfs')
Tammo Spalink01e11722012-07-24 10:17:54 -0700504def VerifyRootFs(options): # pylint: disable=W0613
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800505 """Verify rootfs on SSD is valid by checking hash."""
Tammo Spalink461ddce2012-05-10 19:28:55 +0800506 script = FindScript('verify_rootfs.sh')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800507 rootfs_device = GetReleaseRootPartitionPath()
508 result = Shell('%s %s' % (script, rootfs_device))
509 if not result.success:
510 raise Error, '%r failed, stderr: %r' % (script, result.stderr)
511
512
513@Command('verify_switch_wp')
Tammo Spalink01e11722012-07-24 10:17:54 -0700514def VerifyWpSwitch(options): # pylint: disable=W0613
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800515 """Verify hardware write protection switch is enabled."""
516 if Shell('crossystem wpsw_cur').stdout.strip() != '1':
Hung-Te Lin6d827542012-07-19 11:50:41 +0800517 raise Error, 'write protection switch is disabled'
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800518
519
520@Command('verify_switch_dev')
Tammo Spalink01e11722012-07-24 10:17:54 -0700521def VerifyDevSwitch(options): # pylint: disable=W0613
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800522 """Verify developer switch is disabled."""
Hung-Te Lind7d34722012-07-26 16:48:35 +0800523 VBSD_HONOR_VIRT_DEV_SWITCH = 0x400
524 flags = int(Shell('crossystem vdat_flags').stdout.strip(), 0)
525 if (flags & VBSD_HONOR_VIRT_DEV_SWITCH) != 0:
526 # System is using virtual developer switch. That will be handled in
527 # prepare_wipe.sh by setting "crossystem disable_dev_request=1" -- although
528 # we can't verify that until next reboot, because the real values are stored
529 # in TPM.
530 logging.warn('VerifyDevSwitch: No physical switch.')
531 _event_log.Log('switch_dev', type='virtual switch')
532 return
533 if Shell('crossystem devsw_cur').stdout.strip() != '0':
534 raise Error, 'developer mode is not disabled'
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800535
536
537@Command('write_protect')
Tammo Spalink01e11722012-07-24 10:17:54 -0700538def EnableFwWp(options): # pylint: disable=W0613
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800539 """Enable then verify firmware write protection."""
540
Hung-Te Linb21c6682012-08-01 13:53:57 +0800541 def CalculateLegacyRange(fw_type, length, section_data,
542 section_name):
Hung-Te Lin7ea39e82012-07-31 18:39:33 +0800543 ro_size = length / 2
544 ro_a = int(section_data[0] / ro_size)
545 ro_b = int((section_data[0] + section_data[1] - 1) / ro_size)
546 if ro_a != ro_b:
547 raise Error("%s firmware section %s has illegal size" %
Hung-Te Linb21c6682012-08-01 13:53:57 +0800548 (fw_type, section_name))
Hung-Te Lin7ea39e82012-07-31 18:39:33 +0800549 ro_offset = ro_a * ro_size
550 return (ro_offset, ro_size)
551
552 def WriteProtect(fw_file_path, fw_type, legacy_section):
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800553 """Calculate protection size, then invoke flashrom.
554
555 Our supported chips only allow write protecting half their total
556 size, so we parition the flash chipset space accordingly.
557 """
558 raw_image = open(fw_file_path, 'rb').read()
Hung-Te Lin7ea39e82012-07-31 18:39:33 +0800559 wp_section = 'WP_RO'
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800560 image = crosfw.FirmwareImage(raw_image)
Hung-Te Lin7ea39e82012-07-31 18:39:33 +0800561 if image.has_section(wp_section):
562 section_data = image.get_section_area(wp_section)
563 ro_offset = section_data[0]
564 ro_size = section_data[1]
565 elif image.has_section(legacy_section):
566 section_data = image.get_section_area(legacy_section)
567 (ro_offset, ro_size) = CalculateLegacyRange(
Hung-Te Linb21c6682012-08-01 13:53:57 +0800568 fw_type, len(raw_image), section_data, legacy_section)
Hung-Te Lin7ea39e82012-07-31 18:39:33 +0800569 else:
570 raise Error('could not find %s firmware section %s or %s' %
571 (fw_type, wp_section, legacy_section))
572
573 logging.debug('write protecting %s [off=%x size=%x]', fw_type,
574 ro_offset, ro_size)
575 crosfw.Flashrom(fw_type).EnableWriteProtection(ro_offset, ro_size)
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800576
577 WriteProtect(crosfw.LoadMainFirmware().GetFileName(), 'main', 'RO_SECTION')
Tammo Spalink86a61c62012-05-25 15:10:35 +0800578 _event_log.Log('wp', fw='main')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800579 ec_fw_file = crosfw.LoadEcFirmware().GetFileName()
580 if ec_fw_file is not None:
581 WriteProtect(ec_fw_file, 'ec', 'EC_RO')
Tammo Spalink86a61c62012-05-25 15:10:35 +0800582 _event_log.Log('wp', fw='ec')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800583 else:
584 logging.warning('EC not write protected (seems there is no EC flash).')
585
586
587@Command('clear_gbb_flags')
Tammo Spalink01e11722012-07-24 10:17:54 -0700588def ClearGbbFlags(options): # pylint: disable=W0613
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800589 """Zero out the GBB flags, in preparation for transition to release state.
590
591 No GBB flags are set in release/shipping state, but they are useful
592 for factory/development. See "gbb_utility --flags" for details.
593 """
Tammo Spalink461ddce2012-05-10 19:28:55 +0800594 script = FindScript('clear_gbb_flags.sh')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800595 result = Shell(script)
596 if not result.success:
597 raise Error, '%r failed, stderr: %r' % (script, result.stderr)
Tammo Spalink86a61c62012-05-25 15:10:35 +0800598 _event_log.Log('clear_gbb_flags')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800599
600
601@Command('prepare_wipe',
602 CmdArg('--fast', action='store_true',
603 help='use non-secure but faster wipe method.'))
604def PrepareWipe(options):
605 """Prepare system for transition to release state in next reboot."""
Tammo Spalink461ddce2012-05-10 19:28:55 +0800606 script = FindScript('prepare_wipe.sh')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800607 tag = 'fast' if options.fast else ''
608 rootfs_device = GetReleaseRootPartitionPath()
609 result = Shell('FACTORY_WIPE_TAGS=%s %s %s' % (tag, script, rootfs_device))
610 if not result.success:
611 raise Error, '%r failed, stderr: %r' % (script, result.stderr)
612
613
614@Command('verify',
Hung-Te Lin6d827542012-07-19 11:50:41 +0800615 CmdArg('--no_write_protect', action='store_true',
616 help='Do not check write protection switch state.'),
Tammo Spalink95c43732012-07-25 15:57:14 -0700617 _hwid_status_list_cmd_arg,
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800618 _hwdb_path_cmd_arg)
619def Verify(options):
620 """Verifies if whole factory process is ready for finalization.
621
622 This routine performs all the necessary checks to make sure the
623 device is ready to be finalized, but does not modify state. These
624 checks include dev switch, firmware write protection switch, hwid,
625 system time, keys, and root file system.
626 """
Hung-Te Lin6d827542012-07-19 11:50:41 +0800627 if not options.no_write_protect:
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800628 VerifyWpSwitch({})
Hung-Te Lin6d827542012-07-19 11:50:41 +0800629 VerifyDevSwitch({})
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800630 VerifyHwid(options)
631 VerifySystemTime({})
632 VerifyKeys({})
633 VerifyRootFs({})
634
635
Tammo Spalink86a61c62012-05-25 15:10:35 +0800636@Command('log_system_details')
Tammo Spalink01e11722012-07-24 10:17:54 -0700637def LogSystemDetails(options): # pylint: disable=W0613
Tammo Spalink86a61c62012-05-25 15:10:35 +0800638 """Write miscellaneous system details to the event log."""
639 raw_cs_data = Shell('crossystem').stdout.strip().splitlines()
640 # The crossytem output contains many lines like:
641 # 'key = value # description'
642 # Use regexps to pull out the key-value pairs and build a dict.
643 cs_data = dict((k, v.strip()) for k, v in
644 map(lambda x: re.findall(r'\A(\S+)\s+=\s+(.*)#.*\Z', x)[0],
645 raw_cs_data))
646 _event_log.Log(
647 'system_details',
648 platform_name=Shell('mosys platform name').stdout.strip(),
649 crossystem=cs_data,
650 modem_status=Shell('modem status').stdout.splitlines(),
651 ec_wp_status=Shell(
652 'flashrom -p internal:bus=lpc --get-size 2>/dev/null && '
653 'flashrom -p internal:bus=lpc --wp-status || '
654 'echo "EC is not available."').stdout,
655 bios_wp_status = Shell(
656 'flashrom -p internal:bus=spi --wp-status').stdout)
657
658
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800659_upload_method_cmd_arg = CmdArg(
660 '--upload_method', metavar='METHOD:PARAM',
661 help=('How to perform the upload. METHOD should be one of '
662 '{ftp, shopfloor, curl, cpfe, custom}.'))
Jon Salz65266432012-07-30 19:02:49 +0800663_add_file_cmd_arg = CmdArg(
664 '--add_file', metavar='FILE', action='append',
665 help='Extra file to include in report (must be an absolute path)')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800666
667@Command('upload_report',
Jon Salz65266432012-07-30 19:02:49 +0800668 _upload_method_cmd_arg,
669 _add_file_cmd_arg)
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800670def UploadReport(options):
671 """Create and a report containing key device details."""
Hung-Te Lin6bd16472012-06-20 16:26:47 +0800672 def NormalizeAsFileName(token):
673 return re.sub(r'\W+', '', token).strip()
Tammo Spalink86a61c62012-05-25 15:10:35 +0800674 ro_vpd = ReadRoVpd(crosfw.LoadMainFirmware().GetFileName())
675 device_sn = ro_vpd.get('serial_number', None)
676 if device_sn is None:
677 logging.warning('RO_VPD missing device serial number')
678 device_sn = 'MISSING_SN_' + TimedUuid()
Hung-Te Lin6bd16472012-06-20 16:26:47 +0800679 target_name = '%s_%s.tbz2' % (time.strftime('%Y%m%dT%H%M%SZ', time.gmtime()),
680 NormalizeAsFileName(device_sn))
Tammo Spalink86a61c62012-05-25 15:10:35 +0800681 target_path = os.path.join(gettempdir(), target_name)
682 # Intentionally ignoring dotfiles in EVENT_LOG_DIR.
683 tar_cmd = 'cd %s ; tar cjf %s *' % (EVENT_LOG_DIR, target_path)
684 tar_cmd += ' --add-file %s' % FACTORY_LOG_PATH
Jon Salz65266432012-07-30 19:02:49 +0800685 if options.add_file:
686 for f in options.add_file:
687 # Require absolute paths since the tar command may change the
688 # directory.
689 if not f.startswith('/'):
690 raise Error('Not an absolute path: %s' % f)
691 if not os.path.exists(f):
692 raise Error('File does not exist: %s' % f)
693 tar_cmd += ' --add-file %s' % pipes.quote(f)
Tammo Spalink86a61c62012-05-25 15:10:35 +0800694 cmd_result = Shell(tar_cmd)
695 if not cmd_result.success:
696 raise Error('unable to tar event logs, cmd %r failed, stderr: %r' %
697 (tar_cmd, cmd_result.stderr))
698 if options.upload_method is None or options.upload_method == 'none':
699 logging.warning('REPORT UPLOAD SKIPPED (report left at %s)', target_path)
700 return
701 method, param = options.upload_method.split(':', 1)
702 if method == 'shopfloor':
703 report_upload.ShopFloorUpload(target_path, param)
704 elif method == 'ftp':
Jay Kim360c1dd2012-06-25 10:58:11 -0700705 report_upload.FtpUpload(target_path, 'ftp:' + param)
Tammo Spalink86a61c62012-05-25 15:10:35 +0800706 elif method == 'ftps':
707 report_upload.CurlUrlUpload(target_path, '--ftp-ssl-reqd ftp:%s' % param)
708 elif method == 'cpfe':
709 report_upload.CpfeUpload(target_path, param)
710 else:
711 raise Error('unknown report upload method %r', method)
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800712
713
714@Command('finalize',
Hung-Te Lin6d827542012-07-19 11:50:41 +0800715 CmdArg('--no_write_protect', action='store_true',
716 help='Do not enable firmware write protection.'),
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800717 CmdArg('--fast', action='store_true',
718 help='use non-secure but faster wipe method.'),
719 _hwdb_path_cmd_arg,
Tammo Spalink95c43732012-07-25 15:57:14 -0700720 _hwid_status_list_cmd_arg,
Jon Salz65266432012-07-30 19:02:49 +0800721 _upload_method_cmd_arg,
722 _add_file_cmd_arg)
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800723def Finalize(options):
724 """Verify system readiness and trigger transition into release state.
725
Hung-Te Lin6d827542012-07-19 11:50:41 +0800726 This routine first verifies system state (see verify command), modifies
727 firmware bitmaps to match locale, and then clears all of the factory-friendly
728 flags from the GBB. If everything is fine, it enables firmware write
729 protection (cannot rollback after this stage), uploads system logs & reports,
730 and sets the necessary boot flags to cause wipe of the factory image on the
731 next boot.
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800732 """
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800733 Verify(options)
734 SetFirmwareBitmapLocale({})
Hung-Te Lin6d827542012-07-19 11:50:41 +0800735 ClearGbbFlags({})
736 if options.no_write_protect:
737 logging.warn('WARNING: Firmware Write Protection is SKIPPED.')
738 _event_log.Log('wp', fw='both', status='skipped')
739 else:
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800740 EnableFwWp({})
Jon Salza0f58e02012-05-29 19:33:39 +0800741 LogSystemDetails(options)
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800742 UploadReport(options)
743 PrepareWipe(options)
744
745
746def Main():
747 """Run sub-command specified by the command line args."""
748 options = ParseCmdline(
749 'Perform Google required factory tests.',
750 CmdArg('-l', '--log', metavar='PATH',
751 help='Write logs to this file.'),
Tammo Spalink8fab5312012-05-28 18:33:30 +0800752 verbosity_cmd_arg)
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800753 SetupLogging(options.verbosity, options.log)
754 logging.debug('gooftool options: %s', repr(options))
755 try:
756 logging.debug('GOOFTOOL command %r', options.command_name)
757 options.command(options)
758 logging.info('GOOFTOOL command %r SUCCESS', options.command_name)
759 except Error, e:
760 logging.exception(e)
761 sys.exit('GOOFTOOL command %r ERROR: %s' % (options.command_name, e))
762 except Exception, e:
763 logging.exception(e)
764 sys.exit('UNCAUGHT RUNTIME EXCEPTION %s' % e)
765
766
767if __name__ == '__main__':
768 Main()