blob: cbb8c24b1c0812ca6dce056030ea0636e429dfa7 [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
20import bmpblk
21import crosfw
22import hwid_tool
23import probe
24import report
25import report_upload
26import vpd_data
27
28from common import Error, ParseKeyValueData, SetupLogging, Shell, YamlWrite
29from hacked_argparse import CmdArg, Command, ParseCmdline, verbosity_cmd_arg
30from tempfile import NamedTemporaryFile
31
32
33def GetPrimaryDevicePath(partition=None):
34 def IsFixed(dev):
35 sysfs_path = '/sys/block/%s/removable' % dev
36 return (os.path.exists(sysfs_path) and
37 open(sysfs_path).read().strip() == '0')
38 alpha_re = re.compile(r'^/dev/([a-zA-Z]+)[0-9]+$')
39 alnum_re = re.compile(r'^/dev/([a-zA-Z]+[0-9]+)p[0-9]+$')
cychiangde1dee22012-05-22 09:42:09 +080040 matched_alnum = False
41 dev_set = set()
42 for path in Shell('cgpt find -t rootfs').stdout.strip().split():
43 for dev in alpha_re.findall(path):
44 if IsFixed(dev):
45 dev_set.add(dev)
46 matched_alnum = False
47 for dev in alnum_re.findall(path):
48 if IsFixed(dev):
49 dev_set.add(dev)
50 matched_alnum = True
Tammo Spalink9a96b8a2012-04-03 11:10:41 +080051 if len(dev_set) != 1:
52 raise Error('zero or multiple primary devs: %s' % dev_set)
53 dev_path = os.path.join('/dev', dev_set.pop())
54 if partition is None:
55 return dev_path
cychiangde1dee22012-05-22 09:42:09 +080056 fmt_str = '%sp%d' if matched_alnum else '%s%d'
Tammo Spalink9a96b8a2012-04-03 11:10:41 +080057 return fmt_str % (dev_path, partition)
58
59
60def GetReleaseRootPartitionPath():
61 return GetPrimaryDevicePath(5)
62
63
64def GetReleaseKernelPartitionPath():
65 return GetPrimaryDevicePath(4)
66
67
68def FindScript(script_name):
cychiangde1dee22012-05-22 09:42:09 +080069 script_path = os.path.join(os.path.dirname(sys.path[0]), 'sh', script_name)
Tammo Spalink9a96b8a2012-04-03 11:10:41 +080070 if not os.path.exists(script_path):
71 raise Error('Needed script %s does not exist.' % script_path)
72 return script_path
73
74
75def ReadRoVpd(fw_image_file):
76 raw_vpd_data = Shell('vpd -i RO_VPD -l -f %s' % fw_image_file).stdout
77 return ParseKeyValueData('"(.*)"="(.*)"$', raw_vpd_data)
78
79
80@Command('write_hwid',
81 CmdArg('hwid', metavar='HWID', help='HWID string'))
82def WriteHwid(options):
83 """Write specified HWID value into the system BB."""
84 logging.debug('writing hwid string %r', options.hwid)
85 main_fw = crosfw.LoadMainFirmware()
86 Shell('gbb_utility --set --hwid="%s" "%s"' %
87 (options.hwid, main_fw.GetFileName()))
88 main_fw.Write(sections=['GBB'])
89
90
91@Command('probe',
92 CmdArg('--comps', nargs='*',
93 help='List of keys from the component_db registry.'),
94 CmdArg('--no_vol', action='store_true',
95 help='Do not probe volatile data.'),
96 CmdArg('--no_ic', action='store_true',
97 help='Do not probe initial_config data.'))
98def RunProbe(options):
99 """Print yaml-formatted breakdown of probed device properties."""
100 probe_results = probe.Probe(target_comp_classes=options.comps,
101 probe_volatile=not options.no_vol,
102 probe_initial_config=not options.no_ic)
103 print YamlWrite(probe_results.__dict__)
104
105
106_hwdb_path_cmd_arg = CmdArg(
107 '--hwdb_path', metavar='PATH',
108 default=hwid_tool.DEFAULT_HWID_DATA_PATH,
109 help='Path to the HWID database.')
110
111
112@Command('verify_hwid',
113 _hwdb_path_cmd_arg)
114def VerifyHwid(options):
115 """Verify system HWID properties match probed device properties.
116
117 First probe components, volatile and initial_config parameters for
118 the DUT. Then use the available device data to produce a list of
119 candidate HWIDs. Then verify the HWID from the DUT is present in
120 that list. Then verify that the DUT initial config values match
121 those specified for its HWID. Finally, verify that VPD contains all
122 the necessary fields as specified by the board data, and when
123 possible verify that values are legitimate.
124 """
125 hwdb = hwid_tool.ReadDatastore(options.hwdb_path)
126 main_fw_file = crosfw.LoadMainFirmware().GetFileName()
127 gbb_result = Shell('gbb_utility -g --hwid %s' % main_fw_file).stdout
128 hwid = re.findall(r'hardware_id:(.*)', gbb_result)[0].strip()
129 hwid_properties = hwid_tool.LookupHwidProperties(hwdb, hwid)
130 logging.info('Verifying system HWID: %r', hwid_properties.hwid)
131 logging.debug('expected system properties:\n%s',
132 YamlWrite(hwid_properties.__dict__))
133 probe_results = probe.Probe()
134 cooked_results = hwid_tool.CookProbeResults(
135 hwdb, probe_results, hwid_properties.board)
136 logging.debug('found system properties:\n%s',
137 YamlWrite(cooked_results.__dict__))
138 # TODO(tammo): Output a new-style log event with device details here.
139 match_errors = []
140 for comp_class, expected_name in hwid_properties.component_map.items():
141 if expected_name == 'ANY':
142 continue
143 if expected_name == cooked_results.matched_components.get(comp_class, None):
144 continue
145 if comp_class in probe_results.missing_components:
146 match_errors.append(' %s component mismatch, expected %s, found nothing'
147 % (comp_class, expected_name))
148 else:
149 probe_value = probe_results.found_components.get(comp_class, None)
150 match_errors.append(' %s component mismatch, expected %s, found %r' %
151 (comp_class, expected_name, probe_value))
152 if match_errors:
153 raise Error('HWID verification FAILED.\n%s' % '\n'.join(match_errors))
154 if hwid_properties.volatile not in cooked_results.matched_volatile_tags:
155 msg = (' HWID specified volatile %s, but found match only for %s' %
156 (hwid_properties.volatile,
157 ', '.join(cooked_results.matched_volatile_tags)))
158 raise Error('HWID verification FAILED.\n%s' % msg)
159 if (hwid_properties.initial_config is not None and
160 hwid_properties.initial_config not in
161 cooked_results.matched_initial_config_tags):
162 msg = (' HWID specified initial_config %s, but only found match for [%s]' %
163 (hwid_properties.initial_config,
164 ', '.join(cooked_results.matched_initial_config_tags)))
165 raise Error('HWID verification FAILED.\n%s' % msg)
166 # TODO(tammo): Verify HWID status is supported (or deprecated for RMA).
167 ro_vpd = ReadRoVpd(main_fw_file)
168 for field in hwid_properties.vpd_ro_field_list:
169 if field not in ro_vpd:
170 raise Error('Missing required VPD field: %s' % field)
171 known_valid_values = vpd_data.KNOWN_VPD_FIELD_DATA.get(field, None)
172 value = ro_vpd[field]
173 if known_valid_values is not None and value not in known_valid_values:
174 raise Error('Invalid VPD entry : field %r, value %r' % (field, value))
175
176
177@Command('verify_keys')
178def VerifyKeys(options):
179 """Verify keys in firmware and SSD match."""
Tammo Spalink461ddce2012-05-10 19:28:55 +0800180 script = FindScript('verify_keys.sh')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800181 kernel_device = GetReleaseKernelPartitionPath()
182 main_fw_file = crosfw.LoadMainFirmware().GetFileName()
183 result = Shell('%s %s %s' % (script, kernel_device, main_fw_file))
184 if not result.success:
185 raise Error, '%r failed, stderr: %r' % (script, result.stderr)
186
187
188@Command('set_fw_bitmap_locale')
189def SetFirmwareBitmapLocale(options):
190 """Use VPD locale value to set firmware bitmap default language."""
191 image_file = crosfw.LoadMainFirmware().GetFileName()
192 locale = ReadRoVpd(image_file).get('initial_locale', None)
193 if locale is None:
194 raise Error, 'Missing initial_locale VPD.'
195 bitmap_locales = []
196 with NamedTemporaryFile() as f:
197 Shell('gbb_utility -g --bmpfv=%s %s' % (f.name, image_file))
198 bmpblk_data = bmpblk.unpack_bmpblock(f.read())
199 bitmap_locales = bmpblk_data.get('locales', bitmap_locales)
200 # Some locale values are just a language code and others are a
201 # hyphen-separated language code and country code pair. We care
202 # only about the language code part.
203 language_code = locale.partition('-')[0]
204 if language_code not in bitmap_locales:
205 raise Error, ('Firmware bitmaps do not contain support for the specified '
206 'initial locale language %r' % language_code)
207 else:
208 locale_index = bitmap_locales.index(language_code)
209 logging.info('Firmware bitmap initial locale set to %d (%s).',
210 locale_index, bitmap_locales[locale_index])
211 Shell('crossystem loc_idx=%d' % locale_index)
212
213
214@Command('verify_system_time')
215def VerifySystemTime(options):
216 """Verify system time is later than release filesystem creation time."""
Tammo Spalink461ddce2012-05-10 19:28:55 +0800217 script = FindScript('verify_system_time.sh')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800218 rootfs_device = GetReleaseRootPartitionPath()
219 result = Shell('%s %s' % (script, rootfs_device))
220 if not result.success:
221 raise Error, '%r failed, stderr: %r' % (script, result.stderr)
222
223
224@Command('verify_rootfs')
225def VerifyRootFs(options):
226 """Verify rootfs on SSD is valid by checking hash."""
Tammo Spalink461ddce2012-05-10 19:28:55 +0800227 script = FindScript('verify_rootfs.sh')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800228 rootfs_device = GetReleaseRootPartitionPath()
229 result = Shell('%s %s' % (script, rootfs_device))
230 if not result.success:
231 raise Error, '%r failed, stderr: %r' % (script, result.stderr)
232
233
234@Command('verify_switch_wp')
235def VerifyWpSwitch(options):
236 """Verify hardware write protection switch is enabled."""
237 if Shell('crossystem wpsw_cur').stdout.strip() != '1':
238 raise Error, 'write protection is disabled'
239
240
241@Command('verify_switch_dev')
242def VerifyDevSwitch(options):
243 """Verify developer switch is disabled."""
Hung-Te Lin8f643e32012-05-23 18:40:47 +0800244 result = Shell('crossystem devsw_cur')
245 if result.success:
246 if result.stdout.strip() != '0':
247 raise Error, 'developer mode is enabled'
248 else:
249 return
250 # Try ChromeOS-EC. This may hang 15 seconds if the EC does not respond.
251 logging.warn('VerifyDevSwitch: Trying ChromeOS-EC...')
252 if not Shell('ectool vboot 0').success:
253 raise Error, 'failed to turn off developer mode.'
254 # TODO(hungte) Verify if the switch is turned off properly, using "ectoo
255 # vboot" and parse the key-value pairs, when the names are determined.
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800256
257
258@Command('write_protect')
259def EnableFwWp(options):
260 """Enable then verify firmware write protection."""
261
262 def WriteProtect(fw_file_path, fw_type, section):
263 """Calculate protection size, then invoke flashrom.
264
265 Our supported chips only allow write protecting half their total
266 size, so we parition the flash chipset space accordingly.
267 """
268 raw_image = open(fw_file_path, 'rb').read()
269 image = crosfw.FirmwareImage(raw_image)
270 if not image.has_section(section):
271 raise Error('could not find %s firmware section %s' % (fw_type, section))
272 section_data = image.get_section_area(section)
273 protectable_size = len(raw_image) / 2
274 ro_a = int(section_data[0] / protectable_size)
275 ro_b = int((section_data[0] + section_data[1] - 1) / protectable_size)
276 if ro_a != ro_b:
277 raise Error("%s firmware section %s has illegal size" %
278 (fw_type, section))
279 ro_offset = ro_a * protectable_size
280 logging.debug('write protecting %s', fw_type)
281 crosfw.Flashrom(fw_type).EnableWriteProtection(ro_offset, protectable_size)
282
283 WriteProtect(crosfw.LoadMainFirmware().GetFileName(), 'main', 'RO_SECTION')
284 ec_fw_file = crosfw.LoadEcFirmware().GetFileName()
285 if ec_fw_file is not None:
286 WriteProtect(ec_fw_file, 'ec', 'EC_RO')
287 else:
288 logging.warning('EC not write protected (seems there is no EC flash).')
289
290
291@Command('clear_gbb_flags')
292def ClearGbbFlags(options):
293 """Zero out the GBB flags, in preparation for transition to release state.
294
295 No GBB flags are set in release/shipping state, but they are useful
296 for factory/development. See "gbb_utility --flags" for details.
297 """
Tammo Spalink461ddce2012-05-10 19:28:55 +0800298 script = FindScript('clear_gbb_flags.sh')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800299 result = Shell(script)
300 if not result.success:
301 raise Error, '%r failed, stderr: %r' % (script, result.stderr)
302
303
304@Command('prepare_wipe',
305 CmdArg('--fast', action='store_true',
306 help='use non-secure but faster wipe method.'))
307def PrepareWipe(options):
308 """Prepare system for transition to release state in next reboot."""
Tammo Spalink461ddce2012-05-10 19:28:55 +0800309 script = FindScript('prepare_wipe.sh')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800310 tag = 'fast' if options.fast else ''
311 rootfs_device = GetReleaseRootPartitionPath()
312 result = Shell('FACTORY_WIPE_TAGS=%s %s %s' % (tag, script, rootfs_device))
313 if not result.success:
314 raise Error, '%r failed, stderr: %r' % (script, result.stderr)
315
316
317@Command('verify',
318 CmdArg('--dev', action='store_true',
319 help='Do not verify switch state (dev mode and fw wp).'),
320 _hwdb_path_cmd_arg)
321def Verify(options):
322 """Verifies if whole factory process is ready for finalization.
323
324 This routine performs all the necessary checks to make sure the
325 device is ready to be finalized, but does not modify state. These
326 checks include dev switch, firmware write protection switch, hwid,
327 system time, keys, and root file system.
328 """
329 if not options.dev:
330 VerifyDevSwitch({})
331 VerifyWpSwitch({})
332 VerifyHwid(options)
333 VerifySystemTime({})
334 VerifyKeys({})
335 VerifyRootFs({})
336
337
338_upload_method_cmd_arg = CmdArg(
339 '--upload_method', metavar='METHOD:PARAM',
340 help=('How to perform the upload. METHOD should be one of '
341 '{ftp, shopfloor, curl, cpfe, custom}.'))
342
343
344@Command('upload_report',
345 _upload_method_cmd_arg)
346def UploadReport(options):
347 """Create and a report containing key device details."""
348 report_upload.Upload(report.Create(options.log), options.upload_method)
349
350
351@Command('finalize',
352 CmdArg('--dev', action='store_true',
353 help='Do not verify or alter write protection or dev mode.'),
354 CmdArg('--fast', action='store_true',
355 help='use non-secure but faster wipe method.'),
356 _hwdb_path_cmd_arg,
357 _upload_method_cmd_arg)
358def Finalize(options):
359 """Verify system readiness and trigger transition into release state.
360
361 This routine first verifies system state (see verify command), then
362 clears all of the testing flags from the GBB, then modifies firmware
363 bitmaps to match locale. Then it enables firmware write protection
364 and sets the necessary boot flags to cause wipe of the factory image
365 on the next boot.
366 """
367 ClearGbbFlags({})
368 Verify(options)
369 SetFirmwareBitmapLocale({})
370 if not options.dev:
371 EnableFwWp({})
372 UploadReport(options)
373 PrepareWipe(options)
374
375
376def Main():
377 """Run sub-command specified by the command line args."""
378 options = ParseCmdline(
379 'Perform Google required factory tests.',
380 CmdArg('-l', '--log', metavar='PATH',
381 help='Write logs to this file.'),
382 verbosity_cmd_arg
383 )
384 SetupLogging(options.verbosity, options.log)
385 logging.debug('gooftool options: %s', repr(options))
386 try:
387 logging.debug('GOOFTOOL command %r', options.command_name)
388 options.command(options)
389 logging.info('GOOFTOOL command %r SUCCESS', options.command_name)
390 except Error, e:
391 logging.exception(e)
392 sys.exit('GOOFTOOL command %r ERROR: %s' % (options.command_name, e))
393 except Exception, e:
394 logging.exception(e)
395 sys.exit('UNCAUGHT RUNTIME EXCEPTION %s' % e)
396
397
398if __name__ == '__main__':
399 Main()