blob: 7c951ad16c5c1a0ba6528e7ba98c0f1cb7aea159 [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
32from cros.factory.gooftool.probe import Probe
33from 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."""
115 logging.debug('writing hwid string %r', options.hwid)
116 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 Spalink9a96b8a2012-04-03 11:10:41 +0800121
122
Tammo Spalink8fab5312012-05-28 18:33:30 +0800123_hwdb_path_cmd_arg = CmdArg(
124 '--hwdb_path', metavar='PATH',
125 default=hwid_tool.DEFAULT_HWID_DATA_PATH,
126 help='Path to the HWID database.')
127
128
129@Command('probe_hwids',
130 _hwdb_path_cmd_arg,
131 CmdArg('-b', '--board', metavar='BOARD',
132 help='BOARD name', required=True),
133 CmdArg('--bom', metavar='BOM', help='BOM name'),
134 CmdArg('--variant', metavar='VARIANT', help='VARIANT code'),
Tammo Spalink5c699832012-07-03 17:50:39 +0800135 CmdArg('--optimistic', action='store_true',
136 help='do not probe; assume singletons match'),
137 CmdArg('--stdin_comp_map', action='store_true'),
138 CmdArg('--status', nargs='*', default=['supported'],
Tammo Spalink8fab5312012-05-28 18:33:30 +0800139 help='consider only HWIDs with this status'))
Tammo Spalink5c699832012-07-03 17:50:39 +0800140# TODO(tammo): Add a 'hw-only probe' option that does not probe firmware.
Tammo Spalink8fab5312012-05-28 18:33:30 +0800141def ProbeHwid(options):
142 """Determine a list of possible HWIDs using provided args and probeing.
143
144 VOLATILE can always be determined by probing. To get a unique
145 result, VARIANT must be specified for all cases where the matching
146 BOM has more than one associated variant code, otherwise all HWID
147 variants will be returned. Both VARIANT and BOM information can
Tammo Spalink5c699832012-07-03 17:50:39 +0800148 alternatively be specified using the --stdin_comp_map argument,
149 which allows specifying a list of
Tammo Spalink8fab5312012-05-28 18:33:30 +0800150
151 component-class: canonical-component-name
152
153 pairs on stdin, one per line (yaml format). Based on what is known
Tammo Spalink5c699832012-07-03 17:50:39 +0800154 from BOM and stdin_comp_map, determine a list of components to probe
155 for, and use those probe results to resolve a list of matching
156 HWIDs. If no boms, components, or variant codes are specified, then
157 a list of all HWIDs that match probable components will be returned.
Tammo Spalink8fab5312012-05-28 18:33:30 +0800158
159 Returns (on stdout): A list of HWIDs that match the available probe
160 results and argument contraints, one per line.
161 """
Tammo Spalink5c699832012-07-03 17:50:39 +0800162 map(hwid_tool.Validate.Status, options.status)
Tammo Spalink01e11722012-07-24 10:17:54 -0700163 hw_db = hwid_tool.HardwareDb(options.data_path)
Tammo Spalink5c699832012-07-03 17:50:39 +0800164 comp_db = hw_db.comp_db
Tammo Spalink3a7e9022012-06-27 14:08:40 +0800165 device = hw_db.GetDevice(options.board)
Tammo Spalink5c699832012-07-03 17:50:39 +0800166 component_spec = hwid_tool.ComponentSpec.New()
Tammo Spalink8fab5312012-05-28 18:33:30 +0800167 if options.bom:
Tammo Spalink5c699832012-07-03 17:50:39 +0800168 device.BomExists(options.bom)
Tammo Spalink01e11722012-07-24 10:17:54 -0700169 component_spec = hwid_tool.CombineComponentSpecs(
Tammo Spalink5c699832012-07-03 17:50:39 +0800170 component_spec, device.boms[options.bom].primary)
Tammo Spalink8fab5312012-05-28 18:33:30 +0800171 if options.variant:
Tammo Spalink5c699832012-07-03 17:50:39 +0800172 device.VariantExists(options.variant)
173 variant_spec = device.variants[options.variant]
Tammo Spalink01e11722012-07-24 10:17:54 -0700174 if hwid_tool.ComponentSpecsConflict(component_spec, variant_spec):
175 # TODO(tammo): This error meesage arg is wrong; fix this when
176 # also making the whole function work (it is definitely broken).
Tammo Spalink5c699832012-07-03 17:50:39 +0800177 sys.exit('ERROR: multiple specifications for %r components'
Tammo Spalink01e11722012-07-24 10:17:54 -0700178 ' (both VARIANT and BOM)' % component_spec)
179 component_spec = hwid_tool.CombineComponentSpecs(
180 component_spec, variant_spec)
Tammo Spalink5c699832012-07-03 17:50:39 +0800181 if options.stdin_comp_map:
Tammo Spalink8fab5312012-05-28 18:33:30 +0800182 input_map = YamlRead(sys.stdin.read())
183 logging.info('stdin component map: %r', input_map)
Tammo Spalink01e11722012-07-24 10:17:54 -0700184 spec_classes = hwid_tool.ComponentSpecClasses(component_spec)
Tammo Spalink8fab5312012-05-28 18:33:30 +0800185 for key, value in input_map.items():
Tammo Spalink01e11722012-07-24 10:17:54 -0700186 if key not in comp_db.all_comp_classes:
Tammo Spalink8fab5312012-05-28 18:33:30 +0800187 sys.exit('ERROR: unknown component class %r (from stdin)' % key)
Tammo Spalink5c699832012-07-03 17:50:39 +0800188 if value not in comp_db.all_comp_names:
Tammo Spalink8fab5312012-05-28 18:33:30 +0800189 sys.exit('ERROR: unkown component name %r (from stdin)' % value)
Tammo Spalink5c699832012-07-03 17:50:39 +0800190 if key in spec_classes:
Tammo Spalink8fab5312012-05-28 18:33:30 +0800191 sys.exit('ERROR: multiple specifications for %r components'
192 ' (stdin and BOM/VARIANT)' % key)
Tammo Spalink5c699832012-07-03 17:50:39 +0800193 component_spec.components.update(input_map)
Tammo Spalink01e11722012-07-24 10:17:54 -0700194 logging.info('component spec used for matching:\n%s', component_spec.Encode)
195 spec_classes = hwid_tool.ComponentSpecClasses(component_spec)
Tammo Spalink5c699832012-07-03 17:50:39 +0800196 missing_classes = list(set(comp_db.all_comp_classes) - spec_classes)
197 if missing_classes and not options.optimistic:
198 logging.info('probing for missing classes %s', ', '.join(missing_classes))
Tammo Spalink01e11722012-07-24 10:17:54 -0700199 probe_results = Probe(target_comp_classes=missing_classes,
Tammo Spalink5c699832012-07-03 17:50:39 +0800200 probe_volatile=True, probe_initial_config=False)
201 else:
202 probe_results = hwid_tool.ProbeResults(
203 found_components=component_spec.components,
204 missing_component_classes=component_spec.classes_missing,
205 volatiles={}, initial_configs={})
206 cooked_results = hwid_tool.CookedProbeResults(comp_db, device, probe_results)
207 if not cooked_results.match_tree:
208 sys.exit('NO matching BOMs found')
209 if cooked_results.matched_hwids:
210 print '\n'.join(cooked_results.matched_hwids)
211 return
Tammo Spalink01e11722012-07-24 10:17:54 -0700212 logging.info('exact HWID matching failed, but the following BOMs match: %s',
Tammo Spalink5c699832012-07-03 17:50:39 +0800213 ', '.join(sorted(cooked_results.match_tree)))
214 if options.optimistic and len(cooked_results.match_tree) == 1:
215 bom_name = set(cooked_results.match_tree).pop()
216 bom = device.boms[bom_name]
217 variant_matches = cooked_results.match_tree[bom_name]
218 if len(variant_matches) == 1:
219 var_code = set(variant_matches).pop()
220 elif len(bom.variants) == 1:
221 var_code = set(bom.variants).pop()
222 else:
223 sys.exit('NO matching HWIDs found; optimistic matching failed because '
224 'there were too many variants to choose from for BOM %r' %
225 bom_name)
226 print '\n'.join(
227 device.FmtHwid(bom_name, var_code, vol_code)
228 for vol_code in device.volatiles
229 if device.GetHwidStatus(bom_name, var_code, vol_code)
230 in options.status)
231 return
232 else:
233 logging.info('optimistic matching not attempted because either it was '
234 'not requested, or because the number of BOMs was <> 1')
Tammo Spalink01e11722012-07-24 10:17:54 -0700235 print 'NO matching HWIDs found'
Tammo Spalink8fab5312012-05-28 18:33:30 +0800236
237
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800238@Command('probe',
239 CmdArg('--comps', nargs='*',
240 help='List of keys from the component_db registry.'),
241 CmdArg('--no_vol', action='store_true',
242 help='Do not probe volatile data.'),
243 CmdArg('--no_ic', action='store_true',
244 help='Do not probe initial_config data.'))
245def RunProbe(options):
246 """Print yaml-formatted breakdown of probed device properties."""
Tammo Spalink01e11722012-07-24 10:17:54 -0700247 probe_results = Probe(target_comp_classes=options.comps,
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800248 probe_volatile=not options.no_vol,
249 probe_initial_config=not options.no_ic)
Tammo Spalink3a7e9022012-06-27 14:08:40 +0800250 print probe_results.Encode()
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800251
252
Tammo Spalink214caf42012-05-28 10:45:00 +0800253@Command('verify_components',
254 _hwdb_path_cmd_arg,
Tammo Spalink5c699832012-07-03 17:50:39 +0800255 CmdArg('target_comps', nargs='*'))
Tammo Spalink214caf42012-05-28 10:45:00 +0800256def VerifyComponents(options):
257 """Verify that probable components all match entries in the component_db.
258
Tammo Spalink5c699832012-07-03 17:50:39 +0800259 Probe for each component class in the target_comps and verify
Tammo Spalink214caf42012-05-28 10:45:00 +0800260 that a corresponding match exists in the component_db -- make sure
261 that these components are present, that they have been approved, but
262 do not check against any specific BOM/HWID configurations.
263 """
Tammo Spalink5c699832012-07-03 17:50:39 +0800264 comp_db = hwid_tool.HardwareDb(options.hwdb_path).comp_db
265 if not options.target_comps:
266 sys.exit('ERROR: no target component classes specified; possible choices:\n'
267 + '\n '.join(sorted(comp_db.components)))
268 for comp_class in options.target_comps:
269 if comp_class not in comp_db.components:
270 sys.exit('ERROR: specified component class %r does not exist'
Tammo Spalink214caf42012-05-28 10:45:00 +0800271 ' in the component DB.' % comp_class)
Tammo Spalink01e11722012-07-24 10:17:54 -0700272 probe_results = Probe(target_comp_classes=options.target_comps,
Tammo Spalink214caf42012-05-28 10:45:00 +0800273 probe_volatile=False, probe_initial_config=False)
Tammo Spalink214caf42012-05-28 10:45:00 +0800274 errors = []
275 matches = []
Tammo Spalink5c699832012-07-03 17:50:39 +0800276 for comp_class in sorted(options.target_comps):
Tammo Spalink214caf42012-05-28 10:45:00 +0800277 probe_val = probe_results.found_components.get(comp_class, None)
278 if probe_val is not None:
Tammo Spalink5c699832012-07-03 17:50:39 +0800279 comp_name = comp_db.result_name_map.get(probe_val, None)
Tammo Spalink214caf42012-05-28 10:45:00 +0800280 if comp_name is not None:
281 matches.append(comp_name)
282 else:
283 errors.append('unsupported %r component found with probe result'
284 ' %r (no matching name in the component DB)' %
285 (comp_class, probe_val))
286 else:
287 errors.append('missing %r component' % comp_class)
288 if errors:
289 print '\n'.join(errors)
290 sys.exit('component verification FAILURE')
291 else:
292 print 'component verification SUCCESS'
293 print 'found components:\n %s' % '\n '.join(matches)
294
295
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800296@Command('verify_hwid',
Tammo Spalink5c699832012-07-03 17:50:39 +0800297 CmdArg('--status', nargs='*', default=['supported'],
298 help='allow only HWIDs with these status values'),
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800299 _hwdb_path_cmd_arg)
300def VerifyHwid(options):
301 """Verify system HWID properties match probed device properties.
302
303 First probe components, volatile and initial_config parameters for
304 the DUT. Then use the available device data to produce a list of
305 candidate HWIDs. Then verify the HWID from the DUT is present in
306 that list. Then verify that the DUT initial config values match
307 those specified for its HWID. Finally, verify that VPD contains all
308 the necessary fields as specified by the board data, and when
309 possible verify that values are legitimate.
310 """
Tammo Spalink5c699832012-07-03 17:50:39 +0800311 def VerifyVpd(ro_vpd_keys):
312 ro_vpd = ReadRoVpd(main_fw_file)
313 for key in ro_vpd_keys:
314 if key not in ro_vpd:
315 sys.exit('Missing required VPD field: %s' % key)
Tammo Spalink01e11722012-07-24 10:17:54 -0700316 known_valid_values = KNOWN_VPD_FIELD_DATA.get(key, None)
Tammo Spalink5c699832012-07-03 17:50:39 +0800317 value = ro_vpd[key]
318 if known_valid_values is not None and value not in known_valid_values:
319 sys.exit('Invalid VPD entry : key %r, value %r' % (key, value))
320 rw_vpd = ReadRwVpd(main_fw_file)
321 _event_log.Log('vpd', ro_vpd=ro_vpd, rw_vpd=rw_vpd)
322 map(hwid_tool.Validate.Status, options.status)
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800323 main_fw_file = crosfw.LoadMainFirmware().GetFileName()
324 gbb_result = Shell('gbb_utility -g --hwid %s' % main_fw_file).stdout
325 hwid = re.findall(r'hardware_id:(.*)', gbb_result)[0].strip()
Tammo Spalink5c699832012-07-03 17:50:39 +0800326 hw_db = hwid_tool.HardwareDb(options.hwdb_path)
327 hwid_data = hw_db.GetHwidData(hwid)
328 logging.info('Verifying system HWID: %r', hwid_data.hwid)
329 if hwid_data.status not in options.status:
330 sys.exit('HWID status must be one of [%s], found %r' %
331 (', '.join(options.status, hwid_data.status)))
332 logging.debug('expected system properties:\n%s', hwid_data.Encode())
333 device = hw_db.GetDevice(hwid_data.board_name)
334 cooked_probe_results = hwid_tool.CookedProbeResults(
Tammo Spalink01e11722012-07-24 10:17:54 -0700335 hw_db.comp_db, device, Probe())
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800336 logging.debug('found system properties:\n%s',
Tammo Spalink01e11722012-07-24 10:17:54 -0700337 YamlWrite(cooked_probe_results.__dict__))
Tammo Spalink86a61c62012-05-25 15:10:35 +0800338 _event_log.Log(
Tammo Spalink5c699832012-07-03 17:50:39 +0800339 'probe',
Tammo Spalink01e11722012-07-24 10:17:54 -0700340 found_components=cooked_probe_results.found_components,
341 missing_component_classes=cooked_probe_results.missing_component_classes,
342 volatiles=cooked_probe_results.volatiles,
343 initial_configs=cooked_probe_results.initial_configs)
Tammo Spalink5c699832012-07-03 17:50:39 +0800344 if hwid not in cooked_probe_results.matched_hwids:
345 err_msg = 'HWID verification FAILED.\n'
346 if cooked_probe_results.unmatched_components:
347 sys.exit(err_msg + 'some components could not be indentified:\n%s' %
348 YamlWrite(cooked_probe_results.unmatched_components))
349 if not cooked_probe_results.match_tree:
350 sys.exit(err_msg + 'no matching boms were found for components:\n%s' %
351 cooked_probe_results.component_data.Encode())
352 if hwid.bom_name not in cooked_probe_results.match_tree:
353 sys.exit(err_msg + 'matching boms [%s] do not include target bom %r' %
354 (', '.join(sorted(cooked_probe_results.match_tree)),
355 hwid_data.bom))
356 err_msg += 'target bom %r matches components' % hwid_data.bom_name
357 if hwid.bom_name not in device.matched_ic_boms:
358 sys.exit(err_msg + ', but failed initial config verification')
359 match_tree = cooked_probe_results.match_tree
360 matched_variants = match_tree.get(hwid_data.bom_name, {})
361 if hwid.variant_code not in matched_variants:
362 sys.exit(err_msg + ', but target variant_code %r did not match' %
363 hwid_data.variant_code)
364 matched_volatiles = matched_variants.get(hwid_data.variant_code, {})
365 if hwid.volatile_code not in matched_volatiles:
366 sys.exit(err_msg + ', but target volatile_code %r did not match' %
367 hwid_data.volatile_code)
368 found_status = matched_volatiles.get(hwid_data.volatile_code, None)
369 sys.exit(err_msg + ', but hwid status %r was unacceptable' % found_status)
370 VerifyVpd(hwid_data.ro_vpds)
371 _event_log.Log('verified_hwid', hwid=hwid)
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800372
373
374@Command('verify_keys')
Tammo Spalink01e11722012-07-24 10:17:54 -0700375def VerifyKeys(options): # pylint: disable=W0613
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800376 """Verify keys in firmware and SSD match."""
Tammo Spalink461ddce2012-05-10 19:28:55 +0800377 script = FindScript('verify_keys.sh')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800378 kernel_device = GetReleaseKernelPartitionPath()
379 main_fw_file = crosfw.LoadMainFirmware().GetFileName()
380 result = Shell('%s %s %s' % (script, kernel_device, main_fw_file))
381 if not result.success:
382 raise Error, '%r failed, stderr: %r' % (script, result.stderr)
383
384
385@Command('set_fw_bitmap_locale')
Tammo Spalink01e11722012-07-24 10:17:54 -0700386def SetFirmwareBitmapLocale(options): # pylint: disable=W0613
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800387 """Use VPD locale value to set firmware bitmap default language."""
388 image_file = crosfw.LoadMainFirmware().GetFileName()
389 locale = ReadRoVpd(image_file).get('initial_locale', None)
390 if locale is None:
391 raise Error, 'Missing initial_locale VPD.'
392 bitmap_locales = []
393 with NamedTemporaryFile() as f:
394 Shell('gbb_utility -g --bmpfv=%s %s' % (f.name, image_file))
Tammo Spalink01e11722012-07-24 10:17:54 -0700395 bmpblk_data = unpack_bmpblock(f.read())
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800396 bitmap_locales = bmpblk_data.get('locales', bitmap_locales)
397 # Some locale values are just a language code and others are a
398 # hyphen-separated language code and country code pair. We care
399 # only about the language code part.
400 language_code = locale.partition('-')[0]
401 if language_code not in bitmap_locales:
402 raise Error, ('Firmware bitmaps do not contain support for the specified '
403 'initial locale language %r' % language_code)
404 else:
405 locale_index = bitmap_locales.index(language_code)
406 logging.info('Firmware bitmap initial locale set to %d (%s).',
407 locale_index, bitmap_locales[locale_index])
408 Shell('crossystem loc_idx=%d' % locale_index)
409
410
411@Command('verify_system_time')
Tammo Spalink01e11722012-07-24 10:17:54 -0700412def VerifySystemTime(options): # pylint: disable=W0613
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800413 """Verify system time is later than release filesystem creation time."""
Tammo Spalink461ddce2012-05-10 19:28:55 +0800414 script = FindScript('verify_system_time.sh')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800415 rootfs_device = GetReleaseRootPartitionPath()
416 result = Shell('%s %s' % (script, rootfs_device))
417 if not result.success:
418 raise Error, '%r failed, stderr: %r' % (script, result.stderr)
419
420
421@Command('verify_rootfs')
Tammo Spalink01e11722012-07-24 10:17:54 -0700422def VerifyRootFs(options): # pylint: disable=W0613
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800423 """Verify rootfs on SSD is valid by checking hash."""
Tammo Spalink461ddce2012-05-10 19:28:55 +0800424 script = FindScript('verify_rootfs.sh')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800425 rootfs_device = GetReleaseRootPartitionPath()
426 result = Shell('%s %s' % (script, rootfs_device))
427 if not result.success:
428 raise Error, '%r failed, stderr: %r' % (script, result.stderr)
429
430
431@Command('verify_switch_wp')
Tammo Spalink01e11722012-07-24 10:17:54 -0700432def VerifyWpSwitch(options): # pylint: disable=W0613
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800433 """Verify hardware write protection switch is enabled."""
434 if Shell('crossystem wpsw_cur').stdout.strip() != '1':
Hung-Te Lin6d827542012-07-19 11:50:41 +0800435 raise Error, 'write protection switch is disabled'
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800436
437
438@Command('verify_switch_dev')
Tammo Spalink01e11722012-07-24 10:17:54 -0700439def VerifyDevSwitch(options): # pylint: disable=W0613
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800440 """Verify developer switch is disabled."""
Hung-Te Lind7d34722012-07-26 16:48:35 +0800441 VBSD_HONOR_VIRT_DEV_SWITCH = 0x400
442 flags = int(Shell('crossystem vdat_flags').stdout.strip(), 0)
443 if (flags & VBSD_HONOR_VIRT_DEV_SWITCH) != 0:
444 # System is using virtual developer switch. That will be handled in
445 # prepare_wipe.sh by setting "crossystem disable_dev_request=1" -- although
446 # we can't verify that until next reboot, because the real values are stored
447 # in TPM.
448 logging.warn('VerifyDevSwitch: No physical switch.')
449 _event_log.Log('switch_dev', type='virtual switch')
450 return
451 if Shell('crossystem devsw_cur').stdout.strip() != '0':
452 raise Error, 'developer mode is not disabled'
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800453
454
455@Command('write_protect')
Tammo Spalink01e11722012-07-24 10:17:54 -0700456def EnableFwWp(options): # pylint: disable=W0613
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800457 """Enable then verify firmware write protection."""
458
459 def WriteProtect(fw_file_path, fw_type, section):
460 """Calculate protection size, then invoke flashrom.
461
462 Our supported chips only allow write protecting half their total
463 size, so we parition the flash chipset space accordingly.
464 """
465 raw_image = open(fw_file_path, 'rb').read()
466 image = crosfw.FirmwareImage(raw_image)
467 if not image.has_section(section):
468 raise Error('could not find %s firmware section %s' % (fw_type, section))
469 section_data = image.get_section_area(section)
470 protectable_size = len(raw_image) / 2
471 ro_a = int(section_data[0] / protectable_size)
472 ro_b = int((section_data[0] + section_data[1] - 1) / protectable_size)
473 if ro_a != ro_b:
474 raise Error("%s firmware section %s has illegal size" %
475 (fw_type, section))
476 ro_offset = ro_a * protectable_size
477 logging.debug('write protecting %s', fw_type)
478 crosfw.Flashrom(fw_type).EnableWriteProtection(ro_offset, protectable_size)
479
480 WriteProtect(crosfw.LoadMainFirmware().GetFileName(), 'main', 'RO_SECTION')
Tammo Spalink86a61c62012-05-25 15:10:35 +0800481 _event_log.Log('wp', fw='main')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800482 ec_fw_file = crosfw.LoadEcFirmware().GetFileName()
483 if ec_fw_file is not None:
Hung-Te Lin6d827542012-07-19 11:50:41 +0800484 # TODO(hungte) Support WP_RO if that section exist.
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800485 WriteProtect(ec_fw_file, 'ec', 'EC_RO')
Tammo Spalink86a61c62012-05-25 15:10:35 +0800486 _event_log.Log('wp', fw='ec')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800487 else:
488 logging.warning('EC not write protected (seems there is no EC flash).')
489
490
491@Command('clear_gbb_flags')
Tammo Spalink01e11722012-07-24 10:17:54 -0700492def ClearGbbFlags(options): # pylint: disable=W0613
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800493 """Zero out the GBB flags, in preparation for transition to release state.
494
495 No GBB flags are set in release/shipping state, but they are useful
496 for factory/development. See "gbb_utility --flags" for details.
497 """
Tammo Spalink461ddce2012-05-10 19:28:55 +0800498 script = FindScript('clear_gbb_flags.sh')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800499 result = Shell(script)
500 if not result.success:
501 raise Error, '%r failed, stderr: %r' % (script, result.stderr)
Tammo Spalink86a61c62012-05-25 15:10:35 +0800502 _event_log.Log('clear_gbb_flags')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800503
504
505@Command('prepare_wipe',
506 CmdArg('--fast', action='store_true',
507 help='use non-secure but faster wipe method.'))
508def PrepareWipe(options):
509 """Prepare system for transition to release state in next reboot."""
Tammo Spalink461ddce2012-05-10 19:28:55 +0800510 script = FindScript('prepare_wipe.sh')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800511 tag = 'fast' if options.fast else ''
512 rootfs_device = GetReleaseRootPartitionPath()
513 result = Shell('FACTORY_WIPE_TAGS=%s %s %s' % (tag, script, rootfs_device))
514 if not result.success:
515 raise Error, '%r failed, stderr: %r' % (script, result.stderr)
516
517
518@Command('verify',
Hung-Te Lin6d827542012-07-19 11:50:41 +0800519 CmdArg('--no_write_protect', action='store_true',
520 help='Do not check write protection switch state.'),
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800521 _hwdb_path_cmd_arg)
522def Verify(options):
523 """Verifies if whole factory process is ready for finalization.
524
525 This routine performs all the necessary checks to make sure the
526 device is ready to be finalized, but does not modify state. These
527 checks include dev switch, firmware write protection switch, hwid,
528 system time, keys, and root file system.
529 """
Hung-Te Lin6d827542012-07-19 11:50:41 +0800530 if not options.no_write_protect:
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800531 VerifyWpSwitch({})
Hung-Te Lin6d827542012-07-19 11:50:41 +0800532 VerifyDevSwitch({})
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800533 VerifyHwid(options)
534 VerifySystemTime({})
535 VerifyKeys({})
536 VerifyRootFs({})
537
538
Tammo Spalink86a61c62012-05-25 15:10:35 +0800539@Command('log_system_details')
Tammo Spalink01e11722012-07-24 10:17:54 -0700540def LogSystemDetails(options): # pylint: disable=W0613
Tammo Spalink86a61c62012-05-25 15:10:35 +0800541 """Write miscellaneous system details to the event log."""
542 raw_cs_data = Shell('crossystem').stdout.strip().splitlines()
543 # The crossytem output contains many lines like:
544 # 'key = value # description'
545 # Use regexps to pull out the key-value pairs and build a dict.
546 cs_data = dict((k, v.strip()) for k, v in
547 map(lambda x: re.findall(r'\A(\S+)\s+=\s+(.*)#.*\Z', x)[0],
548 raw_cs_data))
549 _event_log.Log(
550 'system_details',
551 platform_name=Shell('mosys platform name').stdout.strip(),
552 crossystem=cs_data,
553 modem_status=Shell('modem status').stdout.splitlines(),
554 ec_wp_status=Shell(
555 'flashrom -p internal:bus=lpc --get-size 2>/dev/null && '
556 'flashrom -p internal:bus=lpc --wp-status || '
557 'echo "EC is not available."').stdout,
558 bios_wp_status = Shell(
559 'flashrom -p internal:bus=spi --wp-status').stdout)
560
561
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800562_upload_method_cmd_arg = CmdArg(
563 '--upload_method', metavar='METHOD:PARAM',
564 help=('How to perform the upload. METHOD should be one of '
565 '{ftp, shopfloor, curl, cpfe, custom}.'))
Jon Salz65266432012-07-30 19:02:49 +0800566_add_file_cmd_arg = CmdArg(
567 '--add_file', metavar='FILE', action='append',
568 help='Extra file to include in report (must be an absolute path)')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800569
570@Command('upload_report',
Jon Salz65266432012-07-30 19:02:49 +0800571 _upload_method_cmd_arg,
572 _add_file_cmd_arg)
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800573def UploadReport(options):
574 """Create and a report containing key device details."""
Hung-Te Lin6bd16472012-06-20 16:26:47 +0800575 def NormalizeAsFileName(token):
576 return re.sub(r'\W+', '', token).strip()
Tammo Spalink86a61c62012-05-25 15:10:35 +0800577 ro_vpd = ReadRoVpd(crosfw.LoadMainFirmware().GetFileName())
578 device_sn = ro_vpd.get('serial_number', None)
579 if device_sn is None:
580 logging.warning('RO_VPD missing device serial number')
581 device_sn = 'MISSING_SN_' + TimedUuid()
Hung-Te Lin6bd16472012-06-20 16:26:47 +0800582 target_name = '%s_%s.tbz2' % (time.strftime('%Y%m%dT%H%M%SZ', time.gmtime()),
583 NormalizeAsFileName(device_sn))
Tammo Spalink86a61c62012-05-25 15:10:35 +0800584 target_path = os.path.join(gettempdir(), target_name)
585 # Intentionally ignoring dotfiles in EVENT_LOG_DIR.
586 tar_cmd = 'cd %s ; tar cjf %s *' % (EVENT_LOG_DIR, target_path)
587 tar_cmd += ' --add-file %s' % FACTORY_LOG_PATH
Jon Salz65266432012-07-30 19:02:49 +0800588 if options.add_file:
589 for f in options.add_file:
590 # Require absolute paths since the tar command may change the
591 # directory.
592 if not f.startswith('/'):
593 raise Error('Not an absolute path: %s' % f)
594 if not os.path.exists(f):
595 raise Error('File does not exist: %s' % f)
596 tar_cmd += ' --add-file %s' % pipes.quote(f)
Tammo Spalink86a61c62012-05-25 15:10:35 +0800597 cmd_result = Shell(tar_cmd)
598 if not cmd_result.success:
599 raise Error('unable to tar event logs, cmd %r failed, stderr: %r' %
600 (tar_cmd, cmd_result.stderr))
601 if options.upload_method is None or options.upload_method == 'none':
602 logging.warning('REPORT UPLOAD SKIPPED (report left at %s)', target_path)
603 return
604 method, param = options.upload_method.split(':', 1)
605 if method == 'shopfloor':
606 report_upload.ShopFloorUpload(target_path, param)
607 elif method == 'ftp':
Jay Kim360c1dd2012-06-25 10:58:11 -0700608 report_upload.FtpUpload(target_path, 'ftp:' + param)
Tammo Spalink86a61c62012-05-25 15:10:35 +0800609 elif method == 'ftps':
610 report_upload.CurlUrlUpload(target_path, '--ftp-ssl-reqd ftp:%s' % param)
611 elif method == 'cpfe':
612 report_upload.CpfeUpload(target_path, param)
613 else:
614 raise Error('unknown report upload method %r', method)
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800615
616
617@Command('finalize',
Hung-Te Lin6d827542012-07-19 11:50:41 +0800618 CmdArg('--no_write_protect', action='store_true',
619 help='Do not enable firmware write protection.'),
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800620 CmdArg('--fast', action='store_true',
621 help='use non-secure but faster wipe method.'),
622 _hwdb_path_cmd_arg,
Jon Salz65266432012-07-30 19:02:49 +0800623 _upload_method_cmd_arg,
624 _add_file_cmd_arg)
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800625def Finalize(options):
626 """Verify system readiness and trigger transition into release state.
627
Hung-Te Lin6d827542012-07-19 11:50:41 +0800628 This routine first verifies system state (see verify command), modifies
629 firmware bitmaps to match locale, and then clears all of the factory-friendly
630 flags from the GBB. If everything is fine, it enables firmware write
631 protection (cannot rollback after this stage), uploads system logs & reports,
632 and sets the necessary boot flags to cause wipe of the factory image on the
633 next boot.
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800634 """
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800635 Verify(options)
636 SetFirmwareBitmapLocale({})
Hung-Te Lin6d827542012-07-19 11:50:41 +0800637 ClearGbbFlags({})
638 if options.no_write_protect:
639 logging.warn('WARNING: Firmware Write Protection is SKIPPED.')
640 _event_log.Log('wp', fw='both', status='skipped')
641 else:
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800642 EnableFwWp({})
Jon Salza0f58e02012-05-29 19:33:39 +0800643 LogSystemDetails(options)
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800644 UploadReport(options)
645 PrepareWipe(options)
646
647
648def Main():
649 """Run sub-command specified by the command line args."""
650 options = ParseCmdline(
651 'Perform Google required factory tests.',
652 CmdArg('-l', '--log', metavar='PATH',
653 help='Write logs to this file.'),
Tammo Spalink8fab5312012-05-28 18:33:30 +0800654 verbosity_cmd_arg)
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800655 SetupLogging(options.verbosity, options.log)
656 logging.debug('gooftool options: %s', repr(options))
657 try:
658 logging.debug('GOOFTOOL command %r', options.command_name)
659 options.command(options)
660 logging.info('GOOFTOOL command %r SUCCESS', options.command_name)
661 except Error, e:
662 logging.exception(e)
663 sys.exit('GOOFTOOL command %r ERROR: %s' % (options.command_name, e))
664 except Exception, e:
665 logging.exception(e)
666 sys.exit('UNCAUGHT RUNTIME EXCEPTION %s' % e)
667
668
669if __name__ == '__main__':
670 Main()