blob: 9199b5ecc0373233ad2e8088774eef99eb2a88c8 [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
18import re
19import sys
Hung-Te Lin6bd16472012-06-20 16:26:47 +080020import time
Tammo Spalink9a96b8a2012-04-03 11:10:41 +080021
Tammo Spalink8fab5312012-05-28 18:33:30 +080022from tempfile import gettempdir, NamedTemporaryFile
Tammo Spalink86a61c62012-05-25 15:10:35 +080023
Tammo Spalinka40293e2012-07-04 14:58:56 +080024import factory_common # pylint: disable=W0611
Tammo Spalink9a96b8a2012-04-03 11:10:41 +080025
Tammo Spalinka40293e2012-07-04 14:58:56 +080026from cros.factory.common import Error, ParseKeyValueData, SetupLogging, Shell
27from cros.factory.common import YamlRead, YamlWrite
Tammo Spalink01e11722012-07-24 10:17:54 -070028from cros.factory.gooftool import crosfw
29from cros.factory.gooftool import report_upload
30from cros.factory.gooftool.bmpblk import unpack_bmpblock
31from cros.factory.gooftool.probe import Probe
32from cros.factory.gooftool.vpd_data import KNOWN_VPD_FIELD_DATA
Tammo Spalinka40293e2012-07-04 14:58:56 +080033from cros.factory.hacked_argparse import CmdArg, Command, ParseCmdline
34from cros.factory.hacked_argparse import verbosity_cmd_arg
Tammo Spalink01e11722012-07-24 10:17:54 -070035from cros.factory.hwdb import hwid_tool
Jon Salz83591782012-06-26 11:09:58 +080036from cros.factory.event_log import EventLog, EVENT_LOG_DIR
37from cros.factory.event_log import TimedUuid
cychiang7fe09372012-07-04 14:31:23 +080038from cros.factory.test.factory import FACTORY_LOG_PATH
Tammo Spalink86a61c62012-05-25 15:10:35 +080039
Tammo Spalink5c699832012-07-03 17:50:39 +080040
Tammo Spalink86a61c62012-05-25 15:10:35 +080041# Use a global event log, so that only a single log is created when
42# gooftool is called programmatically.
43_event_log = EventLog('gooftool')
44
Tammo Spalink9a96b8a2012-04-03 11:10:41 +080045
46def GetPrimaryDevicePath(partition=None):
47 def IsFixed(dev):
48 sysfs_path = '/sys/block/%s/removable' % dev
49 return (os.path.exists(sysfs_path) and
50 open(sysfs_path).read().strip() == '0')
51 alpha_re = re.compile(r'^/dev/([a-zA-Z]+)[0-9]+$')
52 alnum_re = re.compile(r'^/dev/([a-zA-Z]+[0-9]+)p[0-9]+$')
cychiangde1dee22012-05-22 09:42:09 +080053 matched_alnum = False
54 dev_set = set()
55 for path in Shell('cgpt find -t rootfs').stdout.strip().split():
56 for dev in alpha_re.findall(path):
57 if IsFixed(dev):
58 dev_set.add(dev)
59 matched_alnum = False
60 for dev in alnum_re.findall(path):
61 if IsFixed(dev):
62 dev_set.add(dev)
63 matched_alnum = True
Tammo Spalink9a96b8a2012-04-03 11:10:41 +080064 if len(dev_set) != 1:
65 raise Error('zero or multiple primary devs: %s' % dev_set)
66 dev_path = os.path.join('/dev', dev_set.pop())
67 if partition is None:
68 return dev_path
cychiangde1dee22012-05-22 09:42:09 +080069 fmt_str = '%sp%d' if matched_alnum else '%s%d'
Tammo Spalink9a96b8a2012-04-03 11:10:41 +080070 return fmt_str % (dev_path, partition)
71
72
73def GetReleaseRootPartitionPath():
74 return GetPrimaryDevicePath(5)
75
76
77def GetReleaseKernelPartitionPath():
78 return GetPrimaryDevicePath(4)
79
80
81def FindScript(script_name):
Jon Salzd24269c2012-05-29 17:22:59 +080082 script_path = os.path.join(os.path.dirname(os.path.dirname(
83 os.path.realpath(__file__))), 'sh', script_name)
Tammo Spalink9a96b8a2012-04-03 11:10:41 +080084 if not os.path.exists(script_path):
85 raise Error('Needed script %s does not exist.' % script_path)
86 return script_path
87
88
Tammo Spalink86a61c62012-05-25 15:10:35 +080089def ReadVpd(fw_image_file, kind):
90 raw_vpd_data = Shell('vpd -i %s -l -f %s' % (kind, fw_image_file)).stdout
Tammo Spalink9a96b8a2012-04-03 11:10:41 +080091 return ParseKeyValueData('"(.*)"="(.*)"$', raw_vpd_data)
92
93
Tammo Spalink86a61c62012-05-25 15:10:35 +080094def ReadRoVpd(fw_image_file):
95 return ReadVpd(fw_image_file, 'RO_VPD')
96
97
98def ReadRwVpd(fw_image_file):
99 return ReadVpd(fw_image_file, 'RW_VPD')
100
101
Tammo Spalink5c699832012-07-03 17:50:39 +0800102# TODO(tammo): Replace calls to sys.exit with raise Exit, and maybe
103# treat that specially (as a smoot exit, as opposed to the more
104# verbose output for generic Error).
105
106
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800107@Command('write_hwid',
108 CmdArg('hwid', metavar='HWID', help='HWID string'))
109def WriteHwid(options):
110 """Write specified HWID value into the system BB."""
111 logging.debug('writing hwid string %r', options.hwid)
112 main_fw = crosfw.LoadMainFirmware()
113 Shell('gbb_utility --set --hwid="%s" "%s"' %
114 (options.hwid, main_fw.GetFileName()))
115 main_fw.Write(sections=['GBB'])
Tammo Spalink86a61c62012-05-25 15:10:35 +0800116 _event_log.Log('write_hwid', hwid=options.hwid)
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800117
118
Tammo Spalink8fab5312012-05-28 18:33:30 +0800119_hwdb_path_cmd_arg = CmdArg(
120 '--hwdb_path', metavar='PATH',
121 default=hwid_tool.DEFAULT_HWID_DATA_PATH,
122 help='Path to the HWID database.')
123
124
125@Command('probe_hwids',
126 _hwdb_path_cmd_arg,
127 CmdArg('-b', '--board', metavar='BOARD',
128 help='BOARD name', required=True),
129 CmdArg('--bom', metavar='BOM', help='BOM name'),
130 CmdArg('--variant', metavar='VARIANT', help='VARIANT code'),
Tammo Spalink5c699832012-07-03 17:50:39 +0800131 CmdArg('--optimistic', action='store_true',
132 help='do not probe; assume singletons match'),
133 CmdArg('--stdin_comp_map', action='store_true'),
134 CmdArg('--status', nargs='*', default=['supported'],
Tammo Spalink8fab5312012-05-28 18:33:30 +0800135 help='consider only HWIDs with this status'))
Tammo Spalink5c699832012-07-03 17:50:39 +0800136# TODO(tammo): Add a 'hw-only probe' option that does not probe firmware.
Tammo Spalink8fab5312012-05-28 18:33:30 +0800137def ProbeHwid(options):
138 """Determine a list of possible HWIDs using provided args and probeing.
139
140 VOLATILE can always be determined by probing. To get a unique
141 result, VARIANT must be specified for all cases where the matching
142 BOM has more than one associated variant code, otherwise all HWID
143 variants will be returned. Both VARIANT and BOM information can
Tammo Spalink5c699832012-07-03 17:50:39 +0800144 alternatively be specified using the --stdin_comp_map argument,
145 which allows specifying a list of
Tammo Spalink8fab5312012-05-28 18:33:30 +0800146
147 component-class: canonical-component-name
148
149 pairs on stdin, one per line (yaml format). Based on what is known
Tammo Spalink5c699832012-07-03 17:50:39 +0800150 from BOM and stdin_comp_map, determine a list of components to probe
151 for, and use those probe results to resolve a list of matching
152 HWIDs. If no boms, components, or variant codes are specified, then
153 a list of all HWIDs that match probable components will be returned.
Tammo Spalink8fab5312012-05-28 18:33:30 +0800154
155 Returns (on stdout): A list of HWIDs that match the available probe
156 results and argument contraints, one per line.
157 """
Tammo Spalink5c699832012-07-03 17:50:39 +0800158 map(hwid_tool.Validate.Status, options.status)
Tammo Spalink01e11722012-07-24 10:17:54 -0700159 hw_db = hwid_tool.HardwareDb(options.data_path)
Tammo Spalink5c699832012-07-03 17:50:39 +0800160 comp_db = hw_db.comp_db
Tammo Spalink3a7e9022012-06-27 14:08:40 +0800161 device = hw_db.GetDevice(options.board)
Tammo Spalink5c699832012-07-03 17:50:39 +0800162 component_spec = hwid_tool.ComponentSpec.New()
Tammo Spalink8fab5312012-05-28 18:33:30 +0800163 if options.bom:
Tammo Spalink5c699832012-07-03 17:50:39 +0800164 device.BomExists(options.bom)
Tammo Spalink01e11722012-07-24 10:17:54 -0700165 component_spec = hwid_tool.CombineComponentSpecs(
Tammo Spalink5c699832012-07-03 17:50:39 +0800166 component_spec, device.boms[options.bom].primary)
Tammo Spalink8fab5312012-05-28 18:33:30 +0800167 if options.variant:
Tammo Spalink5c699832012-07-03 17:50:39 +0800168 device.VariantExists(options.variant)
169 variant_spec = device.variants[options.variant]
Tammo Spalink01e11722012-07-24 10:17:54 -0700170 if hwid_tool.ComponentSpecsConflict(component_spec, variant_spec):
171 # TODO(tammo): This error meesage arg is wrong; fix this when
172 # also making the whole function work (it is definitely broken).
Tammo Spalink5c699832012-07-03 17:50:39 +0800173 sys.exit('ERROR: multiple specifications for %r components'
Tammo Spalink01e11722012-07-24 10:17:54 -0700174 ' (both VARIANT and BOM)' % component_spec)
175 component_spec = hwid_tool.CombineComponentSpecs(
176 component_spec, variant_spec)
Tammo Spalink5c699832012-07-03 17:50:39 +0800177 if options.stdin_comp_map:
Tammo Spalink8fab5312012-05-28 18:33:30 +0800178 input_map = YamlRead(sys.stdin.read())
179 logging.info('stdin component map: %r', input_map)
Tammo Spalink01e11722012-07-24 10:17:54 -0700180 spec_classes = hwid_tool.ComponentSpecClasses(component_spec)
Tammo Spalink8fab5312012-05-28 18:33:30 +0800181 for key, value in input_map.items():
Tammo Spalink01e11722012-07-24 10:17:54 -0700182 if key not in comp_db.all_comp_classes:
Tammo Spalink8fab5312012-05-28 18:33:30 +0800183 sys.exit('ERROR: unknown component class %r (from stdin)' % key)
Tammo Spalink5c699832012-07-03 17:50:39 +0800184 if value not in comp_db.all_comp_names:
Tammo Spalink8fab5312012-05-28 18:33:30 +0800185 sys.exit('ERROR: unkown component name %r (from stdin)' % value)
Tammo Spalink5c699832012-07-03 17:50:39 +0800186 if key in spec_classes:
Tammo Spalink8fab5312012-05-28 18:33:30 +0800187 sys.exit('ERROR: multiple specifications for %r components'
188 ' (stdin and BOM/VARIANT)' % key)
Tammo Spalink5c699832012-07-03 17:50:39 +0800189 component_spec.components.update(input_map)
Tammo Spalink01e11722012-07-24 10:17:54 -0700190 logging.info('component spec used for matching:\n%s', component_spec.Encode)
191 spec_classes = hwid_tool.ComponentSpecClasses(component_spec)
Tammo Spalink5c699832012-07-03 17:50:39 +0800192 missing_classes = list(set(comp_db.all_comp_classes) - spec_classes)
193 if missing_classes and not options.optimistic:
194 logging.info('probing for missing classes %s', ', '.join(missing_classes))
Tammo Spalink01e11722012-07-24 10:17:54 -0700195 probe_results = Probe(target_comp_classes=missing_classes,
Tammo Spalink5c699832012-07-03 17:50:39 +0800196 probe_volatile=True, probe_initial_config=False)
197 else:
198 probe_results = hwid_tool.ProbeResults(
199 found_components=component_spec.components,
200 missing_component_classes=component_spec.classes_missing,
201 volatiles={}, initial_configs={})
202 cooked_results = hwid_tool.CookedProbeResults(comp_db, device, probe_results)
203 if not cooked_results.match_tree:
204 sys.exit('NO matching BOMs found')
205 if cooked_results.matched_hwids:
206 print '\n'.join(cooked_results.matched_hwids)
207 return
Tammo Spalink01e11722012-07-24 10:17:54 -0700208 logging.info('exact HWID matching failed, but the following BOMs match: %s',
Tammo Spalink5c699832012-07-03 17:50:39 +0800209 ', '.join(sorted(cooked_results.match_tree)))
210 if options.optimistic and len(cooked_results.match_tree) == 1:
211 bom_name = set(cooked_results.match_tree).pop()
212 bom = device.boms[bom_name]
213 variant_matches = cooked_results.match_tree[bom_name]
214 if len(variant_matches) == 1:
215 var_code = set(variant_matches).pop()
216 elif len(bom.variants) == 1:
217 var_code = set(bom.variants).pop()
218 else:
219 sys.exit('NO matching HWIDs found; optimistic matching failed because '
220 'there were too many variants to choose from for BOM %r' %
221 bom_name)
222 print '\n'.join(
223 device.FmtHwid(bom_name, var_code, vol_code)
224 for vol_code in device.volatiles
225 if device.GetHwidStatus(bom_name, var_code, vol_code)
226 in options.status)
227 return
228 else:
229 logging.info('optimistic matching not attempted because either it was '
230 'not requested, or because the number of BOMs was <> 1')
Tammo Spalink01e11722012-07-24 10:17:54 -0700231 print 'NO matching HWIDs found'
Tammo Spalink8fab5312012-05-28 18:33:30 +0800232
233
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800234@Command('probe',
235 CmdArg('--comps', nargs='*',
236 help='List of keys from the component_db registry.'),
237 CmdArg('--no_vol', action='store_true',
238 help='Do not probe volatile data.'),
239 CmdArg('--no_ic', action='store_true',
240 help='Do not probe initial_config data.'))
241def RunProbe(options):
242 """Print yaml-formatted breakdown of probed device properties."""
Tammo Spalink01e11722012-07-24 10:17:54 -0700243 probe_results = Probe(target_comp_classes=options.comps,
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800244 probe_volatile=not options.no_vol,
245 probe_initial_config=not options.no_ic)
Tammo Spalink3a7e9022012-06-27 14:08:40 +0800246 print probe_results.Encode()
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800247
248
Tammo Spalink214caf42012-05-28 10:45:00 +0800249@Command('verify_components',
250 _hwdb_path_cmd_arg,
Tammo Spalink5c699832012-07-03 17:50:39 +0800251 CmdArg('target_comps', nargs='*'))
Tammo Spalink214caf42012-05-28 10:45:00 +0800252def VerifyComponents(options):
253 """Verify that probable components all match entries in the component_db.
254
Tammo Spalink5c699832012-07-03 17:50:39 +0800255 Probe for each component class in the target_comps and verify
Tammo Spalink214caf42012-05-28 10:45:00 +0800256 that a corresponding match exists in the component_db -- make sure
257 that these components are present, that they have been approved, but
258 do not check against any specific BOM/HWID configurations.
259 """
Tammo Spalink5c699832012-07-03 17:50:39 +0800260 comp_db = hwid_tool.HardwareDb(options.hwdb_path).comp_db
261 if not options.target_comps:
262 sys.exit('ERROR: no target component classes specified; possible choices:\n'
263 + '\n '.join(sorted(comp_db.components)))
264 for comp_class in options.target_comps:
265 if comp_class not in comp_db.components:
266 sys.exit('ERROR: specified component class %r does not exist'
Tammo Spalink214caf42012-05-28 10:45:00 +0800267 ' in the component DB.' % comp_class)
Tammo Spalink01e11722012-07-24 10:17:54 -0700268 probe_results = Probe(target_comp_classes=options.target_comps,
Tammo Spalink214caf42012-05-28 10:45:00 +0800269 probe_volatile=False, probe_initial_config=False)
Tammo Spalink214caf42012-05-28 10:45:00 +0800270 errors = []
271 matches = []
Tammo Spalink5c699832012-07-03 17:50:39 +0800272 for comp_class in sorted(options.target_comps):
Tammo Spalink214caf42012-05-28 10:45:00 +0800273 probe_val = probe_results.found_components.get(comp_class, None)
274 if probe_val is not None:
Tammo Spalink5c699832012-07-03 17:50:39 +0800275 comp_name = comp_db.result_name_map.get(probe_val, None)
Tammo Spalink214caf42012-05-28 10:45:00 +0800276 if comp_name is not None:
277 matches.append(comp_name)
278 else:
279 errors.append('unsupported %r component found with probe result'
280 ' %r (no matching name in the component DB)' %
281 (comp_class, probe_val))
282 else:
283 errors.append('missing %r component' % comp_class)
284 if errors:
285 print '\n'.join(errors)
286 sys.exit('component verification FAILURE')
287 else:
288 print 'component verification SUCCESS'
289 print 'found components:\n %s' % '\n '.join(matches)
290
291
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800292@Command('verify_hwid',
Tammo Spalink5c699832012-07-03 17:50:39 +0800293 CmdArg('--status', nargs='*', default=['supported'],
294 help='allow only HWIDs with these status values'),
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800295 _hwdb_path_cmd_arg)
296def VerifyHwid(options):
297 """Verify system HWID properties match probed device properties.
298
299 First probe components, volatile and initial_config parameters for
300 the DUT. Then use the available device data to produce a list of
301 candidate HWIDs. Then verify the HWID from the DUT is present in
302 that list. Then verify that the DUT initial config values match
303 those specified for its HWID. Finally, verify that VPD contains all
304 the necessary fields as specified by the board data, and when
305 possible verify that values are legitimate.
306 """
Tammo Spalink5c699832012-07-03 17:50:39 +0800307 def VerifyVpd(ro_vpd_keys):
308 ro_vpd = ReadRoVpd(main_fw_file)
309 for key in ro_vpd_keys:
310 if key not in ro_vpd:
311 sys.exit('Missing required VPD field: %s' % key)
Tammo Spalink01e11722012-07-24 10:17:54 -0700312 known_valid_values = KNOWN_VPD_FIELD_DATA.get(key, None)
Tammo Spalink5c699832012-07-03 17:50:39 +0800313 value = ro_vpd[key]
314 if known_valid_values is not None and value not in known_valid_values:
315 sys.exit('Invalid VPD entry : key %r, value %r' % (key, value))
316 rw_vpd = ReadRwVpd(main_fw_file)
317 _event_log.Log('vpd', ro_vpd=ro_vpd, rw_vpd=rw_vpd)
318 map(hwid_tool.Validate.Status, options.status)
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800319 main_fw_file = crosfw.LoadMainFirmware().GetFileName()
320 gbb_result = Shell('gbb_utility -g --hwid %s' % main_fw_file).stdout
321 hwid = re.findall(r'hardware_id:(.*)', gbb_result)[0].strip()
Tammo Spalink5c699832012-07-03 17:50:39 +0800322 hw_db = hwid_tool.HardwareDb(options.hwdb_path)
323 hwid_data = hw_db.GetHwidData(hwid)
324 logging.info('Verifying system HWID: %r', hwid_data.hwid)
325 if hwid_data.status not in options.status:
326 sys.exit('HWID status must be one of [%s], found %r' %
327 (', '.join(options.status, hwid_data.status)))
328 logging.debug('expected system properties:\n%s', hwid_data.Encode())
329 device = hw_db.GetDevice(hwid_data.board_name)
330 cooked_probe_results = hwid_tool.CookedProbeResults(
Tammo Spalink01e11722012-07-24 10:17:54 -0700331 hw_db.comp_db, device, Probe())
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800332 logging.debug('found system properties:\n%s',
Tammo Spalink01e11722012-07-24 10:17:54 -0700333 YamlWrite(cooked_probe_results.__dict__))
Tammo Spalink86a61c62012-05-25 15:10:35 +0800334 _event_log.Log(
Tammo Spalink5c699832012-07-03 17:50:39 +0800335 'probe',
Tammo Spalink01e11722012-07-24 10:17:54 -0700336 found_components=cooked_probe_results.found_components,
337 missing_component_classes=cooked_probe_results.missing_component_classes,
338 volatiles=cooked_probe_results.volatiles,
339 initial_configs=cooked_probe_results.initial_configs)
Tammo Spalink5c699832012-07-03 17:50:39 +0800340 if hwid not in cooked_probe_results.matched_hwids:
341 err_msg = 'HWID verification FAILED.\n'
342 if cooked_probe_results.unmatched_components:
343 sys.exit(err_msg + 'some components could not be indentified:\n%s' %
344 YamlWrite(cooked_probe_results.unmatched_components))
345 if not cooked_probe_results.match_tree:
346 sys.exit(err_msg + 'no matching boms were found for components:\n%s' %
347 cooked_probe_results.component_data.Encode())
348 if hwid.bom_name not in cooked_probe_results.match_tree:
349 sys.exit(err_msg + 'matching boms [%s] do not include target bom %r' %
350 (', '.join(sorted(cooked_probe_results.match_tree)),
351 hwid_data.bom))
352 err_msg += 'target bom %r matches components' % hwid_data.bom_name
353 if hwid.bom_name not in device.matched_ic_boms:
354 sys.exit(err_msg + ', but failed initial config verification')
355 match_tree = cooked_probe_results.match_tree
356 matched_variants = match_tree.get(hwid_data.bom_name, {})
357 if hwid.variant_code not in matched_variants:
358 sys.exit(err_msg + ', but target variant_code %r did not match' %
359 hwid_data.variant_code)
360 matched_volatiles = matched_variants.get(hwid_data.variant_code, {})
361 if hwid.volatile_code not in matched_volatiles:
362 sys.exit(err_msg + ', but target volatile_code %r did not match' %
363 hwid_data.volatile_code)
364 found_status = matched_volatiles.get(hwid_data.volatile_code, None)
365 sys.exit(err_msg + ', but hwid status %r was unacceptable' % found_status)
366 VerifyVpd(hwid_data.ro_vpds)
367 _event_log.Log('verified_hwid', hwid=hwid)
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800368
369
370@Command('verify_keys')
Tammo Spalink01e11722012-07-24 10:17:54 -0700371def VerifyKeys(options): # pylint: disable=W0613
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800372 """Verify keys in firmware and SSD match."""
Tammo Spalink461ddce2012-05-10 19:28:55 +0800373 script = FindScript('verify_keys.sh')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800374 kernel_device = GetReleaseKernelPartitionPath()
375 main_fw_file = crosfw.LoadMainFirmware().GetFileName()
376 result = Shell('%s %s %s' % (script, kernel_device, main_fw_file))
377 if not result.success:
378 raise Error, '%r failed, stderr: %r' % (script, result.stderr)
379
380
381@Command('set_fw_bitmap_locale')
Tammo Spalink01e11722012-07-24 10:17:54 -0700382def SetFirmwareBitmapLocale(options): # pylint: disable=W0613
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800383 """Use VPD locale value to set firmware bitmap default language."""
384 image_file = crosfw.LoadMainFirmware().GetFileName()
385 locale = ReadRoVpd(image_file).get('initial_locale', None)
386 if locale is None:
387 raise Error, 'Missing initial_locale VPD.'
388 bitmap_locales = []
389 with NamedTemporaryFile() as f:
390 Shell('gbb_utility -g --bmpfv=%s %s' % (f.name, image_file))
Tammo Spalink01e11722012-07-24 10:17:54 -0700391 bmpblk_data = unpack_bmpblock(f.read())
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800392 bitmap_locales = bmpblk_data.get('locales', bitmap_locales)
393 # Some locale values are just a language code and others are a
394 # hyphen-separated language code and country code pair. We care
395 # only about the language code part.
396 language_code = locale.partition('-')[0]
397 if language_code not in bitmap_locales:
398 raise Error, ('Firmware bitmaps do not contain support for the specified '
399 'initial locale language %r' % language_code)
400 else:
401 locale_index = bitmap_locales.index(language_code)
402 logging.info('Firmware bitmap initial locale set to %d (%s).',
403 locale_index, bitmap_locales[locale_index])
404 Shell('crossystem loc_idx=%d' % locale_index)
405
406
407@Command('verify_system_time')
Tammo Spalink01e11722012-07-24 10:17:54 -0700408def VerifySystemTime(options): # pylint: disable=W0613
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800409 """Verify system time is later than release filesystem creation time."""
Tammo Spalink461ddce2012-05-10 19:28:55 +0800410 script = FindScript('verify_system_time.sh')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800411 rootfs_device = GetReleaseRootPartitionPath()
412 result = Shell('%s %s' % (script, rootfs_device))
413 if not result.success:
414 raise Error, '%r failed, stderr: %r' % (script, result.stderr)
415
416
417@Command('verify_rootfs')
Tammo Spalink01e11722012-07-24 10:17:54 -0700418def VerifyRootFs(options): # pylint: disable=W0613
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800419 """Verify rootfs on SSD is valid by checking hash."""
Tammo Spalink461ddce2012-05-10 19:28:55 +0800420 script = FindScript('verify_rootfs.sh')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800421 rootfs_device = GetReleaseRootPartitionPath()
422 result = Shell('%s %s' % (script, rootfs_device))
423 if not result.success:
424 raise Error, '%r failed, stderr: %r' % (script, result.stderr)
425
426
427@Command('verify_switch_wp')
Tammo Spalink01e11722012-07-24 10:17:54 -0700428def VerifyWpSwitch(options): # pylint: disable=W0613
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800429 """Verify hardware write protection switch is enabled."""
430 if Shell('crossystem wpsw_cur').stdout.strip() != '1':
Hung-Te Lin6d827542012-07-19 11:50:41 +0800431 raise Error, 'write protection switch is disabled'
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800432
433
434@Command('verify_switch_dev')
Tammo Spalink01e11722012-07-24 10:17:54 -0700435def VerifyDevSwitch(options): # pylint: disable=W0613
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800436 """Verify developer switch is disabled."""
Hung-Te Lind7d34722012-07-26 16:48:35 +0800437 VBSD_HONOR_VIRT_DEV_SWITCH = 0x400
438 flags = int(Shell('crossystem vdat_flags').stdout.strip(), 0)
439 if (flags & VBSD_HONOR_VIRT_DEV_SWITCH) != 0:
440 # System is using virtual developer switch. That will be handled in
441 # prepare_wipe.sh by setting "crossystem disable_dev_request=1" -- although
442 # we can't verify that until next reboot, because the real values are stored
443 # in TPM.
444 logging.warn('VerifyDevSwitch: No physical switch.')
445 _event_log.Log('switch_dev', type='virtual switch')
446 return
447 if Shell('crossystem devsw_cur').stdout.strip() != '0':
448 raise Error, 'developer mode is not disabled'
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800449
450
451@Command('write_protect')
Tammo Spalink01e11722012-07-24 10:17:54 -0700452def EnableFwWp(options): # pylint: disable=W0613
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800453 """Enable then verify firmware write protection."""
454
455 def WriteProtect(fw_file_path, fw_type, section):
456 """Calculate protection size, then invoke flashrom.
457
458 Our supported chips only allow write protecting half their total
459 size, so we parition the flash chipset space accordingly.
460 """
461 raw_image = open(fw_file_path, 'rb').read()
462 image = crosfw.FirmwareImage(raw_image)
463 if not image.has_section(section):
464 raise Error('could not find %s firmware section %s' % (fw_type, section))
465 section_data = image.get_section_area(section)
466 protectable_size = len(raw_image) / 2
467 ro_a = int(section_data[0] / protectable_size)
468 ro_b = int((section_data[0] + section_data[1] - 1) / protectable_size)
469 if ro_a != ro_b:
470 raise Error("%s firmware section %s has illegal size" %
471 (fw_type, section))
472 ro_offset = ro_a * protectable_size
473 logging.debug('write protecting %s', fw_type)
474 crosfw.Flashrom(fw_type).EnableWriteProtection(ro_offset, protectable_size)
475
476 WriteProtect(crosfw.LoadMainFirmware().GetFileName(), 'main', 'RO_SECTION')
Tammo Spalink86a61c62012-05-25 15:10:35 +0800477 _event_log.Log('wp', fw='main')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800478 ec_fw_file = crosfw.LoadEcFirmware().GetFileName()
479 if ec_fw_file is not None:
Hung-Te Lin6d827542012-07-19 11:50:41 +0800480 # TODO(hungte) Support WP_RO if that section exist.
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800481 WriteProtect(ec_fw_file, 'ec', 'EC_RO')
Tammo Spalink86a61c62012-05-25 15:10:35 +0800482 _event_log.Log('wp', fw='ec')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800483 else:
484 logging.warning('EC not write protected (seems there is no EC flash).')
485
486
487@Command('clear_gbb_flags')
Tammo Spalink01e11722012-07-24 10:17:54 -0700488def ClearGbbFlags(options): # pylint: disable=W0613
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800489 """Zero out the GBB flags, in preparation for transition to release state.
490
491 No GBB flags are set in release/shipping state, but they are useful
492 for factory/development. See "gbb_utility --flags" for details.
493 """
Tammo Spalink461ddce2012-05-10 19:28:55 +0800494 script = FindScript('clear_gbb_flags.sh')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800495 result = Shell(script)
496 if not result.success:
497 raise Error, '%r failed, stderr: %r' % (script, result.stderr)
Tammo Spalink86a61c62012-05-25 15:10:35 +0800498 _event_log.Log('clear_gbb_flags')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800499
500
501@Command('prepare_wipe',
502 CmdArg('--fast', action='store_true',
503 help='use non-secure but faster wipe method.'))
504def PrepareWipe(options):
505 """Prepare system for transition to release state in next reboot."""
Tammo Spalink461ddce2012-05-10 19:28:55 +0800506 script = FindScript('prepare_wipe.sh')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800507 tag = 'fast' if options.fast else ''
508 rootfs_device = GetReleaseRootPartitionPath()
509 result = Shell('FACTORY_WIPE_TAGS=%s %s %s' % (tag, script, rootfs_device))
510 if not result.success:
511 raise Error, '%r failed, stderr: %r' % (script, result.stderr)
512
513
514@Command('verify',
Hung-Te Lin6d827542012-07-19 11:50:41 +0800515 CmdArg('--no_write_protect', action='store_true',
516 help='Do not check write protection switch state.'),
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800517 _hwdb_path_cmd_arg)
518def Verify(options):
519 """Verifies if whole factory process is ready for finalization.
520
521 This routine performs all the necessary checks to make sure the
522 device is ready to be finalized, but does not modify state. These
523 checks include dev switch, firmware write protection switch, hwid,
524 system time, keys, and root file system.
525 """
Hung-Te Lin6d827542012-07-19 11:50:41 +0800526 if not options.no_write_protect:
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800527 VerifyWpSwitch({})
Hung-Te Lin6d827542012-07-19 11:50:41 +0800528 VerifyDevSwitch({})
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800529 VerifyHwid(options)
530 VerifySystemTime({})
531 VerifyKeys({})
532 VerifyRootFs({})
533
534
Tammo Spalink86a61c62012-05-25 15:10:35 +0800535@Command('log_system_details')
Tammo Spalink01e11722012-07-24 10:17:54 -0700536def LogSystemDetails(options): # pylint: disable=W0613
Tammo Spalink86a61c62012-05-25 15:10:35 +0800537 """Write miscellaneous system details to the event log."""
538 raw_cs_data = Shell('crossystem').stdout.strip().splitlines()
539 # The crossytem output contains many lines like:
540 # 'key = value # description'
541 # Use regexps to pull out the key-value pairs and build a dict.
542 cs_data = dict((k, v.strip()) for k, v in
543 map(lambda x: re.findall(r'\A(\S+)\s+=\s+(.*)#.*\Z', x)[0],
544 raw_cs_data))
545 _event_log.Log(
546 'system_details',
547 platform_name=Shell('mosys platform name').stdout.strip(),
548 crossystem=cs_data,
549 modem_status=Shell('modem status').stdout.splitlines(),
550 ec_wp_status=Shell(
551 'flashrom -p internal:bus=lpc --get-size 2>/dev/null && '
552 'flashrom -p internal:bus=lpc --wp-status || '
553 'echo "EC is not available."').stdout,
554 bios_wp_status = Shell(
555 'flashrom -p internal:bus=spi --wp-status').stdout)
556
557
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800558_upload_method_cmd_arg = CmdArg(
559 '--upload_method', metavar='METHOD:PARAM',
560 help=('How to perform the upload. METHOD should be one of '
561 '{ftp, shopfloor, curl, cpfe, custom}.'))
562
563
564@Command('upload_report',
565 _upload_method_cmd_arg)
566def UploadReport(options):
567 """Create and a report containing key device details."""
Hung-Te Lin6bd16472012-06-20 16:26:47 +0800568 def NormalizeAsFileName(token):
569 return re.sub(r'\W+', '', token).strip()
Tammo Spalink86a61c62012-05-25 15:10:35 +0800570 ro_vpd = ReadRoVpd(crosfw.LoadMainFirmware().GetFileName())
571 device_sn = ro_vpd.get('serial_number', None)
572 if device_sn is None:
573 logging.warning('RO_VPD missing device serial number')
574 device_sn = 'MISSING_SN_' + TimedUuid()
Hung-Te Lin6bd16472012-06-20 16:26:47 +0800575 target_name = '%s_%s.tbz2' % (time.strftime('%Y%m%dT%H%M%SZ', time.gmtime()),
576 NormalizeAsFileName(device_sn))
Tammo Spalink86a61c62012-05-25 15:10:35 +0800577 target_path = os.path.join(gettempdir(), target_name)
578 # Intentionally ignoring dotfiles in EVENT_LOG_DIR.
579 tar_cmd = 'cd %s ; tar cjf %s *' % (EVENT_LOG_DIR, target_path)
580 tar_cmd += ' --add-file %s' % FACTORY_LOG_PATH
581 cmd_result = Shell(tar_cmd)
582 if not cmd_result.success:
583 raise Error('unable to tar event logs, cmd %r failed, stderr: %r' %
584 (tar_cmd, cmd_result.stderr))
585 if options.upload_method is None or options.upload_method == 'none':
586 logging.warning('REPORT UPLOAD SKIPPED (report left at %s)', target_path)
587 return
588 method, param = options.upload_method.split(':', 1)
589 if method == 'shopfloor':
590 report_upload.ShopFloorUpload(target_path, param)
591 elif method == 'ftp':
Jay Kim360c1dd2012-06-25 10:58:11 -0700592 report_upload.FtpUpload(target_path, 'ftp:' + param)
Tammo Spalink86a61c62012-05-25 15:10:35 +0800593 elif method == 'ftps':
594 report_upload.CurlUrlUpload(target_path, '--ftp-ssl-reqd ftp:%s' % param)
595 elif method == 'cpfe':
596 report_upload.CpfeUpload(target_path, param)
597 else:
598 raise Error('unknown report upload method %r', method)
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800599
600
601@Command('finalize',
Hung-Te Lin6d827542012-07-19 11:50:41 +0800602 CmdArg('--no_write_protect', action='store_true',
603 help='Do not enable firmware write protection.'),
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800604 CmdArg('--fast', action='store_true',
605 help='use non-secure but faster wipe method.'),
606 _hwdb_path_cmd_arg,
607 _upload_method_cmd_arg)
608def Finalize(options):
609 """Verify system readiness and trigger transition into release state.
610
Hung-Te Lin6d827542012-07-19 11:50:41 +0800611 This routine first verifies system state (see verify command), modifies
612 firmware bitmaps to match locale, and then clears all of the factory-friendly
613 flags from the GBB. If everything is fine, it enables firmware write
614 protection (cannot rollback after this stage), uploads system logs & reports,
615 and sets the necessary boot flags to cause wipe of the factory image on the
616 next boot.
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800617 """
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800618 Verify(options)
619 SetFirmwareBitmapLocale({})
Hung-Te Lin6d827542012-07-19 11:50:41 +0800620 ClearGbbFlags({})
621 if options.no_write_protect:
622 logging.warn('WARNING: Firmware Write Protection is SKIPPED.')
623 _event_log.Log('wp', fw='both', status='skipped')
624 else:
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800625 EnableFwWp({})
Jon Salza0f58e02012-05-29 19:33:39 +0800626 LogSystemDetails(options)
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800627 UploadReport(options)
628 PrepareWipe(options)
629
630
631def Main():
632 """Run sub-command specified by the command line args."""
633 options = ParseCmdline(
634 'Perform Google required factory tests.',
635 CmdArg('-l', '--log', metavar='PATH',
636 help='Write logs to this file.'),
Tammo Spalink8fab5312012-05-28 18:33:30 +0800637 verbosity_cmd_arg)
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800638 SetupLogging(options.verbosity, options.log)
639 logging.debug('gooftool options: %s', repr(options))
640 try:
641 logging.debug('GOOFTOOL command %r', options.command_name)
642 options.command(options)
643 logging.info('GOOFTOOL command %r SUCCESS', options.command_name)
644 except Error, e:
645 logging.exception(e)
646 sys.exit('GOOFTOOL command %r ERROR: %s' % (options.command_name, e))
647 except Exception, e:
648 logging.exception(e)
649 sys.exit('UNCAUGHT RUNTIME EXCEPTION %s' % e)
650
651
652if __name__ == '__main__':
653 Main()