blob: ff08eac8691306917794df0b81c631c00ca85c13 [file] [log] [blame]
Tammo Spalink9a96b8a2012-04-03 11:10:41 +08001#!/usr/bin/python
2#
3# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7"""Google Factory Tool.
8
9This tool is indended to be used on factory assembly lines. It
10provides all of the Google required test functionality and must be run
11on each device as part of the assembly process.
12"""
13
14
15import logging
16import os
17import re
18import sys
19
Tammo Spalink86a61c62012-05-25 15:10:35 +080020from tempfile import gettempdir
21
Tammo Spalink9a96b8a2012-04-03 11:10:41 +080022import bmpblk
23import crosfw
24import hwid_tool
25import probe
26import report
27import report_upload
28import vpd_data
29
30from common import Error, ParseKeyValueData, SetupLogging, Shell, YamlWrite
31from hacked_argparse import CmdArg, Command, ParseCmdline, verbosity_cmd_arg
32from tempfile import NamedTemporaryFile
33
Tammo Spalink86a61c62012-05-25 15:10:35 +080034# TODO(tammo): Remove imp logic once the cros/factory code moves into this repo.
35import imp
36at_common = imp.find_module('common', ['/usr/local/autotest/client/bin'])
37imp.load_module('at_common', *at_common)
38from autotest_lib.client.cros.factory.event_log import EventLog, EVENT_LOG_DIR
39from autotest_lib.client.cros.factory.event_log import TimeString, TimedUuid
40from autotest_lib.client.cros.factory import FACTORY_LOG_PATH
41
42
43# Use a global event log, so that only a single log is created when
44# gooftool is called programmatically.
45_event_log = EventLog('gooftool')
46
Tammo Spalink9a96b8a2012-04-03 11:10:41 +080047
48def GetPrimaryDevicePath(partition=None):
49 def IsFixed(dev):
50 sysfs_path = '/sys/block/%s/removable' % dev
51 return (os.path.exists(sysfs_path) and
52 open(sysfs_path).read().strip() == '0')
53 alpha_re = re.compile(r'^/dev/([a-zA-Z]+)[0-9]+$')
54 alnum_re = re.compile(r'^/dev/([a-zA-Z]+[0-9]+)p[0-9]+$')
cychiangde1dee22012-05-22 09:42:09 +080055 matched_alnum = False
56 dev_set = set()
57 for path in Shell('cgpt find -t rootfs').stdout.strip().split():
58 for dev in alpha_re.findall(path):
59 if IsFixed(dev):
60 dev_set.add(dev)
61 matched_alnum = False
62 for dev in alnum_re.findall(path):
63 if IsFixed(dev):
64 dev_set.add(dev)
65 matched_alnum = True
Tammo Spalink9a96b8a2012-04-03 11:10:41 +080066 if len(dev_set) != 1:
67 raise Error('zero or multiple primary devs: %s' % dev_set)
68 dev_path = os.path.join('/dev', dev_set.pop())
69 if partition is None:
70 return dev_path
cychiangde1dee22012-05-22 09:42:09 +080071 fmt_str = '%sp%d' if matched_alnum else '%s%d'
Tammo Spalink9a96b8a2012-04-03 11:10:41 +080072 return fmt_str % (dev_path, partition)
73
74
75def GetReleaseRootPartitionPath():
76 return GetPrimaryDevicePath(5)
77
78
79def GetReleaseKernelPartitionPath():
80 return GetPrimaryDevicePath(4)
81
82
83def FindScript(script_name):
cychiangde1dee22012-05-22 09:42:09 +080084 script_path = os.path.join(os.path.dirname(sys.path[0]), '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 Spalink9a96b8a2012-04-03 11:10:41 +0800103@Command('write_hwid',
104 CmdArg('hwid', metavar='HWID', help='HWID string'))
105def WriteHwid(options):
106 """Write specified HWID value into the system BB."""
107 logging.debug('writing hwid string %r', options.hwid)
108 main_fw = crosfw.LoadMainFirmware()
109 Shell('gbb_utility --set --hwid="%s" "%s"' %
110 (options.hwid, main_fw.GetFileName()))
111 main_fw.Write(sections=['GBB'])
Tammo Spalink86a61c62012-05-25 15:10:35 +0800112 _event_log.Log('write_hwid', hwid=options.hwid)
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800113
114
115@Command('probe',
116 CmdArg('--comps', nargs='*',
117 help='List of keys from the component_db registry.'),
118 CmdArg('--no_vol', action='store_true',
119 help='Do not probe volatile data.'),
120 CmdArg('--no_ic', action='store_true',
121 help='Do not probe initial_config data.'))
122def RunProbe(options):
123 """Print yaml-formatted breakdown of probed device properties."""
124 probe_results = probe.Probe(target_comp_classes=options.comps,
125 probe_volatile=not options.no_vol,
126 probe_initial_config=not options.no_ic)
127 print YamlWrite(probe_results.__dict__)
128
129
130_hwdb_path_cmd_arg = CmdArg(
131 '--hwdb_path', metavar='PATH',
132 default=hwid_tool.DEFAULT_HWID_DATA_PATH,
133 help='Path to the HWID database.')
134
135
136@Command('verify_hwid',
137 _hwdb_path_cmd_arg)
138def VerifyHwid(options):
139 """Verify system HWID properties match probed device properties.
140
141 First probe components, volatile and initial_config parameters for
142 the DUT. Then use the available device data to produce a list of
143 candidate HWIDs. Then verify the HWID from the DUT is present in
144 that list. Then verify that the DUT initial config values match
145 those specified for its HWID. Finally, verify that VPD contains all
146 the necessary fields as specified by the board data, and when
147 possible verify that values are legitimate.
148 """
149 hwdb = hwid_tool.ReadDatastore(options.hwdb_path)
150 main_fw_file = crosfw.LoadMainFirmware().GetFileName()
151 gbb_result = Shell('gbb_utility -g --hwid %s' % main_fw_file).stdout
152 hwid = re.findall(r'hardware_id:(.*)', gbb_result)[0].strip()
153 hwid_properties = hwid_tool.LookupHwidProperties(hwdb, hwid)
154 logging.info('Verifying system HWID: %r', hwid_properties.hwid)
155 logging.debug('expected system properties:\n%s',
156 YamlWrite(hwid_properties.__dict__))
157 probe_results = probe.Probe()
158 cooked_results = hwid_tool.CookProbeResults(
159 hwdb, probe_results, hwid_properties.board)
160 logging.debug('found system properties:\n%s',
161 YamlWrite(cooked_results.__dict__))
162 # TODO(tammo): Output a new-style log event with device details here.
163 match_errors = []
164 for comp_class, expected_name in hwid_properties.component_map.items():
165 if expected_name == 'ANY':
166 continue
167 if expected_name == cooked_results.matched_components.get(comp_class, None):
168 continue
169 if comp_class in probe_results.missing_components:
170 match_errors.append(' %s component mismatch, expected %s, found nothing'
171 % (comp_class, expected_name))
172 else:
173 probe_value = probe_results.found_components.get(comp_class, None)
174 match_errors.append(' %s component mismatch, expected %s, found %r' %
175 (comp_class, expected_name, probe_value))
176 if match_errors:
177 raise Error('HWID verification FAILED.\n%s' % '\n'.join(match_errors))
178 if hwid_properties.volatile not in cooked_results.matched_volatile_tags:
179 msg = (' HWID specified volatile %s, but found match only for %s' %
180 (hwid_properties.volatile,
181 ', '.join(cooked_results.matched_volatile_tags)))
182 raise Error('HWID verification FAILED.\n%s' % msg)
183 if (hwid_properties.initial_config is not None and
184 hwid_properties.initial_config not in
185 cooked_results.matched_initial_config_tags):
186 msg = (' HWID specified initial_config %s, but only found match for [%s]' %
187 (hwid_properties.initial_config,
188 ', '.join(cooked_results.matched_initial_config_tags)))
189 raise Error('HWID verification FAILED.\n%s' % msg)
190 # TODO(tammo): Verify HWID status is supported (or deprecated for RMA).
191 ro_vpd = ReadRoVpd(main_fw_file)
192 for field in hwid_properties.vpd_ro_field_list:
193 if field not in ro_vpd:
194 raise Error('Missing required VPD field: %s' % field)
195 known_valid_values = vpd_data.KNOWN_VPD_FIELD_DATA.get(field, None)
196 value = ro_vpd[field]
197 if known_valid_values is not None and value not in known_valid_values:
198 raise Error('Invalid VPD entry : field %r, value %r' % (field, value))
Tammo Spalink86a61c62012-05-25 15:10:35 +0800199 rw_vpd = ReadRwVpd(main_fw_file)
200 _event_log.Log(
201 'verify_hwid',
202 matched_components=cooked_results.matched_components,
203 initial_configs=cooked_results.matched_initial_config_tags,
204 volatiles=cooked_results.matched_volatile_tags,
205 ro_vpd=ro_vpd,
206 rw_vpd=rw_vpd)
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800207
208
209@Command('verify_keys')
210def VerifyKeys(options):
211 """Verify keys in firmware and SSD match."""
Tammo Spalink461ddce2012-05-10 19:28:55 +0800212 script = FindScript('verify_keys.sh')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800213 kernel_device = GetReleaseKernelPartitionPath()
214 main_fw_file = crosfw.LoadMainFirmware().GetFileName()
215 result = Shell('%s %s %s' % (script, kernel_device, main_fw_file))
216 if not result.success:
217 raise Error, '%r failed, stderr: %r' % (script, result.stderr)
218
219
220@Command('set_fw_bitmap_locale')
221def SetFirmwareBitmapLocale(options):
222 """Use VPD locale value to set firmware bitmap default language."""
223 image_file = crosfw.LoadMainFirmware().GetFileName()
224 locale = ReadRoVpd(image_file).get('initial_locale', None)
225 if locale is None:
226 raise Error, 'Missing initial_locale VPD.'
227 bitmap_locales = []
228 with NamedTemporaryFile() as f:
229 Shell('gbb_utility -g --bmpfv=%s %s' % (f.name, image_file))
230 bmpblk_data = bmpblk.unpack_bmpblock(f.read())
231 bitmap_locales = bmpblk_data.get('locales', bitmap_locales)
232 # Some locale values are just a language code and others are a
233 # hyphen-separated language code and country code pair. We care
234 # only about the language code part.
235 language_code = locale.partition('-')[0]
236 if language_code not in bitmap_locales:
237 raise Error, ('Firmware bitmaps do not contain support for the specified '
238 'initial locale language %r' % language_code)
239 else:
240 locale_index = bitmap_locales.index(language_code)
241 logging.info('Firmware bitmap initial locale set to %d (%s).',
242 locale_index, bitmap_locales[locale_index])
243 Shell('crossystem loc_idx=%d' % locale_index)
244
245
246@Command('verify_system_time')
247def VerifySystemTime(options):
248 """Verify system time is later than release filesystem creation time."""
Tammo Spalink461ddce2012-05-10 19:28:55 +0800249 script = FindScript('verify_system_time.sh')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800250 rootfs_device = GetReleaseRootPartitionPath()
251 result = Shell('%s %s' % (script, rootfs_device))
252 if not result.success:
253 raise Error, '%r failed, stderr: %r' % (script, result.stderr)
254
255
256@Command('verify_rootfs')
257def VerifyRootFs(options):
258 """Verify rootfs on SSD is valid by checking hash."""
Tammo Spalink461ddce2012-05-10 19:28:55 +0800259 script = FindScript('verify_rootfs.sh')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800260 rootfs_device = GetReleaseRootPartitionPath()
261 result = Shell('%s %s' % (script, rootfs_device))
262 if not result.success:
263 raise Error, '%r failed, stderr: %r' % (script, result.stderr)
264
265
266@Command('verify_switch_wp')
267def VerifyWpSwitch(options):
268 """Verify hardware write protection switch is enabled."""
269 if Shell('crossystem wpsw_cur').stdout.strip() != '1':
270 raise Error, 'write protection is disabled'
271
272
273@Command('verify_switch_dev')
274def VerifyDevSwitch(options):
275 """Verify developer switch is disabled."""
Hung-Te Lin8f643e32012-05-23 18:40:47 +0800276 result = Shell('crossystem devsw_cur')
277 if result.success:
278 if result.stdout.strip() != '0':
279 raise Error, 'developer mode is enabled'
280 else:
281 return
282 # Try ChromeOS-EC. This may hang 15 seconds if the EC does not respond.
283 logging.warn('VerifyDevSwitch: Trying ChromeOS-EC...')
284 if not Shell('ectool vboot 0').success:
285 raise Error, 'failed to turn off developer mode.'
286 # TODO(hungte) Verify if the switch is turned off properly, using "ectoo
287 # vboot" and parse the key-value pairs, when the names are determined.
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800288
289
290@Command('write_protect')
291def EnableFwWp(options):
292 """Enable then verify firmware write protection."""
293
294 def WriteProtect(fw_file_path, fw_type, section):
295 """Calculate protection size, then invoke flashrom.
296
297 Our supported chips only allow write protecting half their total
298 size, so we parition the flash chipset space accordingly.
299 """
300 raw_image = open(fw_file_path, 'rb').read()
301 image = crosfw.FirmwareImage(raw_image)
302 if not image.has_section(section):
303 raise Error('could not find %s firmware section %s' % (fw_type, section))
304 section_data = image.get_section_area(section)
305 protectable_size = len(raw_image) / 2
306 ro_a = int(section_data[0] / protectable_size)
307 ro_b = int((section_data[0] + section_data[1] - 1) / protectable_size)
308 if ro_a != ro_b:
309 raise Error("%s firmware section %s has illegal size" %
310 (fw_type, section))
311 ro_offset = ro_a * protectable_size
312 logging.debug('write protecting %s', fw_type)
313 crosfw.Flashrom(fw_type).EnableWriteProtection(ro_offset, protectable_size)
314
315 WriteProtect(crosfw.LoadMainFirmware().GetFileName(), 'main', 'RO_SECTION')
Tammo Spalink86a61c62012-05-25 15:10:35 +0800316 _event_log.Log('wp', fw='main')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800317 ec_fw_file = crosfw.LoadEcFirmware().GetFileName()
318 if ec_fw_file is not None:
319 WriteProtect(ec_fw_file, 'ec', 'EC_RO')
Tammo Spalink86a61c62012-05-25 15:10:35 +0800320 _event_log.Log('wp', fw='ec')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800321 else:
322 logging.warning('EC not write protected (seems there is no EC flash).')
323
324
325@Command('clear_gbb_flags')
326def ClearGbbFlags(options):
327 """Zero out the GBB flags, in preparation for transition to release state.
328
329 No GBB flags are set in release/shipping state, but they are useful
330 for factory/development. See "gbb_utility --flags" for details.
331 """
Tammo Spalink461ddce2012-05-10 19:28:55 +0800332 script = FindScript('clear_gbb_flags.sh')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800333 result = Shell(script)
334 if not result.success:
335 raise Error, '%r failed, stderr: %r' % (script, result.stderr)
Tammo Spalink86a61c62012-05-25 15:10:35 +0800336 _event_log.Log('clear_gbb_flags')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800337
338
339@Command('prepare_wipe',
340 CmdArg('--fast', action='store_true',
341 help='use non-secure but faster wipe method.'))
342def PrepareWipe(options):
343 """Prepare system for transition to release state in next reboot."""
Tammo Spalink461ddce2012-05-10 19:28:55 +0800344 script = FindScript('prepare_wipe.sh')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800345 tag = 'fast' if options.fast else ''
346 rootfs_device = GetReleaseRootPartitionPath()
347 result = Shell('FACTORY_WIPE_TAGS=%s %s %s' % (tag, script, rootfs_device))
348 if not result.success:
349 raise Error, '%r failed, stderr: %r' % (script, result.stderr)
350
351
352@Command('verify',
353 CmdArg('--dev', action='store_true',
354 help='Do not verify switch state (dev mode and fw wp).'),
355 _hwdb_path_cmd_arg)
356def Verify(options):
357 """Verifies if whole factory process is ready for finalization.
358
359 This routine performs all the necessary checks to make sure the
360 device is ready to be finalized, but does not modify state. These
361 checks include dev switch, firmware write protection switch, hwid,
362 system time, keys, and root file system.
363 """
364 if not options.dev:
365 VerifyDevSwitch({})
366 VerifyWpSwitch({})
367 VerifyHwid(options)
368 VerifySystemTime({})
369 VerifyKeys({})
370 VerifyRootFs({})
371
372
Tammo Spalink86a61c62012-05-25 15:10:35 +0800373@Command('log_system_details')
374def LogSystemDetails(options):
375 """Write miscellaneous system details to the event log."""
376 raw_cs_data = Shell('crossystem').stdout.strip().splitlines()
377 # The crossytem output contains many lines like:
378 # 'key = value # description'
379 # Use regexps to pull out the key-value pairs and build a dict.
380 cs_data = dict((k, v.strip()) for k, v in
381 map(lambda x: re.findall(r'\A(\S+)\s+=\s+(.*)#.*\Z', x)[0],
382 raw_cs_data))
383 _event_log.Log(
384 'system_details',
385 platform_name=Shell('mosys platform name').stdout.strip(),
386 crossystem=cs_data,
387 modem_status=Shell('modem status').stdout.splitlines(),
388 ec_wp_status=Shell(
389 'flashrom -p internal:bus=lpc --get-size 2>/dev/null && '
390 'flashrom -p internal:bus=lpc --wp-status || '
391 'echo "EC is not available."').stdout,
392 bios_wp_status = Shell(
393 'flashrom -p internal:bus=spi --wp-status').stdout)
394
395
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800396_upload_method_cmd_arg = CmdArg(
397 '--upload_method', metavar='METHOD:PARAM',
398 help=('How to perform the upload. METHOD should be one of '
399 '{ftp, shopfloor, curl, cpfe, custom}.'))
400
401
402@Command('upload_report',
403 _upload_method_cmd_arg)
404def UploadReport(options):
405 """Create and a report containing key device details."""
Tammo Spalink86a61c62012-05-25 15:10:35 +0800406 ro_vpd = ReadRoVpd(crosfw.LoadMainFirmware().GetFileName())
407 device_sn = ro_vpd.get('serial_number', None)
408 if device_sn is None:
409 logging.warning('RO_VPD missing device serial number')
410 device_sn = 'MISSING_SN_' + TimedUuid()
411 target_name = '%s_%s.tbz2' % (TimeString(), device_sn)
412 target_path = os.path.join(gettempdir(), target_name)
413 # Intentionally ignoring dotfiles in EVENT_LOG_DIR.
414 tar_cmd = 'cd %s ; tar cjf %s *' % (EVENT_LOG_DIR, target_path)
415 tar_cmd += ' --add-file %s' % FACTORY_LOG_PATH
416 cmd_result = Shell(tar_cmd)
417 if not cmd_result.success:
418 raise Error('unable to tar event logs, cmd %r failed, stderr: %r' %
419 (tar_cmd, cmd_result.stderr))
420 if options.upload_method is None or options.upload_method == 'none':
421 logging.warning('REPORT UPLOAD SKIPPED (report left at %s)', target_path)
422 return
423 method, param = options.upload_method.split(':', 1)
424 if method == 'shopfloor':
425 report_upload.ShopFloorUpload(target_path, param)
426 elif method == 'ftp':
427 report_upload.FtpUpload(target_path, 'ftp://' + param)
428 elif method == 'ftps':
429 report_upload.CurlUrlUpload(target_path, '--ftp-ssl-reqd ftp:%s' % param)
430 elif method == 'cpfe':
431 report_upload.CpfeUpload(target_path, param)
432 else:
433 raise Error('unknown report upload method %r', method)
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800434
435
436@Command('finalize',
437 CmdArg('--dev', action='store_true',
438 help='Do not verify or alter write protection or dev mode.'),
439 CmdArg('--fast', action='store_true',
440 help='use non-secure but faster wipe method.'),
441 _hwdb_path_cmd_arg,
442 _upload_method_cmd_arg)
443def Finalize(options):
444 """Verify system readiness and trigger transition into release state.
445
446 This routine first verifies system state (see verify command), then
447 clears all of the testing flags from the GBB, then modifies firmware
448 bitmaps to match locale. Then it enables firmware write protection
449 and sets the necessary boot flags to cause wipe of the factory image
450 on the next boot.
451 """
452 ClearGbbFlags({})
453 Verify(options)
454 SetFirmwareBitmapLocale({})
455 if not options.dev:
456 EnableFwWp({})
Tammo Spalink86a61c62012-05-25 15:10:35 +0800457 LogSystemDetails()
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800458 UploadReport(options)
459 PrepareWipe(options)
460
461
462def Main():
463 """Run sub-command specified by the command line args."""
464 options = ParseCmdline(
465 'Perform Google required factory tests.',
466 CmdArg('-l', '--log', metavar='PATH',
467 help='Write logs to this file.'),
468 verbosity_cmd_arg
469 )
470 SetupLogging(options.verbosity, options.log)
471 logging.debug('gooftool options: %s', repr(options))
472 try:
473 logging.debug('GOOFTOOL command %r', options.command_name)
474 options.command(options)
475 logging.info('GOOFTOOL command %r SUCCESS', options.command_name)
476 except Error, e:
477 logging.exception(e)
478 sys.exit('GOOFTOOL command %r ERROR: %s' % (options.command_name, e))
479 except Exception, e:
480 logging.exception(e)
481 sys.exit('UNCAUGHT RUNTIME EXCEPTION %s' % e)
482
483
484if __name__ == '__main__':
485 Main()