blob: 32b829e13db590aa1f4679cf5ad1adf656ef67d1 [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
Hung-Te Lin7ea39e82012-07-31 18:39:33 +0800459 def CalculateLegacyRange(image, length, section_data):
460 ro_size = length / 2
461 ro_a = int(section_data[0] / ro_size)
462 ro_b = int((section_data[0] + section_data[1] - 1) / ro_size)
463 if ro_a != ro_b:
464 raise Error("%s firmware section %s has illegal size" %
465 (fw_type, section))
466 ro_offset = ro_a * ro_size
467 return (ro_offset, ro_size)
468
469 def WriteProtect(fw_file_path, fw_type, legacy_section):
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800470 """Calculate protection size, then invoke flashrom.
471
472 Our supported chips only allow write protecting half their total
473 size, so we parition the flash chipset space accordingly.
474 """
475 raw_image = open(fw_file_path, 'rb').read()
Hung-Te Lin7ea39e82012-07-31 18:39:33 +0800476 wp_section = 'WP_RO'
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800477 image = crosfw.FirmwareImage(raw_image)
Hung-Te Lin7ea39e82012-07-31 18:39:33 +0800478 if image.has_section(wp_section):
479 section_data = image.get_section_area(wp_section)
480 ro_offset = section_data[0]
481 ro_size = section_data[1]
482 elif image.has_section(legacy_section):
483 section_data = image.get_section_area(legacy_section)
484 (ro_offset, ro_size) = CalculateLegacyRange(
485 image, len(raw_image), section_data)
486 else:
487 raise Error('could not find %s firmware section %s or %s' %
488 (fw_type, wp_section, legacy_section))
489
490 logging.debug('write protecting %s [off=%x size=%x]', fw_type,
491 ro_offset, ro_size)
492 crosfw.Flashrom(fw_type).EnableWriteProtection(ro_offset, ro_size)
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800493
494 WriteProtect(crosfw.LoadMainFirmware().GetFileName(), 'main', 'RO_SECTION')
Tammo Spalink86a61c62012-05-25 15:10:35 +0800495 _event_log.Log('wp', fw='main')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800496 ec_fw_file = crosfw.LoadEcFirmware().GetFileName()
497 if ec_fw_file is not None:
498 WriteProtect(ec_fw_file, 'ec', 'EC_RO')
Tammo Spalink86a61c62012-05-25 15:10:35 +0800499 _event_log.Log('wp', fw='ec')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800500 else:
501 logging.warning('EC not write protected (seems there is no EC flash).')
502
503
504@Command('clear_gbb_flags')
Tammo Spalink01e11722012-07-24 10:17:54 -0700505def ClearGbbFlags(options): # pylint: disable=W0613
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800506 """Zero out the GBB flags, in preparation for transition to release state.
507
508 No GBB flags are set in release/shipping state, but they are useful
509 for factory/development. See "gbb_utility --flags" for details.
510 """
Tammo Spalink461ddce2012-05-10 19:28:55 +0800511 script = FindScript('clear_gbb_flags.sh')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800512 result = Shell(script)
513 if not result.success:
514 raise Error, '%r failed, stderr: %r' % (script, result.stderr)
Tammo Spalink86a61c62012-05-25 15:10:35 +0800515 _event_log.Log('clear_gbb_flags')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800516
517
518@Command('prepare_wipe',
519 CmdArg('--fast', action='store_true',
520 help='use non-secure but faster wipe method.'))
521def PrepareWipe(options):
522 """Prepare system for transition to release state in next reboot."""
Tammo Spalink461ddce2012-05-10 19:28:55 +0800523 script = FindScript('prepare_wipe.sh')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800524 tag = 'fast' if options.fast else ''
525 rootfs_device = GetReleaseRootPartitionPath()
526 result = Shell('FACTORY_WIPE_TAGS=%s %s %s' % (tag, script, rootfs_device))
527 if not result.success:
528 raise Error, '%r failed, stderr: %r' % (script, result.stderr)
529
530
531@Command('verify',
Hung-Te Lin6d827542012-07-19 11:50:41 +0800532 CmdArg('--no_write_protect', action='store_true',
533 help='Do not check write protection switch state.'),
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800534 _hwdb_path_cmd_arg)
535def Verify(options):
536 """Verifies if whole factory process is ready for finalization.
537
538 This routine performs all the necessary checks to make sure the
539 device is ready to be finalized, but does not modify state. These
540 checks include dev switch, firmware write protection switch, hwid,
541 system time, keys, and root file system.
542 """
Hung-Te Lin6d827542012-07-19 11:50:41 +0800543 if not options.no_write_protect:
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800544 VerifyWpSwitch({})
Hung-Te Lin6d827542012-07-19 11:50:41 +0800545 VerifyDevSwitch({})
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800546 VerifyHwid(options)
547 VerifySystemTime({})
548 VerifyKeys({})
549 VerifyRootFs({})
550
551
Tammo Spalink86a61c62012-05-25 15:10:35 +0800552@Command('log_system_details')
Tammo Spalink01e11722012-07-24 10:17:54 -0700553def LogSystemDetails(options): # pylint: disable=W0613
Tammo Spalink86a61c62012-05-25 15:10:35 +0800554 """Write miscellaneous system details to the event log."""
555 raw_cs_data = Shell('crossystem').stdout.strip().splitlines()
556 # The crossytem output contains many lines like:
557 # 'key = value # description'
558 # Use regexps to pull out the key-value pairs and build a dict.
559 cs_data = dict((k, v.strip()) for k, v in
560 map(lambda x: re.findall(r'\A(\S+)\s+=\s+(.*)#.*\Z', x)[0],
561 raw_cs_data))
562 _event_log.Log(
563 'system_details',
564 platform_name=Shell('mosys platform name').stdout.strip(),
565 crossystem=cs_data,
566 modem_status=Shell('modem status').stdout.splitlines(),
567 ec_wp_status=Shell(
568 'flashrom -p internal:bus=lpc --get-size 2>/dev/null && '
569 'flashrom -p internal:bus=lpc --wp-status || '
570 'echo "EC is not available."').stdout,
571 bios_wp_status = Shell(
572 'flashrom -p internal:bus=spi --wp-status').stdout)
573
574
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800575_upload_method_cmd_arg = CmdArg(
576 '--upload_method', metavar='METHOD:PARAM',
577 help=('How to perform the upload. METHOD should be one of '
578 '{ftp, shopfloor, curl, cpfe, custom}.'))
Jon Salz65266432012-07-30 19:02:49 +0800579_add_file_cmd_arg = CmdArg(
580 '--add_file', metavar='FILE', action='append',
581 help='Extra file to include in report (must be an absolute path)')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800582
583@Command('upload_report',
Jon Salz65266432012-07-30 19:02:49 +0800584 _upload_method_cmd_arg,
585 _add_file_cmd_arg)
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800586def UploadReport(options):
587 """Create and a report containing key device details."""
Hung-Te Lin6bd16472012-06-20 16:26:47 +0800588 def NormalizeAsFileName(token):
589 return re.sub(r'\W+', '', token).strip()
Tammo Spalink86a61c62012-05-25 15:10:35 +0800590 ro_vpd = ReadRoVpd(crosfw.LoadMainFirmware().GetFileName())
591 device_sn = ro_vpd.get('serial_number', None)
592 if device_sn is None:
593 logging.warning('RO_VPD missing device serial number')
594 device_sn = 'MISSING_SN_' + TimedUuid()
Hung-Te Lin6bd16472012-06-20 16:26:47 +0800595 target_name = '%s_%s.tbz2' % (time.strftime('%Y%m%dT%H%M%SZ', time.gmtime()),
596 NormalizeAsFileName(device_sn))
Tammo Spalink86a61c62012-05-25 15:10:35 +0800597 target_path = os.path.join(gettempdir(), target_name)
598 # Intentionally ignoring dotfiles in EVENT_LOG_DIR.
599 tar_cmd = 'cd %s ; tar cjf %s *' % (EVENT_LOG_DIR, target_path)
600 tar_cmd += ' --add-file %s' % FACTORY_LOG_PATH
Jon Salz65266432012-07-30 19:02:49 +0800601 if options.add_file:
602 for f in options.add_file:
603 # Require absolute paths since the tar command may change the
604 # directory.
605 if not f.startswith('/'):
606 raise Error('Not an absolute path: %s' % f)
607 if not os.path.exists(f):
608 raise Error('File does not exist: %s' % f)
609 tar_cmd += ' --add-file %s' % pipes.quote(f)
Tammo Spalink86a61c62012-05-25 15:10:35 +0800610 cmd_result = Shell(tar_cmd)
611 if not cmd_result.success:
612 raise Error('unable to tar event logs, cmd %r failed, stderr: %r' %
613 (tar_cmd, cmd_result.stderr))
614 if options.upload_method is None or options.upload_method == 'none':
615 logging.warning('REPORT UPLOAD SKIPPED (report left at %s)', target_path)
616 return
617 method, param = options.upload_method.split(':', 1)
618 if method == 'shopfloor':
619 report_upload.ShopFloorUpload(target_path, param)
620 elif method == 'ftp':
Jay Kim360c1dd2012-06-25 10:58:11 -0700621 report_upload.FtpUpload(target_path, 'ftp:' + param)
Tammo Spalink86a61c62012-05-25 15:10:35 +0800622 elif method == 'ftps':
623 report_upload.CurlUrlUpload(target_path, '--ftp-ssl-reqd ftp:%s' % param)
624 elif method == 'cpfe':
625 report_upload.CpfeUpload(target_path, param)
626 else:
627 raise Error('unknown report upload method %r', method)
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800628
629
630@Command('finalize',
Hung-Te Lin6d827542012-07-19 11:50:41 +0800631 CmdArg('--no_write_protect', action='store_true',
632 help='Do not enable firmware write protection.'),
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800633 CmdArg('--fast', action='store_true',
634 help='use non-secure but faster wipe method.'),
635 _hwdb_path_cmd_arg,
Jon Salz65266432012-07-30 19:02:49 +0800636 _upload_method_cmd_arg,
637 _add_file_cmd_arg)
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800638def Finalize(options):
639 """Verify system readiness and trigger transition into release state.
640
Hung-Te Lin6d827542012-07-19 11:50:41 +0800641 This routine first verifies system state (see verify command), modifies
642 firmware bitmaps to match locale, and then clears all of the factory-friendly
643 flags from the GBB. If everything is fine, it enables firmware write
644 protection (cannot rollback after this stage), uploads system logs & reports,
645 and sets the necessary boot flags to cause wipe of the factory image on the
646 next boot.
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800647 """
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800648 Verify(options)
649 SetFirmwareBitmapLocale({})
Hung-Te Lin6d827542012-07-19 11:50:41 +0800650 ClearGbbFlags({})
651 if options.no_write_protect:
652 logging.warn('WARNING: Firmware Write Protection is SKIPPED.')
653 _event_log.Log('wp', fw='both', status='skipped')
654 else:
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800655 EnableFwWp({})
Jon Salza0f58e02012-05-29 19:33:39 +0800656 LogSystemDetails(options)
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800657 UploadReport(options)
658 PrepareWipe(options)
659
660
661def Main():
662 """Run sub-command specified by the command line args."""
663 options = ParseCmdline(
664 'Perform Google required factory tests.',
665 CmdArg('-l', '--log', metavar='PATH',
666 help='Write logs to this file.'),
Tammo Spalink8fab5312012-05-28 18:33:30 +0800667 verbosity_cmd_arg)
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800668 SetupLogging(options.verbosity, options.log)
669 logging.debug('gooftool options: %s', repr(options))
670 try:
671 logging.debug('GOOFTOOL command %r', options.command_name)
672 options.command(options)
673 logging.info('GOOFTOOL command %r SUCCESS', options.command_name)
674 except Error, e:
675 logging.exception(e)
676 sys.exit('GOOFTOOL command %r ERROR: %s' % (options.command_name, e))
677 except Exception, e:
678 logging.exception(e)
679 sys.exit('UNCAUGHT RUNTIME EXCEPTION %s' % e)
680
681
682if __name__ == '__main__':
683 Main()