blob: 9d5a61ba7c1e84a9858e5f02f90e3400d5095978 [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):
Jon Salzd24269c2012-05-29 17:22:59 +080083 script_path = os.path.join(os.path.dirname(os.path.dirname(
84 os.path.realpath(__file__))), 'sh', script_name)
Tammo Spalink9a96b8a2012-04-03 11:10:41 +080085 if not os.path.exists(script_path):
86 raise Error('Needed script %s does not exist.' % script_path)
87 return script_path
88
89
Tammo Spalink86a61c62012-05-25 15:10:35 +080090def ReadVpd(fw_image_file, kind):
91 raw_vpd_data = Shell('vpd -i %s -l -f %s' % (kind, fw_image_file)).stdout
Tammo Spalink9a96b8a2012-04-03 11:10:41 +080092 return ParseKeyValueData('"(.*)"="(.*)"$', raw_vpd_data)
93
94
Tammo Spalink86a61c62012-05-25 15:10:35 +080095def ReadRoVpd(fw_image_file):
96 return ReadVpd(fw_image_file, 'RO_VPD')
97
98
99def ReadRwVpd(fw_image_file):
100 return ReadVpd(fw_image_file, 'RW_VPD')
101
102
Tammo Spalink5c699832012-07-03 17:50:39 +0800103# TODO(tammo): Replace calls to sys.exit with raise Exit, and maybe
104# treat that specially (as a smoot exit, as opposed to the more
105# verbose output for generic Error).
106
107
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800108@Command('write_hwid',
109 CmdArg('hwid', metavar='HWID', help='HWID string'))
110def WriteHwid(options):
111 """Write specified HWID value into the system BB."""
112 logging.debug('writing hwid string %r', options.hwid)
113 main_fw = crosfw.LoadMainFirmware()
114 Shell('gbb_utility --set --hwid="%s" "%s"' %
115 (options.hwid, main_fw.GetFileName()))
116 main_fw.Write(sections=['GBB'])
Tammo Spalink86a61c62012-05-25 15:10:35 +0800117 _event_log.Log('write_hwid', hwid=options.hwid)
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800118
119
Tammo Spalink8fab5312012-05-28 18:33:30 +0800120_hwdb_path_cmd_arg = CmdArg(
121 '--hwdb_path', metavar='PATH',
122 default=hwid_tool.DEFAULT_HWID_DATA_PATH,
123 help='Path to the HWID database.')
124
125
126@Command('probe_hwids',
127 _hwdb_path_cmd_arg,
128 CmdArg('-b', '--board', metavar='BOARD',
129 help='BOARD name', required=True),
130 CmdArg('--bom', metavar='BOM', help='BOM name'),
131 CmdArg('--variant', metavar='VARIANT', help='VARIANT code'),
Tammo Spalink5c699832012-07-03 17:50:39 +0800132 CmdArg('--optimistic', action='store_true',
133 help='do not probe; assume singletons match'),
134 CmdArg('--stdin_comp_map', action='store_true'),
135 CmdArg('--status', nargs='*', default=['supported'],
Tammo Spalink8fab5312012-05-28 18:33:30 +0800136 help='consider only HWIDs with this status'))
Tammo Spalink5c699832012-07-03 17:50:39 +0800137# TODO(tammo): Add a 'hw-only probe' option that does not probe firmware.
Tammo Spalink8fab5312012-05-28 18:33:30 +0800138def ProbeHwid(options):
139 """Determine a list of possible HWIDs using provided args and probeing.
140
141 VOLATILE can always be determined by probing. To get a unique
142 result, VARIANT must be specified for all cases where the matching
143 BOM has more than one associated variant code, otherwise all HWID
144 variants will be returned. Both VARIANT and BOM information can
Tammo Spalink5c699832012-07-03 17:50:39 +0800145 alternatively be specified using the --stdin_comp_map argument,
146 which allows specifying a list of
Tammo Spalink8fab5312012-05-28 18:33:30 +0800147
148 component-class: canonical-component-name
149
150 pairs on stdin, one per line (yaml format). Based on what is known
Tammo Spalink5c699832012-07-03 17:50:39 +0800151 from BOM and stdin_comp_map, determine a list of components to probe
152 for, and use those probe results to resolve a list of matching
153 HWIDs. If no boms, components, or variant codes are specified, then
154 a list of all HWIDs that match probable components will be returned.
Tammo Spalink8fab5312012-05-28 18:33:30 +0800155
156 Returns (on stdout): A list of HWIDs that match the available probe
157 results and argument contraints, one per line.
158 """
Tammo Spalink5c699832012-07-03 17:50:39 +0800159 map(hwid_tool.Validate.Status, options.status)
Tammo Spalink01e11722012-07-24 10:17:54 -0700160 hw_db = hwid_tool.HardwareDb(options.data_path)
Tammo Spalink5c699832012-07-03 17:50:39 +0800161 comp_db = hw_db.comp_db
Tammo Spalink3a7e9022012-06-27 14:08:40 +0800162 device = hw_db.GetDevice(options.board)
Tammo Spalink5c699832012-07-03 17:50:39 +0800163 component_spec = hwid_tool.ComponentSpec.New()
Tammo Spalink8fab5312012-05-28 18:33:30 +0800164 if options.bom:
Tammo Spalink5c699832012-07-03 17:50:39 +0800165 device.BomExists(options.bom)
Tammo Spalink01e11722012-07-24 10:17:54 -0700166 component_spec = hwid_tool.CombineComponentSpecs(
Tammo Spalink5c699832012-07-03 17:50:39 +0800167 component_spec, device.boms[options.bom].primary)
Tammo Spalink8fab5312012-05-28 18:33:30 +0800168 if options.variant:
Tammo Spalink5c699832012-07-03 17:50:39 +0800169 device.VariantExists(options.variant)
170 variant_spec = device.variants[options.variant]
Tammo Spalink01e11722012-07-24 10:17:54 -0700171 if hwid_tool.ComponentSpecsConflict(component_spec, variant_spec):
172 # TODO(tammo): This error meesage arg is wrong; fix this when
173 # also making the whole function work (it is definitely broken).
Tammo Spalink5c699832012-07-03 17:50:39 +0800174 sys.exit('ERROR: multiple specifications for %r components'
Tammo Spalink01e11722012-07-24 10:17:54 -0700175 ' (both VARIANT and BOM)' % component_spec)
176 component_spec = hwid_tool.CombineComponentSpecs(
177 component_spec, variant_spec)
Tammo Spalink5c699832012-07-03 17:50:39 +0800178 if options.stdin_comp_map:
Tammo Spalink8fab5312012-05-28 18:33:30 +0800179 input_map = YamlRead(sys.stdin.read())
180 logging.info('stdin component map: %r', input_map)
Tammo Spalink01e11722012-07-24 10:17:54 -0700181 spec_classes = hwid_tool.ComponentSpecClasses(component_spec)
Tammo Spalink8fab5312012-05-28 18:33:30 +0800182 for key, value in input_map.items():
Tammo Spalink01e11722012-07-24 10:17:54 -0700183 if key not in comp_db.all_comp_classes:
Tammo Spalink8fab5312012-05-28 18:33:30 +0800184 sys.exit('ERROR: unknown component class %r (from stdin)' % key)
Tammo Spalink5c699832012-07-03 17:50:39 +0800185 if value not in comp_db.all_comp_names:
Tammo Spalink8fab5312012-05-28 18:33:30 +0800186 sys.exit('ERROR: unkown component name %r (from stdin)' % value)
Tammo Spalink5c699832012-07-03 17:50:39 +0800187 if key in spec_classes:
Tammo Spalink8fab5312012-05-28 18:33:30 +0800188 sys.exit('ERROR: multiple specifications for %r components'
189 ' (stdin and BOM/VARIANT)' % key)
Tammo Spalink5c699832012-07-03 17:50:39 +0800190 component_spec.components.update(input_map)
Tammo Spalink01e11722012-07-24 10:17:54 -0700191 logging.info('component spec used for matching:\n%s', component_spec.Encode)
192 spec_classes = hwid_tool.ComponentSpecClasses(component_spec)
Tammo Spalink5c699832012-07-03 17:50:39 +0800193 missing_classes = list(set(comp_db.all_comp_classes) - spec_classes)
194 if missing_classes and not options.optimistic:
195 logging.info('probing for missing classes %s', ', '.join(missing_classes))
Tammo Spalink01e11722012-07-24 10:17:54 -0700196 probe_results = Probe(target_comp_classes=missing_classes,
Tammo Spalink5c699832012-07-03 17:50:39 +0800197 probe_volatile=True, probe_initial_config=False)
198 else:
199 probe_results = hwid_tool.ProbeResults(
200 found_components=component_spec.components,
201 missing_component_classes=component_spec.classes_missing,
202 volatiles={}, initial_configs={})
203 cooked_results = hwid_tool.CookedProbeResults(comp_db, device, probe_results)
204 if not cooked_results.match_tree:
205 sys.exit('NO matching BOMs found')
206 if cooked_results.matched_hwids:
207 print '\n'.join(cooked_results.matched_hwids)
208 return
Tammo Spalink01e11722012-07-24 10:17:54 -0700209 logging.info('exact HWID matching failed, but the following BOMs match: %s',
Tammo Spalink5c699832012-07-03 17:50:39 +0800210 ', '.join(sorted(cooked_results.match_tree)))
211 if options.optimistic and len(cooked_results.match_tree) == 1:
212 bom_name = set(cooked_results.match_tree).pop()
213 bom = device.boms[bom_name]
214 variant_matches = cooked_results.match_tree[bom_name]
215 if len(variant_matches) == 1:
216 var_code = set(variant_matches).pop()
217 elif len(bom.variants) == 1:
218 var_code = set(bom.variants).pop()
219 else:
220 sys.exit('NO matching HWIDs found; optimistic matching failed because '
221 'there were too many variants to choose from for BOM %r' %
222 bom_name)
223 print '\n'.join(
224 device.FmtHwid(bom_name, var_code, vol_code)
225 for vol_code in device.volatiles
226 if device.GetHwidStatus(bom_name, var_code, vol_code)
227 in options.status)
228 return
229 else:
230 logging.info('optimistic matching not attempted because either it was '
231 'not requested, or because the number of BOMs was <> 1')
Tammo Spalink01e11722012-07-24 10:17:54 -0700232 print 'NO matching HWIDs found'
Tammo Spalink8fab5312012-05-28 18:33:30 +0800233
234
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800235@Command('probe',
236 CmdArg('--comps', nargs='*',
237 help='List of keys from the component_db registry.'),
238 CmdArg('--no_vol', action='store_true',
239 help='Do not probe volatile data.'),
240 CmdArg('--no_ic', action='store_true',
241 help='Do not probe initial_config data.'))
242def RunProbe(options):
243 """Print yaml-formatted breakdown of probed device properties."""
Tammo Spalink01e11722012-07-24 10:17:54 -0700244 probe_results = Probe(target_comp_classes=options.comps,
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800245 probe_volatile=not options.no_vol,
246 probe_initial_config=not options.no_ic)
Tammo Spalink3a7e9022012-06-27 14:08:40 +0800247 print probe_results.Encode()
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800248
249
Tammo Spalink214caf42012-05-28 10:45:00 +0800250@Command('verify_components',
251 _hwdb_path_cmd_arg,
Tammo Spalink5c699832012-07-03 17:50:39 +0800252 CmdArg('target_comps', nargs='*'))
Tammo Spalink214caf42012-05-28 10:45:00 +0800253def VerifyComponents(options):
254 """Verify that probable components all match entries in the component_db.
255
Tammo Spalink5c699832012-07-03 17:50:39 +0800256 Probe for each component class in the target_comps and verify
Tammo Spalink214caf42012-05-28 10:45:00 +0800257 that a corresponding match exists in the component_db -- make sure
258 that these components are present, that they have been approved, but
259 do not check against any specific BOM/HWID configurations.
260 """
Tammo Spalink5c699832012-07-03 17:50:39 +0800261 comp_db = hwid_tool.HardwareDb(options.hwdb_path).comp_db
262 if not options.target_comps:
263 sys.exit('ERROR: no target component classes specified; possible choices:\n'
264 + '\n '.join(sorted(comp_db.components)))
265 for comp_class in options.target_comps:
266 if comp_class not in comp_db.components:
267 sys.exit('ERROR: specified component class %r does not exist'
Tammo Spalink214caf42012-05-28 10:45:00 +0800268 ' in the component DB.' % comp_class)
Tammo Spalink01e11722012-07-24 10:17:54 -0700269 probe_results = Probe(target_comp_classes=options.target_comps,
Tammo Spalink214caf42012-05-28 10:45:00 +0800270 probe_volatile=False, probe_initial_config=False)
Tammo Spalink214caf42012-05-28 10:45:00 +0800271 errors = []
272 matches = []
Tammo Spalink5c699832012-07-03 17:50:39 +0800273 for comp_class in sorted(options.target_comps):
Tammo Spalink214caf42012-05-28 10:45:00 +0800274 probe_val = probe_results.found_components.get(comp_class, None)
275 if probe_val is not None:
Tammo Spalink5c699832012-07-03 17:50:39 +0800276 comp_name = comp_db.result_name_map.get(probe_val, None)
Tammo Spalink214caf42012-05-28 10:45:00 +0800277 if comp_name is not None:
278 matches.append(comp_name)
279 else:
280 errors.append('unsupported %r component found with probe result'
281 ' %r (no matching name in the component DB)' %
282 (comp_class, probe_val))
283 else:
284 errors.append('missing %r component' % comp_class)
285 if errors:
286 print '\n'.join(errors)
287 sys.exit('component verification FAILURE')
288 else:
289 print 'component verification SUCCESS'
290 print 'found components:\n %s' % '\n '.join(matches)
291
292
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800293@Command('verify_hwid',
Tammo Spalink5c699832012-07-03 17:50:39 +0800294 CmdArg('--status', nargs='*', default=['supported'],
295 help='allow only HWIDs with these status values'),
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800296 _hwdb_path_cmd_arg)
297def VerifyHwid(options):
298 """Verify system HWID properties match probed device properties.
299
300 First probe components, volatile and initial_config parameters for
301 the DUT. Then use the available device data to produce a list of
302 candidate HWIDs. Then verify the HWID from the DUT is present in
303 that list. Then verify that the DUT initial config values match
304 those specified for its HWID. Finally, verify that VPD contains all
305 the necessary fields as specified by the board data, and when
306 possible verify that values are legitimate.
307 """
Tammo Spalink5c699832012-07-03 17:50:39 +0800308 def VerifyVpd(ro_vpd_keys):
309 ro_vpd = ReadRoVpd(main_fw_file)
310 for key in ro_vpd_keys:
311 if key not in ro_vpd:
312 sys.exit('Missing required VPD field: %s' % key)
Tammo Spalink01e11722012-07-24 10:17:54 -0700313 known_valid_values = KNOWN_VPD_FIELD_DATA.get(key, None)
Tammo Spalink5c699832012-07-03 17:50:39 +0800314 value = ro_vpd[key]
315 if known_valid_values is not None and value not in known_valid_values:
316 sys.exit('Invalid VPD entry : key %r, value %r' % (key, value))
317 rw_vpd = ReadRwVpd(main_fw_file)
318 _event_log.Log('vpd', ro_vpd=ro_vpd, rw_vpd=rw_vpd)
319 map(hwid_tool.Validate.Status, options.status)
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800320 main_fw_file = crosfw.LoadMainFirmware().GetFileName()
321 gbb_result = Shell('gbb_utility -g --hwid %s' % main_fw_file).stdout
322 hwid = re.findall(r'hardware_id:(.*)', gbb_result)[0].strip()
Tammo Spalink5c699832012-07-03 17:50:39 +0800323 hw_db = hwid_tool.HardwareDb(options.hwdb_path)
324 hwid_data = hw_db.GetHwidData(hwid)
325 logging.info('Verifying system HWID: %r', hwid_data.hwid)
326 if hwid_data.status not in options.status:
327 sys.exit('HWID status must be one of [%s], found %r' %
328 (', '.join(options.status, hwid_data.status)))
329 logging.debug('expected system properties:\n%s', hwid_data.Encode())
330 device = hw_db.GetDevice(hwid_data.board_name)
331 cooked_probe_results = hwid_tool.CookedProbeResults(
Tammo Spalink01e11722012-07-24 10:17:54 -0700332 hw_db.comp_db, device, Probe())
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800333 logging.debug('found system properties:\n%s',
Tammo Spalink01e11722012-07-24 10:17:54 -0700334 YamlWrite(cooked_probe_results.__dict__))
Tammo Spalink86a61c62012-05-25 15:10:35 +0800335 _event_log.Log(
Tammo Spalink5c699832012-07-03 17:50:39 +0800336 'probe',
Tammo Spalink01e11722012-07-24 10:17:54 -0700337 found_components=cooked_probe_results.found_components,
338 missing_component_classes=cooked_probe_results.missing_component_classes,
339 volatiles=cooked_probe_results.volatiles,
340 initial_configs=cooked_probe_results.initial_configs)
Tammo Spalink5c699832012-07-03 17:50:39 +0800341 if hwid not in cooked_probe_results.matched_hwids:
342 err_msg = 'HWID verification FAILED.\n'
343 if cooked_probe_results.unmatched_components:
344 sys.exit(err_msg + 'some components could not be indentified:\n%s' %
345 YamlWrite(cooked_probe_results.unmatched_components))
346 if not cooked_probe_results.match_tree:
347 sys.exit(err_msg + 'no matching boms were found for components:\n%s' %
348 cooked_probe_results.component_data.Encode())
349 if hwid.bom_name not in cooked_probe_results.match_tree:
350 sys.exit(err_msg + 'matching boms [%s] do not include target bom %r' %
351 (', '.join(sorted(cooked_probe_results.match_tree)),
352 hwid_data.bom))
353 err_msg += 'target bom %r matches components' % hwid_data.bom_name
354 if hwid.bom_name not in device.matched_ic_boms:
355 sys.exit(err_msg + ', but failed initial config verification')
356 match_tree = cooked_probe_results.match_tree
357 matched_variants = match_tree.get(hwid_data.bom_name, {})
358 if hwid.variant_code not in matched_variants:
359 sys.exit(err_msg + ', but target variant_code %r did not match' %
360 hwid_data.variant_code)
361 matched_volatiles = matched_variants.get(hwid_data.variant_code, {})
362 if hwid.volatile_code not in matched_volatiles:
363 sys.exit(err_msg + ', but target volatile_code %r did not match' %
364 hwid_data.volatile_code)
365 found_status = matched_volatiles.get(hwid_data.volatile_code, None)
366 sys.exit(err_msg + ', but hwid status %r was unacceptable' % found_status)
367 VerifyVpd(hwid_data.ro_vpds)
368 _event_log.Log('verified_hwid', hwid=hwid)
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800369
370
371@Command('verify_keys')
Tammo Spalink01e11722012-07-24 10:17:54 -0700372def VerifyKeys(options): # pylint: disable=W0613
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800373 """Verify keys in firmware and SSD match."""
Tammo Spalink461ddce2012-05-10 19:28:55 +0800374 script = FindScript('verify_keys.sh')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800375 kernel_device = GetReleaseKernelPartitionPath()
376 main_fw_file = crosfw.LoadMainFirmware().GetFileName()
377 result = Shell('%s %s %s' % (script, kernel_device, main_fw_file))
378 if not result.success:
379 raise Error, '%r failed, stderr: %r' % (script, result.stderr)
380
381
382@Command('set_fw_bitmap_locale')
Tammo Spalink01e11722012-07-24 10:17:54 -0700383def SetFirmwareBitmapLocale(options): # pylint: disable=W0613
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800384 """Use VPD locale value to set firmware bitmap default language."""
385 image_file = crosfw.LoadMainFirmware().GetFileName()
386 locale = ReadRoVpd(image_file).get('initial_locale', None)
387 if locale is None:
388 raise Error, 'Missing initial_locale VPD.'
389 bitmap_locales = []
390 with NamedTemporaryFile() as f:
391 Shell('gbb_utility -g --bmpfv=%s %s' % (f.name, image_file))
Tammo Spalink01e11722012-07-24 10:17:54 -0700392 bmpblk_data = unpack_bmpblock(f.read())
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800393 bitmap_locales = bmpblk_data.get('locales', bitmap_locales)
394 # Some locale values are just a language code and others are a
395 # hyphen-separated language code and country code pair. We care
396 # only about the language code part.
397 language_code = locale.partition('-')[0]
398 if language_code not in bitmap_locales:
399 raise Error, ('Firmware bitmaps do not contain support for the specified '
400 'initial locale language %r' % language_code)
401 else:
402 locale_index = bitmap_locales.index(language_code)
403 logging.info('Firmware bitmap initial locale set to %d (%s).',
404 locale_index, bitmap_locales[locale_index])
405 Shell('crossystem loc_idx=%d' % locale_index)
406
407
408@Command('verify_system_time')
Tammo Spalink01e11722012-07-24 10:17:54 -0700409def VerifySystemTime(options): # pylint: disable=W0613
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800410 """Verify system time is later than release filesystem creation time."""
Tammo Spalink461ddce2012-05-10 19:28:55 +0800411 script = FindScript('verify_system_time.sh')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800412 rootfs_device = GetReleaseRootPartitionPath()
413 result = Shell('%s %s' % (script, rootfs_device))
414 if not result.success:
415 raise Error, '%r failed, stderr: %r' % (script, result.stderr)
416
417
418@Command('verify_rootfs')
Tammo Spalink01e11722012-07-24 10:17:54 -0700419def VerifyRootFs(options): # pylint: disable=W0613
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800420 """Verify rootfs on SSD is valid by checking hash."""
Tammo Spalink461ddce2012-05-10 19:28:55 +0800421 script = FindScript('verify_rootfs.sh')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800422 rootfs_device = GetReleaseRootPartitionPath()
423 result = Shell('%s %s' % (script, rootfs_device))
424 if not result.success:
425 raise Error, '%r failed, stderr: %r' % (script, result.stderr)
426
427
428@Command('verify_switch_wp')
Tammo Spalink01e11722012-07-24 10:17:54 -0700429def VerifyWpSwitch(options): # pylint: disable=W0613
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800430 """Verify hardware write protection switch is enabled."""
431 if Shell('crossystem wpsw_cur').stdout.strip() != '1':
Hung-Te Lin6d827542012-07-19 11:50:41 +0800432 raise Error, 'write protection switch is disabled'
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800433
434
435@Command('verify_switch_dev')
Tammo Spalink01e11722012-07-24 10:17:54 -0700436def VerifyDevSwitch(options): # pylint: disable=W0613
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800437 """Verify developer switch is disabled."""
Hung-Te Lind7d34722012-07-26 16:48:35 +0800438 VBSD_HONOR_VIRT_DEV_SWITCH = 0x400
439 flags = int(Shell('crossystem vdat_flags').stdout.strip(), 0)
440 if (flags & VBSD_HONOR_VIRT_DEV_SWITCH) != 0:
441 # System is using virtual developer switch. That will be handled in
442 # prepare_wipe.sh by setting "crossystem disable_dev_request=1" -- although
443 # we can't verify that until next reboot, because the real values are stored
444 # in TPM.
445 logging.warn('VerifyDevSwitch: No physical switch.')
446 _event_log.Log('switch_dev', type='virtual switch')
447 return
448 if Shell('crossystem devsw_cur').stdout.strip() != '0':
449 raise Error, 'developer mode is not disabled'
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800450
451
452@Command('write_protect')
Tammo Spalink01e11722012-07-24 10:17:54 -0700453def EnableFwWp(options): # pylint: disable=W0613
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800454 """Enable then verify firmware write protection."""
455
456 def WriteProtect(fw_file_path, fw_type, section):
457 """Calculate protection size, then invoke flashrom.
458
459 Our supported chips only allow write protecting half their total
460 size, so we parition the flash chipset space accordingly.
461 """
462 raw_image = open(fw_file_path, 'rb').read()
463 image = crosfw.FirmwareImage(raw_image)
464 if not image.has_section(section):
465 raise Error('could not find %s firmware section %s' % (fw_type, section))
466 section_data = image.get_section_area(section)
467 protectable_size = len(raw_image) / 2
468 ro_a = int(section_data[0] / protectable_size)
469 ro_b = int((section_data[0] + section_data[1] - 1) / protectable_size)
470 if ro_a != ro_b:
471 raise Error("%s firmware section %s has illegal size" %
472 (fw_type, section))
473 ro_offset = ro_a * protectable_size
474 logging.debug('write protecting %s', fw_type)
475 crosfw.Flashrom(fw_type).EnableWriteProtection(ro_offset, protectable_size)
476
477 WriteProtect(crosfw.LoadMainFirmware().GetFileName(), 'main', 'RO_SECTION')
Tammo Spalink86a61c62012-05-25 15:10:35 +0800478 _event_log.Log('wp', fw='main')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800479 ec_fw_file = crosfw.LoadEcFirmware().GetFileName()
480 if ec_fw_file is not None:
Hung-Te Lin6d827542012-07-19 11:50:41 +0800481 # TODO(hungte) Support WP_RO if that section exist.
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800482 WriteProtect(ec_fw_file, 'ec', 'EC_RO')
Tammo Spalink86a61c62012-05-25 15:10:35 +0800483 _event_log.Log('wp', fw='ec')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800484 else:
485 logging.warning('EC not write protected (seems there is no EC flash).')
486
487
488@Command('clear_gbb_flags')
Tammo Spalink01e11722012-07-24 10:17:54 -0700489def ClearGbbFlags(options): # pylint: disable=W0613
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800490 """Zero out the GBB flags, in preparation for transition to release state.
491
492 No GBB flags are set in release/shipping state, but they are useful
493 for factory/development. See "gbb_utility --flags" for details.
494 """
Tammo Spalink461ddce2012-05-10 19:28:55 +0800495 script = FindScript('clear_gbb_flags.sh')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800496 result = Shell(script)
497 if not result.success:
498 raise Error, '%r failed, stderr: %r' % (script, result.stderr)
Tammo Spalink86a61c62012-05-25 15:10:35 +0800499 _event_log.Log('clear_gbb_flags')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800500
501
502@Command('prepare_wipe',
503 CmdArg('--fast', action='store_true',
504 help='use non-secure but faster wipe method.'))
505def PrepareWipe(options):
506 """Prepare system for transition to release state in next reboot."""
Tammo Spalink461ddce2012-05-10 19:28:55 +0800507 script = FindScript('prepare_wipe.sh')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800508 tag = 'fast' if options.fast else ''
509 rootfs_device = GetReleaseRootPartitionPath()
510 result = Shell('FACTORY_WIPE_TAGS=%s %s %s' % (tag, script, rootfs_device))
511 if not result.success:
512 raise Error, '%r failed, stderr: %r' % (script, result.stderr)
513
514
515@Command('verify',
Hung-Te Lin6d827542012-07-19 11:50:41 +0800516 CmdArg('--no_write_protect', action='store_true',
517 help='Do not check write protection switch state.'),
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800518 _hwdb_path_cmd_arg)
519def Verify(options):
520 """Verifies if whole factory process is ready for finalization.
521
522 This routine performs all the necessary checks to make sure the
523 device is ready to be finalized, but does not modify state. These
524 checks include dev switch, firmware write protection switch, hwid,
525 system time, keys, and root file system.
526 """
Hung-Te Lin6d827542012-07-19 11:50:41 +0800527 if not options.no_write_protect:
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800528 VerifyWpSwitch({})
Hung-Te Lin6d827542012-07-19 11:50:41 +0800529 VerifyDevSwitch({})
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800530 VerifyHwid(options)
531 VerifySystemTime({})
532 VerifyKeys({})
533 VerifyRootFs({})
534
535
Tammo Spalink86a61c62012-05-25 15:10:35 +0800536@Command('log_system_details')
Tammo Spalink01e11722012-07-24 10:17:54 -0700537def LogSystemDetails(options): # pylint: disable=W0613
Tammo Spalink86a61c62012-05-25 15:10:35 +0800538 """Write miscellaneous system details to the event log."""
539 raw_cs_data = Shell('crossystem').stdout.strip().splitlines()
540 # The crossytem output contains many lines like:
541 # 'key = value # description'
542 # Use regexps to pull out the key-value pairs and build a dict.
543 cs_data = dict((k, v.strip()) for k, v in
544 map(lambda x: re.findall(r'\A(\S+)\s+=\s+(.*)#.*\Z', x)[0],
545 raw_cs_data))
546 _event_log.Log(
547 'system_details',
548 platform_name=Shell('mosys platform name').stdout.strip(),
549 crossystem=cs_data,
550 modem_status=Shell('modem status').stdout.splitlines(),
551 ec_wp_status=Shell(
552 'flashrom -p internal:bus=lpc --get-size 2>/dev/null && '
553 'flashrom -p internal:bus=lpc --wp-status || '
554 'echo "EC is not available."').stdout,
555 bios_wp_status = Shell(
556 'flashrom -p internal:bus=spi --wp-status').stdout)
557
558
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800559_upload_method_cmd_arg = CmdArg(
560 '--upload_method', metavar='METHOD:PARAM',
561 help=('How to perform the upload. METHOD should be one of '
562 '{ftp, shopfloor, curl, cpfe, custom}.'))
Jon Salz65266432012-07-30 19:02:49 +0800563_add_file_cmd_arg = CmdArg(
564 '--add_file', metavar='FILE', action='append',
565 help='Extra file to include in report (must be an absolute path)')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800566
567@Command('upload_report',
Jon Salz65266432012-07-30 19:02:49 +0800568 _upload_method_cmd_arg,
569 _add_file_cmd_arg)
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800570def UploadReport(options):
571 """Create and a report containing key device details."""
Hung-Te Lin6bd16472012-06-20 16:26:47 +0800572 def NormalizeAsFileName(token):
573 return re.sub(r'\W+', '', token).strip()
Tammo Spalink86a61c62012-05-25 15:10:35 +0800574 ro_vpd = ReadRoVpd(crosfw.LoadMainFirmware().GetFileName())
575 device_sn = ro_vpd.get('serial_number', None)
576 if device_sn is None:
577 logging.warning('RO_VPD missing device serial number')
578 device_sn = 'MISSING_SN_' + TimedUuid()
Hung-Te Lin6bd16472012-06-20 16:26:47 +0800579 target_name = '%s_%s.tbz2' % (time.strftime('%Y%m%dT%H%M%SZ', time.gmtime()),
580 NormalizeAsFileName(device_sn))
Tammo Spalink86a61c62012-05-25 15:10:35 +0800581 target_path = os.path.join(gettempdir(), target_name)
582 # Intentionally ignoring dotfiles in EVENT_LOG_DIR.
583 tar_cmd = 'cd %s ; tar cjf %s *' % (EVENT_LOG_DIR, target_path)
584 tar_cmd += ' --add-file %s' % FACTORY_LOG_PATH
Jon Salz65266432012-07-30 19:02:49 +0800585 if options.add_file:
586 for f in options.add_file:
587 # Require absolute paths since the tar command may change the
588 # directory.
589 if not f.startswith('/'):
590 raise Error('Not an absolute path: %s' % f)
591 if not os.path.exists(f):
592 raise Error('File does not exist: %s' % f)
593 tar_cmd += ' --add-file %s' % pipes.quote(f)
Tammo Spalink86a61c62012-05-25 15:10:35 +0800594 cmd_result = Shell(tar_cmd)
595 if not cmd_result.success:
596 raise Error('unable to tar event logs, cmd %r failed, stderr: %r' %
597 (tar_cmd, cmd_result.stderr))
598 if options.upload_method is None or options.upload_method == 'none':
599 logging.warning('REPORT UPLOAD SKIPPED (report left at %s)', target_path)
600 return
601 method, param = options.upload_method.split(':', 1)
602 if method == 'shopfloor':
603 report_upload.ShopFloorUpload(target_path, param)
604 elif method == 'ftp':
Jay Kim360c1dd2012-06-25 10:58:11 -0700605 report_upload.FtpUpload(target_path, 'ftp:' + param)
Tammo Spalink86a61c62012-05-25 15:10:35 +0800606 elif method == 'ftps':
607 report_upload.CurlUrlUpload(target_path, '--ftp-ssl-reqd ftp:%s' % param)
608 elif method == 'cpfe':
609 report_upload.CpfeUpload(target_path, param)
610 else:
611 raise Error('unknown report upload method %r', method)
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800612
613
614@Command('finalize',
Hung-Te Lin6d827542012-07-19 11:50:41 +0800615 CmdArg('--no_write_protect', action='store_true',
616 help='Do not enable firmware write protection.'),
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800617 CmdArg('--fast', action='store_true',
618 help='use non-secure but faster wipe method.'),
619 _hwdb_path_cmd_arg,
Jon Salz65266432012-07-30 19:02:49 +0800620 _upload_method_cmd_arg,
621 _add_file_cmd_arg)
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800622def Finalize(options):
623 """Verify system readiness and trigger transition into release state.
624
Hung-Te Lin6d827542012-07-19 11:50:41 +0800625 This routine first verifies system state (see verify command), modifies
626 firmware bitmaps to match locale, and then clears all of the factory-friendly
627 flags from the GBB. If everything is fine, it enables firmware write
628 protection (cannot rollback after this stage), uploads system logs & reports,
629 and sets the necessary boot flags to cause wipe of the factory image on the
630 next boot.
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800631 """
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800632 Verify(options)
633 SetFirmwareBitmapLocale({})
Hung-Te Lin6d827542012-07-19 11:50:41 +0800634 ClearGbbFlags({})
635 if options.no_write_protect:
636 logging.warn('WARNING: Firmware Write Protection is SKIPPED.')
637 _event_log.Log('wp', fw='both', status='skipped')
638 else:
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800639 EnableFwWp({})
Jon Salza0f58e02012-05-29 19:33:39 +0800640 LogSystemDetails(options)
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800641 UploadReport(options)
642 PrepareWipe(options)
643
644
645def Main():
646 """Run sub-command specified by the command line args."""
647 options = ParseCmdline(
648 'Perform Google required factory tests.',
649 CmdArg('-l', '--log', metavar='PATH',
650 help='Write logs to this file.'),
Tammo Spalink8fab5312012-05-28 18:33:30 +0800651 verbosity_cmd_arg)
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800652 SetupLogging(options.verbosity, options.log)
653 logging.debug('gooftool options: %s', repr(options))
654 try:
655 logging.debug('GOOFTOOL command %r', options.command_name)
656 options.command(options)
657 logging.info('GOOFTOOL command %r SUCCESS', options.command_name)
658 except Error, e:
659 logging.exception(e)
660 sys.exit('GOOFTOOL command %r ERROR: %s' % (options.command_name, e))
661 except Exception, e:
662 logging.exception(e)
663 sys.exit('UNCAUGHT RUNTIME EXCEPTION %s' % e)
664
665
666if __name__ == '__main__':
667 Main()