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