blob: e1972af270f6228c24351f1dbd11c24aeed3d015 [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."""
244 if Shell('crossystem devsw_cur').stdout.strip() != '0':
245 raise Error, 'developer mode is enabled'
246
247
248@Command('write_protect')
249def EnableFwWp(options):
250 """Enable then verify firmware write protection."""
251
252 def WriteProtect(fw_file_path, fw_type, section):
253 """Calculate protection size, then invoke flashrom.
254
255 Our supported chips only allow write protecting half their total
256 size, so we parition the flash chipset space accordingly.
257 """
258 raw_image = open(fw_file_path, 'rb').read()
259 image = crosfw.FirmwareImage(raw_image)
260 if not image.has_section(section):
261 raise Error('could not find %s firmware section %s' % (fw_type, section))
262 section_data = image.get_section_area(section)
263 protectable_size = len(raw_image) / 2
264 ro_a = int(section_data[0] / protectable_size)
265 ro_b = int((section_data[0] + section_data[1] - 1) / protectable_size)
266 if ro_a != ro_b:
267 raise Error("%s firmware section %s has illegal size" %
268 (fw_type, section))
269 ro_offset = ro_a * protectable_size
270 logging.debug('write protecting %s', fw_type)
271 crosfw.Flashrom(fw_type).EnableWriteProtection(ro_offset, protectable_size)
272
273 WriteProtect(crosfw.LoadMainFirmware().GetFileName(), 'main', 'RO_SECTION')
274 ec_fw_file = crosfw.LoadEcFirmware().GetFileName()
275 if ec_fw_file is not None:
276 WriteProtect(ec_fw_file, 'ec', 'EC_RO')
277 else:
278 logging.warning('EC not write protected (seems there is no EC flash).')
279
280
281@Command('clear_gbb_flags')
282def ClearGbbFlags(options):
283 """Zero out the GBB flags, in preparation for transition to release state.
284
285 No GBB flags are set in release/shipping state, but they are useful
286 for factory/development. See "gbb_utility --flags" for details.
287 """
Tammo Spalink461ddce2012-05-10 19:28:55 +0800288 script = FindScript('clear_gbb_flags.sh')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800289 result = Shell(script)
290 if not result.success:
291 raise Error, '%r failed, stderr: %r' % (script, result.stderr)
292
293
294@Command('prepare_wipe',
295 CmdArg('--fast', action='store_true',
296 help='use non-secure but faster wipe method.'))
297def PrepareWipe(options):
298 """Prepare system for transition to release state in next reboot."""
Tammo Spalink461ddce2012-05-10 19:28:55 +0800299 script = FindScript('prepare_wipe.sh')
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800300 tag = 'fast' if options.fast else ''
301 rootfs_device = GetReleaseRootPartitionPath()
302 result = Shell('FACTORY_WIPE_TAGS=%s %s %s' % (tag, script, rootfs_device))
303 if not result.success:
304 raise Error, '%r failed, stderr: %r' % (script, result.stderr)
305
306
307@Command('verify',
308 CmdArg('--dev', action='store_true',
309 help='Do not verify switch state (dev mode and fw wp).'),
310 _hwdb_path_cmd_arg)
311def Verify(options):
312 """Verifies if whole factory process is ready for finalization.
313
314 This routine performs all the necessary checks to make sure the
315 device is ready to be finalized, but does not modify state. These
316 checks include dev switch, firmware write protection switch, hwid,
317 system time, keys, and root file system.
318 """
319 if not options.dev:
320 VerifyDevSwitch({})
321 VerifyWpSwitch({})
322 VerifyHwid(options)
323 VerifySystemTime({})
324 VerifyKeys({})
325 VerifyRootFs({})
326
327
328_upload_method_cmd_arg = CmdArg(
329 '--upload_method', metavar='METHOD:PARAM',
330 help=('How to perform the upload. METHOD should be one of '
331 '{ftp, shopfloor, curl, cpfe, custom}.'))
332
333
334@Command('upload_report',
335 _upload_method_cmd_arg)
336def UploadReport(options):
337 """Create and a report containing key device details."""
338 report_upload.Upload(report.Create(options.log), options.upload_method)
339
340
341@Command('finalize',
342 CmdArg('--dev', action='store_true',
343 help='Do not verify or alter write protection or dev mode.'),
344 CmdArg('--fast', action='store_true',
345 help='use non-secure but faster wipe method.'),
346 _hwdb_path_cmd_arg,
347 _upload_method_cmd_arg)
348def Finalize(options):
349 """Verify system readiness and trigger transition into release state.
350
351 This routine first verifies system state (see verify command), then
352 clears all of the testing flags from the GBB, then modifies firmware
353 bitmaps to match locale. Then it enables firmware write protection
354 and sets the necessary boot flags to cause wipe of the factory image
355 on the next boot.
356 """
357 ClearGbbFlags({})
358 Verify(options)
359 SetFirmwareBitmapLocale({})
360 if not options.dev:
361 EnableFwWp({})
362 UploadReport(options)
363 PrepareWipe(options)
364
365
366def Main():
367 """Run sub-command specified by the command line args."""
368 options = ParseCmdline(
369 'Perform Google required factory tests.',
370 CmdArg('-l', '--log', metavar='PATH',
371 help='Write logs to this file.'),
372 verbosity_cmd_arg
373 )
374 SetupLogging(options.verbosity, options.log)
375 logging.debug('gooftool options: %s', repr(options))
376 try:
377 logging.debug('GOOFTOOL command %r', options.command_name)
378 options.command(options)
379 logging.info('GOOFTOOL command %r SUCCESS', options.command_name)
380 except Error, e:
381 logging.exception(e)
382 sys.exit('GOOFTOOL command %r ERROR: %s' % (options.command_name, e))
383 except Exception, e:
384 logging.exception(e)
385 sys.exit('UNCAUGHT RUNTIME EXCEPTION %s' % e)
386
387
388if __name__ == '__main__':
389 Main()