blob: e86e352eeb84ec20033008c7ceea694d6e817867 [file] [log] [blame]
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001# Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import ast
6import ctypes
7import logging
8import os
Tom Wai-Hong Tam9a10b9b2015-09-28 16:29:28 +08009import pprint
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -070010import re
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -070011import time
12import uuid
13
14from autotest_lib.client.bin import utils
15from autotest_lib.client.common_lib import error
J. Richard Barnettecab6be32014-07-17 13:07:39 -070016from autotest_lib.server import test
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -070017from autotest_lib.server.cros import vboot_constants as vboot
18from autotest_lib.server.cros.faft.config.config import Config as FAFTConfig
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -070019from autotest_lib.server.cros.faft.rpc_proxy import RPCProxy
Tom Wai-Hong Tamed4d67b2015-05-20 05:20:00 +080020from autotest_lib.server.cros.faft.utils import mode_switcher
J. Richard Barnettea57ff842014-06-05 10:00:31 -070021from autotest_lib.server.cros.faft.utils.faft_checkers import FAFTCheckers
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -070022from autotest_lib.server.cros.servo import chrome_ec
Scottfe06ed82015-11-05 17:15:01 -080023from autotest_lib.server.cros.servo import chrome_usbpd
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -070024
Tom Wai-Hong Tam19bfb6e2015-08-20 06:05:36 +080025ConnectionError = mode_switcher.ConnectionError
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -070026
27
J. Richard Barnettecab6be32014-07-17 13:07:39 -070028class FAFTBase(test.test):
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -070029 """The base class of FAFT classes.
30
31 It launches the FAFTClient on DUT, such that the test can access its
32 firmware functions and interfaces. It also provides some methods to
33 handle the reboot mechanism, in order to ensure FAFTClient is still
34 connected after reboot.
35 """
36 def initialize(self, host):
37 """Create a FAFTClient object and install the dependency."""
J. Richard Barnettecab6be32014-07-17 13:07:39 -070038 self.servo = host.servo
39 self.servo.initialize_dut()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -070040 self._client = host
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -070041 self.faft_client = RPCProxy(host)
Duncan Laurie10eb6182014-10-07 15:39:05 -070042 self.lockfile = '/var/tmp/faft/lock'
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -070043
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -070044
45class FirmwareTest(FAFTBase):
46 """
Yusuf Mohsinallyab1b5fc2014-05-08 12:46:10 -070047 Base class that sets up helper objects/functions for firmware tests.
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -070048
Yusuf Mohsinallyab1b5fc2014-05-08 12:46:10 -070049 TODO: add documentaion as the FAFT rework progresses.
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -070050 """
51 version = 1
52
53 # Mapping of partition number of kernel and rootfs.
54 KERNEL_MAP = {'a':'2', 'b':'4', '2':'2', '4':'4', '3':'2', '5':'4'}
55 ROOTFS_MAP = {'a':'3', 'b':'5', '2':'3', '4':'5', '3':'3', '5':'5'}
56 OTHER_KERNEL_MAP = {'a':'4', 'b':'2', '2':'4', '4':'2', '3':'4', '5':'2'}
57 OTHER_ROOTFS_MAP = {'a':'5', 'b':'3', '2':'5', '4':'3', '3':'5', '5':'3'}
58
59 CHROMEOS_MAGIC = "CHROMEOS"
60 CORRUPTED_MAGIC = "CORRUPTD"
61
Shelley Chen530490a2015-10-21 10:43:06 -070062 # Delay for waiting client to return before EC suspend
63 EC_SUSPEND_DELAY = 5
64
65 # Delay between EC suspend and wake
66 WAKE_DELAY = 10
67
68 # Delay between closing and opening lid
69 LID_DELAY = 1
70
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -070071 _SERVOD_LOG = '/var/log/servod.log'
72
73 _ROOTFS_PARTITION_NUMBER = 3
74
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -070075 _backup_firmware_sha = ()
76 _backup_kernel_sha = dict()
77 _backup_cgpt_attr = dict()
78 _backup_gbb_flags = None
79 _backup_dev_mode = None
80
81 # Class level variable, keep track the states of one time setup.
82 # This variable is preserved across tests which inherit this class.
83 _global_setup_done = {
84 'gbb_flags': False,
85 'reimage': False,
86 'usb_check': False,
87 }
88
89 @classmethod
90 def check_setup_done(cls, label):
91 """Check if the given setup is done.
92
93 @param label: The label of the setup.
94 """
95 return cls._global_setup_done[label]
96
97 @classmethod
98 def mark_setup_done(cls, label):
99 """Mark the given setup done.
100
101 @param label: The label of the setup.
102 """
103 cls._global_setup_done[label] = True
104
105 @classmethod
106 def unmark_setup_done(cls, label):
107 """Mark the given setup not done.
108
109 @param label: The label of the setup.
110 """
111 cls._global_setup_done[label] = False
112
113 def initialize(self, host, cmdline_args, ec_wp=None):
114 super(FirmwareTest, self).initialize(host)
115 self.run_id = str(uuid.uuid4())
116 logging.info('FirmwareTest initialize begin (id=%s)', self.run_id)
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700117 # Parse arguments from command line
118 args = {}
119 self.power_control = host.POWER_CONTROL_RPM
120 for arg in cmdline_args:
121 match = re.search("^(\w+)=(.+)", arg)
122 if match:
123 args[match.group(1)] = match.group(2)
124 if 'power_control' in args:
125 self.power_control = args['power_control']
126 if self.power_control not in host.POWER_CONTROL_VALID_ARGS:
127 raise error.TestError('Valid values for --args=power_control '
128 'are %s. But you entered wrong argument '
129 'as "%s".'
130 % (host.POWER_CONTROL_VALID_ARGS,
131 self.power_control))
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700132
133 self.faft_config = FAFTConfig(
134 self.faft_client.system.get_platform_name())
Tom Wai-Hong Tam0cc9a4f2015-05-02 05:12:39 +0800135 self.checkers = FAFTCheckers(self)
Tom Wai-Hong Tamed4d67b2015-05-20 05:20:00 +0800136 self.switcher = mode_switcher.create_mode_switcher(self)
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700137
138 if self.faft_config.chrome_ec:
139 self.ec = chrome_ec.ChromeEC(self.servo)
Scottfe06ed82015-11-05 17:15:01 -0800140 # Check for presence of a USBPD console
141 if self.faft_config.chrome_usbpd:
142 self.usbpd = chrome_usbpd.ChromeUSBPD(self.servo)
143 elif self.faft_config.chrome_ec:
144 # If no separate USBPD console, then PD exists on EC console
145 self.usbpd = self.ec
146 # Get plankton console
Scott07a848f2016-01-12 15:04:52 -0800147 self.plankton = host.plankton
148 self.plankton_host = host._plankton_host
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700149
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700150 self._setup_uart_capture()
151 self._setup_servo_log()
152 self._record_system_info()
Daisuke Nojiri682a6d62014-11-21 09:59:32 -0800153 self.fw_vboot2 = self.faft_client.system.get_fw_vboot2()
154 logging.info('vboot version: %d', 2 if self.fw_vboot2 else 1)
155 if self.fw_vboot2:
156 self.faft_client.system.set_fw_try_next('A')
157 if self.faft_client.system.get_crossystem_value('mainfw_act') == 'B':
158 logging.info('mainfw_act is B. rebooting to set it A')
Tom Wai-Hong Tam47776242015-05-07 02:45:32 +0800159 self.switcher.mode_aware_reboot()
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700160 self._setup_gbb_flags()
161 self._stop_service('update-engine')
Duncan Laurie10eb6182014-10-07 15:39:05 -0700162 self._create_faft_lockfile()
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700163 self._setup_ec_write_protect(ec_wp)
Yusuf Mohsinally1b7a48b2014-05-12 19:25:35 -0700164 # See chromium:239034 regarding needing this sync.
Yusuf Mohsinally1bacc962014-08-14 11:37:32 -0700165 self.blocking_sync()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700166 logging.info('FirmwareTest initialize done (id=%s)', self.run_id)
167
168 def cleanup(self):
169 """Autotest cleanup function."""
170 # Unset state checker in case it's set by subclass
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700171 logging.info('FirmwareTest cleaning up (id=%s)', self.run_id)
172 try:
173 self.faft_client.system.is_available()
174 except:
175 # Remote is not responding. Revive DUT so that subsequent tests
176 # don't fail.
177 self._restore_routine_from_timeout()
Tom Wai-Hong Tam0cc9a4f2015-05-02 05:12:39 +0800178 self.switcher.restore_mode()
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700179 self._restore_ec_write_protect()
180 self._restore_gbb_flags()
181 self._start_service('update-engine')
Duncan Laurie10eb6182014-10-07 15:39:05 -0700182 self._remove_faft_lockfile()
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700183 self._record_servo_log()
184 self._record_faft_client_log()
185 self._cleanup_uart_capture()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700186 super(FirmwareTest, self).cleanup()
187 logging.info('FirmwareTest cleanup done (id=%s)', self.run_id)
188
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700189 def _record_system_info(self):
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700190 """Record some critical system info to the attr keyval.
191
Christopher Wiley004a8cd2015-05-19 11:49:13 -0700192 This info is used by generate_test_report later.
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700193 """
Tom Wai-Hong Tam9a10b9b2015-09-28 16:29:28 +0800194 system_info = {
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700195 'hwid': self.faft_client.system.get_crossystem_value('hwid'),
Tom Wai-Hong Tam32075c12016-01-20 07:09:47 +0800196 'ec_version': self.faft_client.ec.get_version(),
197 'ro_fwid': self.faft_client.system.get_crossystem_value('ro_fwid'),
198 'rw_fwid': self.faft_client.system.get_crossystem_value('fwid'),
Tom Wai-Hong Tam9a10b9b2015-09-28 16:29:28 +0800199 'servod_version': self._client._servo_host.run(
200 'servod --version').stdout.strip(),
201 }
202 logging.info('System info:\n' + pprint.pformat(system_info))
203 self.write_attr_keyval(system_info)
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700204
205 def invalidate_firmware_setup(self):
206 """Invalidate all firmware related setup state.
207
208 This method is called when the firmware is re-flashed. It resets all
209 firmware related setup states so that the next test setup properly
210 again.
211 """
212 self.unmark_setup_done('gbb_flags')
213
214 def _retrieve_recovery_reason_from_trap(self):
215 """Try to retrieve the recovery reason from a trapped recovery screen.
216
217 @return: The recovery_reason, 0 if any error.
218 """
219 recovery_reason = 0
220 logging.info('Try to retrieve recovery reason...')
221 if self.servo.get_usbkey_direction() == 'dut':
Tom Wai-Hong Tam04302882015-05-14 06:08:34 +0800222 self.switcher.bypass_rec_mode()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700223 else:
224 self.servo.switch_usbkey('dut')
225
226 try:
Tom Wai-Hong Tam19bfb6e2015-08-20 06:05:36 +0800227 self.switcher.wait_for_client()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700228 lines = self.faft_client.system.run_shell_command_get_output(
229 'crossystem recovery_reason')
230 recovery_reason = int(lines[0])
231 logging.info('Got the recovery reason %d.', recovery_reason)
232 except ConnectionError:
233 logging.error('Failed to get the recovery reason due to connection '
234 'error.')
235 return recovery_reason
236
237 def _reset_client(self):
238 """Reset client to a workable state.
239
240 This method is called when the client is not responsive. It may be
241 caused by the following cases:
242 - halt on a firmware screen without timeout, e.g. REC_INSERT screen;
243 - corrupted firmware;
244 - corrutped OS image.
245 """
246 # DUT may halt on a firmware screen. Try cold reboot.
247 logging.info('Try cold reboot...')
Tom Wai-Hong Tama704f182015-05-06 06:12:55 +0800248 self.switcher.mode_aware_reboot(reboot_type='cold',
249 sync_before_boot=False,
250 wait_for_dut_up=False)
Tom Wai-Hong Tam19bfb6e2015-08-20 06:05:36 +0800251 self.switcher.wait_for_client_offline()
Tom Wai-Hong Tam04302882015-05-14 06:08:34 +0800252 self.switcher.bypass_dev_mode()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700253 try:
Tom Wai-Hong Tam19bfb6e2015-08-20 06:05:36 +0800254 self.switcher.wait_for_client()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700255 return
256 except ConnectionError:
257 logging.warn('Cold reboot doesn\'t help, still connection error.')
258
259 # DUT may be broken by a corrupted firmware. Restore firmware.
260 # We assume the recovery boot still works fine. Since the recovery
261 # code is in RO region and all FAFT tests don't change the RO region
262 # except GBB.
263 if self.is_firmware_saved():
264 self._ensure_client_in_recovery()
265 logging.info('Try restore the original firmware...')
266 if self.is_firmware_changed():
267 try:
268 self.restore_firmware()
269 return
270 except ConnectionError:
271 logging.warn('Restoring firmware doesn\'t help, still '
272 'connection error.')
273
274 # Perhaps it's kernel that's broken. Let's try restoring it.
275 if self.is_kernel_saved():
276 self._ensure_client_in_recovery()
277 logging.info('Try restore the original kernel...')
278 if self.is_kernel_changed():
279 try:
280 self.restore_kernel()
281 return
282 except ConnectionError:
283 logging.warn('Restoring kernel doesn\'t help, still '
284 'connection error.')
285
286 # DUT may be broken by a corrupted OS image. Restore OS image.
287 self._ensure_client_in_recovery()
288 logging.info('Try restore the OS image...')
289 self.faft_client.system.run_shell_command('chromeos-install --yes')
Tom Wai-Hong Tama704f182015-05-06 06:12:55 +0800290 self.switcher.mode_aware_reboot(wait_for_dut_up=False)
Tom Wai-Hong Tam19bfb6e2015-08-20 06:05:36 +0800291 self.switcher.wait_for_client_offline()
Tom Wai-Hong Tam04302882015-05-14 06:08:34 +0800292 self.switcher.bypass_dev_mode()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700293 try:
Tom Wai-Hong Tam19bfb6e2015-08-20 06:05:36 +0800294 self.switcher.wait_for_client()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700295 logging.info('Successfully restore OS image.')
296 return
297 except ConnectionError:
298 logging.warn('Restoring OS image doesn\'t help, still connection '
299 'error.')
300
301 def _ensure_client_in_recovery(self):
302 """Ensure client in recovery boot; reboot into it if necessary.
303
304 @raise TestError: if failed to boot the USB image.
305 """
306 logging.info('Try boot into USB image...')
Tom Wai-Hong Tamd7a0d052015-05-14 02:18:23 +0800307 self.switcher.reboot_to_mode(to_mode='rec', sync_before_boot=False,
308 wait_for_dut_up=False)
Tom Wai-Hong Tamf2de4de2015-05-02 02:48:08 +0800309 self.servo.switch_usbkey('host')
Tom Wai-Hong Tam04302882015-05-14 06:08:34 +0800310 self.switcher.bypass_rec_mode()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700311 try:
Tom Wai-Hong Tam19bfb6e2015-08-20 06:05:36 +0800312 self.switcher.wait_for_client()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700313 except ConnectionError:
314 raise error.TestError('Failed to boot the USB image.')
315
Yusuf Mohsinally64ee3a72014-06-26 10:24:27 -0700316 def _restore_routine_from_timeout(self):
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700317 """A routine to try to restore the system from a timeout error.
318
319 This method is called when FAFT failed to connect DUT after reboot.
320
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700321 @raise TestFail: This exception is already raised, with a decription
322 why it failed.
323 """
324 # DUT is disconnected. Capture the UART output for debug.
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700325 self._record_uart_capture()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700326
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700327 # TODO(waihong@chromium.org): Implement replugging the Ethernet to
328 # identify if it is a network flaky.
329
330 recovery_reason = self._retrieve_recovery_reason_from_trap()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700331
332 # Reset client to a workable state.
333 self._reset_client()
334
335 # Raise the proper TestFail exception.
Yusuf Mohsinally64ee3a72014-06-26 10:24:27 -0700336 if recovery_reason:
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700337 raise error.TestFail('Trapped in the recovery screen (reason: %d) '
338 'and timed out' % recovery_reason)
339 else:
340 raise error.TestFail('Timed out waiting for DUT reboot')
341
Julius Werner18c4e162015-07-07 13:02:08 -0700342 def assert_test_image_in_usb_disk(self, usb_dev=None):
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700343 """Assert an USB disk plugged-in on servo and a test image inside.
344
345 @param usb_dev: A string of USB stick path on the host, like '/dev/sdc'.
346 If None, it is detected automatically.
Julius Werner18c4e162015-07-07 13:02:08 -0700347 @raise TestError: if USB disk not detected or not a test image.
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700348 """
349 if self.check_setup_done('usb_check'):
350 return
351 if usb_dev:
352 assert self.servo.get_usbkey_direction() == 'host'
353 else:
354 self.servo.switch_usbkey('host')
355 usb_dev = self.servo.probe_host_usb_dev()
356 if not usb_dev:
357 raise error.TestError(
358 'An USB disk should be plugged in the servo board.')
359
360 rootfs = '%s%s' % (usb_dev, self._ROOTFS_PARTITION_NUMBER)
361 logging.info('usb dev is %s', usb_dev)
362 tmpd = self.servo.system_output('mktemp -d -t usbcheck.XXXX')
363 self.servo.system('mount -o ro %s %s' % (rootfs, tmpd))
364
Julius Wernerdc535df2015-02-26 16:42:38 -0800365 try:
Julius Werner18c4e162015-07-07 13:02:08 -0700366 usb_lsb = self.servo.system_output('cat %s' %
367 os.path.join(tmpd, 'etc/lsb-release'))
368 logging.debug('Dumping lsb-release on USB stick:\n%s', usb_lsb)
369 dut_lsb = '\n'.join(self.faft_client.system.
370 run_shell_command_get_output('cat /etc/lsb-release'))
371 logging.debug('Dumping lsb-release on DUT:\n%s', dut_lsb)
Julius Wernere6adca42015-08-13 11:10:59 -0700372 if not re.search(r'RELEASE_TRACK=.*test', usb_lsb):
Julius Werner18c4e162015-07-07 13:02:08 -0700373 raise error.TestError('USB stick in servo is no test image')
374 usb_board = re.search(r'BOARD=(.*)', usb_lsb).group(1)
375 dut_board = re.search(r'BOARD=(.*)', dut_lsb).group(1)
376 if usb_board != dut_board:
377 raise error.TestError('USB stick in servo contains a %s '
378 'image, but DUT is a %s' % (usb_board, dut_board))
Julius Wernerdc535df2015-02-26 16:42:38 -0800379 finally:
380 for cmd in ('umount %s' % rootfs, 'sync', 'rm -rf %s' % tmpd):
381 self.servo.system(cmd)
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700382
383 self.mark_setup_done('usb_check')
384
Julius Werner18c4e162015-07-07 13:02:08 -0700385 def setup_usbkey(self, usbkey, host=None):
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700386 """Setup the USB disk for the test.
387
388 It checks the setup of USB disk and a valid ChromeOS test image inside.
389 It also muxes the USB disk to either the host or DUT by request.
390
391 @param usbkey: True if the USB disk is required for the test, False if
392 not required.
393 @param host: Optional, True to mux the USB disk to host, False to mux it
394 to DUT, default to do nothing.
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700395 """
396 if usbkey:
Julius Werner18c4e162015-07-07 13:02:08 -0700397 self.assert_test_image_in_usb_disk()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700398 elif host is None:
399 # USB disk is not required for the test. Better to mux it to host.
400 host = True
401
402 if host is True:
403 self.servo.switch_usbkey('host')
404 elif host is False:
405 self.servo.switch_usbkey('dut')
406
407 def get_usbdisk_path_on_dut(self):
408 """Get the path of the USB disk device plugged-in the servo on DUT.
409
410 Returns:
411 A string representing USB disk path, like '/dev/sdb', or None if
412 no USB disk is found.
413 """
414 cmd = 'ls -d /dev/s*[a-z]'
415 original_value = self.servo.get_usbkey_direction()
416
417 # Make the dut unable to see the USB disk.
418 self.servo.switch_usbkey('off')
419 no_usb_set = set(
420 self.faft_client.system.run_shell_command_get_output(cmd))
421
422 # Make the dut able to see the USB disk.
423 self.servo.switch_usbkey('dut')
Tom Wai-Hong Tamb0314a02015-05-20 05:25:22 +0800424 time.sleep(self.faft_config.usb_plug)
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700425 has_usb_set = set(
426 self.faft_client.system.run_shell_command_get_output(cmd))
427
428 # Back to its original value.
429 if original_value != self.servo.get_usbkey_direction():
430 self.servo.switch_usbkey(original_value)
431
432 diff_set = has_usb_set - no_usb_set
433 if len(diff_set) == 1:
434 return diff_set.pop()
435 else:
436 return None
437
Duncan Laurie10eb6182014-10-07 15:39:05 -0700438 def _create_faft_lockfile(self):
439 """Creates the FAFT lockfile."""
440 logging.info('Creating FAFT lockfile...')
441 command = 'touch %s' % (self.lockfile)
442 self.faft_client.system.run_shell_command(command)
443
444 def _remove_faft_lockfile(self):
445 """Removes the FAFT lockfile."""
446 logging.info('Removing FAFT lockfile...')
447 command = 'rm -f %s' % (self.lockfile)
448 self.faft_client.system.run_shell_command(command)
449
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700450 def _stop_service(self, service):
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700451 """Stops a upstart service on the client.
452
453 @param service: The name of the upstart service.
454 """
455 logging.info('Stopping %s...', service)
456 command = 'status %s | grep stop || stop %s' % (service, service)
457 self.faft_client.system.run_shell_command(command)
458
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700459 def _start_service(self, service):
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700460 """Starts a upstart service on the client.
461
462 @param service: The name of the upstart service.
463 """
464 logging.info('Starting %s...', service)
465 command = 'status %s | grep start || start %s' % (service, service)
466 self.faft_client.system.run_shell_command(command)
467
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700468 def clear_set_gbb_flags(self, clear_mask, set_mask):
469 """Clear and set the GBB flags in the current flashrom.
470
471 @param clear_mask: A mask of flags to be cleared.
472 @param set_mask: A mask of flags to be set.
473 """
474 gbb_flags = self.faft_client.bios.get_gbb_flags()
475 new_flags = gbb_flags & ctypes.c_uint32(~clear_mask).value | set_mask
Tom Wai-Hong Tamfc0c7702015-09-12 03:43:53 +0800476 if new_flags != gbb_flags:
477 self._backup_gbb_flags = gbb_flags
478 logging.info('Changing GBB flags from 0x%x to 0x%x.',
479 gbb_flags, new_flags)
480 self.faft_client.bios.set_gbb_flags(new_flags)
481 # If changing FORCE_DEV_SWITCH_ON flag, reboot to get a clear state
482 if ((gbb_flags ^ new_flags) & vboot.GBB_FLAG_FORCE_DEV_SWITCH_ON):
483 self.switcher.mode_aware_reboot()
484 else:
485 logging.info('Current GBB flags look good for test: 0x%x.',
486 gbb_flags)
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700487
488 def check_ec_capability(self, required_cap=None, suppress_warning=False):
489 """Check if current platform has required EC capabilities.
490
491 @param required_cap: A list containing required EC capabilities. Pass in
492 None to only check for presence of Chrome EC.
493 @param suppress_warning: True to suppress any warning messages.
494 @return: True if requirements are met. Otherwise, False.
495 """
496 if not self.faft_config.chrome_ec:
497 if not suppress_warning:
498 logging.warn('Requires Chrome EC to run this test.')
499 return False
500
501 if not required_cap:
502 return True
503
504 for cap in required_cap:
505 if cap not in self.faft_config.ec_capability:
506 if not suppress_warning:
507 logging.warn('Requires EC capability "%s" to run this '
508 'test.', cap)
509 return False
510
511 return True
512
513 def check_root_part_on_non_recovery(self, part):
514 """Check the partition number of root device and on normal/dev boot.
515
516 @param part: A string of partition number, e.g.'3'.
517 @return: True if the root device matched and on normal/dev boot;
518 otherwise, False.
519 """
520 return self.checkers.root_part_checker(part) and \
521 self.checkers.crossystem_checker({
522 'mainfw_type': ('normal', 'developer'),
523 })
524
525 def _join_part(self, dev, part):
526 """Return a concatenated string of device and partition number.
527
528 @param dev: A string of device, e.g.'/dev/sda'.
529 @param part: A string of partition number, e.g.'3'.
530 @return: A concatenated string of device and partition number,
531 e.g.'/dev/sda3'.
532
533 >>> seq = FirmwareTest()
534 >>> seq._join_part('/dev/sda', '3')
535 '/dev/sda3'
536 >>> seq._join_part('/dev/mmcblk0', '2')
537 '/dev/mmcblk0p2'
538 """
539 if 'mmcblk' in dev:
540 return dev + 'p' + part
541 else:
542 return dev + part
543
544 def copy_kernel_and_rootfs(self, from_part, to_part):
545 """Copy kernel and rootfs from from_part to to_part.
546
547 @param from_part: A string of partition number to be copied from.
548 @param to_part: A string of partition number to be copied to.
549 """
550 root_dev = self.faft_client.system.get_root_dev()
551 logging.info('Copying kernel from %s to %s. Please wait...',
552 from_part, to_part)
553 self.faft_client.system.run_shell_command('dd if=%s of=%s bs=4M' %
554 (self._join_part(root_dev, self.KERNEL_MAP[from_part]),
555 self._join_part(root_dev, self.KERNEL_MAP[to_part])))
556 logging.info('Copying rootfs from %s to %s. Please wait...',
557 from_part, to_part)
558 self.faft_client.system.run_shell_command('dd if=%s of=%s bs=4M' %
559 (self._join_part(root_dev, self.ROOTFS_MAP[from_part]),
560 self._join_part(root_dev, self.ROOTFS_MAP[to_part])))
561
562 def ensure_kernel_boot(self, part):
563 """Ensure the request kernel boot.
564
565 If not, it duplicates the current kernel to the requested kernel
566 and sets the requested higher priority to ensure it boot.
567
568 @param part: A string of kernel partition number or 'a'/'b'.
569 """
570 if not self.checkers.root_part_checker(part):
571 if self.faft_client.kernel.diff_a_b():
572 self.copy_kernel_and_rootfs(
573 from_part=self.OTHER_KERNEL_MAP[part],
574 to_part=part)
Yusuf Mohsinallyab1b5fc2014-05-08 12:46:10 -0700575 self.reset_and_prioritize_kernel(part)
Tom Wai-Hong Tamb1a86012015-09-25 14:27:20 +0800576 self.switcher.mode_aware_reboot()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700577
578 def set_hardware_write_protect(self, enable):
579 """Set hardware write protect pin.
580
581 @param enable: True if asserting write protect pin. Otherwise, False.
582 """
Wai-Hong Tameef4edb2016-10-12 15:02:03 -0700583 try:
584 self.servo.set('fw_wp_state', 'force_on' if enable else 'force_off')
585 except:
586 # TODO(waihong): Remove this fallback when all servos have the
587 # above new fw_wp_state control.
588 self.servo.set('fw_wp_vref', self.faft_config.wp_voltage)
589 self.servo.set('fw_wp_en', 'on')
590 self.servo.set('fw_wp', 'on' if enable else 'off')
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700591
592 def set_ec_write_protect_and_reboot(self, enable):
593 """Set EC write protect status and reboot to take effect.
594
595 The write protect state is only activated if both hardware write
596 protect pin is asserted and software write protect flag is set.
597 This method asserts/deasserts hardware write protect pin first, and
598 set corresponding EC software write protect flag.
599
600 If the device uses non-Chrome EC, set the software write protect via
601 flashrom.
602
603 If the device uses Chrome EC, a reboot is required for write protect
604 to take effect. Since the software write protect flag cannot be unset
605 if hardware write protect pin is asserted, we need to deasserted the
606 pin first if we are deactivating write protect. Similarly, a reboot
607 is required before we can modify the software flag.
608
609 @param enable: True if activating EC write protect. Otherwise, False.
610 """
611 self.set_hardware_write_protect(enable)
612 if self.faft_config.chrome_ec:
613 self.set_chrome_ec_write_protect_and_reboot(enable)
614 else:
615 self.faft_client.ec.set_write_protect(enable)
Tom Wai-Hong Tama704f182015-05-06 06:12:55 +0800616 self.switcher.mode_aware_reboot()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700617
618 def set_chrome_ec_write_protect_and_reboot(self, enable):
619 """Set Chrome EC write protect status and reboot to take effect.
620
621 @param enable: True if activating EC write protect. Otherwise, False.
622 """
623 if enable:
624 # Set write protect flag and reboot to take effect.
625 self.ec.set_flash_write_protect(enable)
626 self.sync_and_ec_reboot()
627 else:
628 # Reboot after deasserting hardware write protect pin to deactivate
629 # write protect. And then remove software write protect flag.
630 self.sync_and_ec_reboot()
631 self.ec.set_flash_write_protect(enable)
632
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700633 def _setup_ec_write_protect(self, ec_wp):
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700634 """Setup for EC write-protection.
635
636 It makes sure the EC in the requested write-protection state. If not, it
637 flips the state. Flipping the write-protection requires DUT reboot.
638
639 @param ec_wp: True to request EC write-protected; False to request EC
640 not write-protected; None to do nothing.
641 """
642 if ec_wp is None:
643 self._old_ec_wp = None
644 return
645 self._old_ec_wp = self.checkers.crossystem_checker({'wpsw_boot': '1'})
646 if ec_wp != self._old_ec_wp:
647 logging.info('The test required EC is %swrite-protected. Reboot '
648 'and flip the state.', '' if ec_wp else 'not ')
Tom Wai-Hong Tam3e92b8e2015-05-07 06:29:57 +0800649 self.switcher.mode_aware_reboot(
650 'custom',
651 lambda:self.set_ec_write_protect_and_reboot(ec_wp))
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700652
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700653 def _restore_ec_write_protect(self):
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700654 """Restore the original EC write-protection."""
655 if (not hasattr(self, '_old_ec_wp')) or (self._old_ec_wp is None):
656 return
657 if not self.checkers.crossystem_checker(
658 {'wpsw_boot': '1' if self._old_ec_wp else '0'}):
Yusuf Mohsinallyab1b5fc2014-05-08 12:46:10 -0700659 logging.info('Restore original EC write protection and reboot.')
Tom Wai-Hong Tam3e92b8e2015-05-07 06:29:57 +0800660 self.switcher.mode_aware_reboot(
661 'custom',
662 lambda:self.set_ec_write_protect_and_reboot(
663 self._old_ec_wp))
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700664
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700665 def _setup_uart_capture(self):
Duncan Laurieaf61c1f2014-10-07 15:35:18 -0700666 """Setup the CPU/EC/PD UART capture."""
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700667 self.cpu_uart_file = os.path.join(self.resultsdir, 'cpu_uart.txt')
668 self.servo.set('cpu_uart_capture', 'on')
669 self.ec_uart_file = None
Duncan Laurieaf61c1f2014-10-07 15:35:18 -0700670 self.usbpd_uart_file = None
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700671 if self.faft_config.chrome_ec:
672 try:
673 self.servo.set('ec_uart_capture', 'on')
674 self.ec_uart_file = os.path.join(self.resultsdir, 'ec_uart.txt')
675 except error.TestFail as e:
676 if 'No control named' in str(e):
677 logging.warn('The servod is too old that ec_uart_capture '
678 'not supported.')
Duncan Laurieaf61c1f2014-10-07 15:35:18 -0700679 # Log separate PD console if supported
680 if self.check_ec_capability(['usbpd_uart'], suppress_warning=True):
681 try:
682 self.servo.set('usbpd_uart_capture', 'on')
683 self.usbpd_uart_file = os.path.join(self.resultsdir,
684 'usbpd_uart.txt')
685 except error.TestFail as e:
686 if 'No control named' in str(e):
687 logging.warn('The servod is too old that '
688 'usbpd_uart_capture is not supported.')
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700689 else:
690 logging.info('Not a Google EC, cannot capture ec console output.')
691
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700692 def _record_uart_capture(self):
Duncan Laurieaf61c1f2014-10-07 15:35:18 -0700693 """Record the CPU/EC/PD UART output stream to files."""
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700694 if self.cpu_uart_file:
695 with open(self.cpu_uart_file, 'a') as f:
696 f.write(ast.literal_eval(self.servo.get('cpu_uart_stream')))
697 if self.ec_uart_file and self.faft_config.chrome_ec:
698 with open(self.ec_uart_file, 'a') as f:
699 f.write(ast.literal_eval(self.servo.get('ec_uart_stream')))
Duncan Laurieaf61c1f2014-10-07 15:35:18 -0700700 if (self.usbpd_uart_file and self.faft_config.chrome_ec and
701 self.check_ec_capability(['usbpd_uart'], suppress_warning=True)):
702 with open(self.usbpd_uart_file, 'a') as f:
703 f.write(ast.literal_eval(self.servo.get('usbpd_uart_stream')))
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700704
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700705 def _cleanup_uart_capture(self):
Duncan Laurieaf61c1f2014-10-07 15:35:18 -0700706 """Cleanup the CPU/EC/PD UART capture."""
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700707 # Flush the remaining UART output.
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700708 self._record_uart_capture()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700709 self.servo.set('cpu_uart_capture', 'off')
710 if self.ec_uart_file and self.faft_config.chrome_ec:
711 self.servo.set('ec_uart_capture', 'off')
Duncan Laurieaf61c1f2014-10-07 15:35:18 -0700712 if (self.usbpd_uart_file and self.faft_config.chrome_ec and
713 self.check_ec_capability(['usbpd_uart'], suppress_warning=True)):
714 self.servo.set('usbpd_uart_capture', 'off')
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700715
Shelley Chenc0781c42015-10-05 10:52:42 -0700716 def _get_power_state(self, power_state):
717 """
718 Return the current power state of the AP
719 """
720 return self.ec.send_command_get_output("powerinfo", [power_state])
721
722 def wait_power_state(self, power_state, retries):
723 """
724 Wait for certain power state.
725
726 @param power_state: power state you are expecting
727 @param retries: retries. This is necessary if AP is powering down
728 and transitioning through different states.
729 """
730 logging.info('Checking power state "%s" maximum %d times.',
731 power_state, retries)
732 while retries > 0:
Aseda Aboagye795cf352016-12-12 16:35:17 -0800733 logging.info("try count: %d", retries)
Shelley Chenc0781c42015-10-05 10:52:42 -0700734 try:
735 retries = retries - 1
736 ret = self._get_power_state(power_state)
737 return True
738 except error.TestFail:
739 pass
740 return False
741
Aseda Aboagye795cf352016-12-12 16:35:17 -0800742 def suspend(self):
743 """Suspends the DUT."""
Shelley Chen530490a2015-10-21 10:43:06 -0700744 cmd = '(sleep %d; powerd_dbus_suspend) &' % self.EC_SUSPEND_DELAY
745 self.faft_client.system.run_shell_command(cmd)
Shelley Chen530490a2015-10-21 10:43:06 -0700746 time.sleep(self.EC_SUSPEND_DELAY)
Shelley Chen530490a2015-10-21 10:43:06 -0700747
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700748 def _fetch_servo_log(self):
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700749 """Fetch the servo log."""
750 cmd = '[ -e %s ] && cat %s || echo NOTFOUND' % ((self._SERVOD_LOG,) * 2)
751 servo_log = self.servo.system_output(cmd)
752 return None if servo_log == 'NOTFOUND' else servo_log
753
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700754 def _setup_servo_log(self):
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700755 """Setup the servo log capturing."""
756 self.servo_log_original_len = -1
757 if self.servo.is_localhost():
758 # No servo log recorded when servod runs locally.
759 return
760
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700761 servo_log = self._fetch_servo_log()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700762 if servo_log:
763 self.servo_log_original_len = len(servo_log)
764 else:
765 logging.warn('Servo log file not found.')
766
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700767 def _record_servo_log(self):
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700768 """Record the servo log to the results directory."""
769 if self.servo_log_original_len != -1:
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700770 servo_log = self._fetch_servo_log()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700771 servo_log_file = os.path.join(self.resultsdir, 'servod.log')
772 with open(servo_log_file, 'a') as f:
773 f.write(servo_log[self.servo_log_original_len:])
774
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700775 def _record_faft_client_log(self):
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700776 """Record the faft client log to the results directory."""
777 client_log = self.faft_client.system.dump_log(True)
778 client_log_file = os.path.join(self.resultsdir, 'faft_client.log')
779 with open(client_log_file, 'w') as f:
780 f.write(client_log)
781
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700782 def _setup_gbb_flags(self):
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700783 """Setup the GBB flags for FAFT test."""
784 if self.faft_config.gbb_version < 1.1:
785 logging.info('Skip modifying GBB on versions older than 1.1.')
786 return
787
788 if self.check_setup_done('gbb_flags'):
789 return
790
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700791 logging.info('Set proper GBB flags for test.')
792 self.clear_set_gbb_flags(vboot.GBB_FLAG_DEV_SCREEN_SHORT_DELAY |
793 vboot.GBB_FLAG_FORCE_DEV_SWITCH_ON |
794 vboot.GBB_FLAG_FORCE_DEV_BOOT_USB |
Tom Wai-Hong Tam46ebbb12015-08-28 07:59:32 +0800795 vboot.GBB_FLAG_DISABLE_FW_ROLLBACK_CHECK |
796 vboot.GBB_FLAG_FORCE_DEV_BOOT_FASTBOOT_FULL_CAP,
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700797 vboot.GBB_FLAG_ENTER_TRIGGERS_TONORM |
798 vboot.GBB_FLAG_FAFT_KEY_OVERIDE)
799 self.mark_setup_done('gbb_flags')
800
801 def drop_backup_gbb_flags(self):
802 """Drops the backup GBB flags.
803
804 This can be used when a test intends to permanently change GBB flags.
805 """
806 self._backup_gbb_flags = None
807
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700808 def _restore_gbb_flags(self):
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700809 """Restore GBB flags to their original state."""
Tom Wai-Hong Tamfc0c7702015-09-12 03:43:53 +0800810 if self._backup_gbb_flags is None:
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700811 return
Tom Wai-Hong Tam2e3db4a2015-08-27 06:26:32 +0800812 # Setting up and restoring the GBB flags take a lot of time. For
813 # speed-up purpose, don't restore it.
814 logging.info('***')
815 logging.info('*** Please manually restore the original GBB flags to: '
816 '0x%x ***', self._backup_gbb_flags)
817 logging.info('***')
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700818 self.unmark_setup_done('gbb_flags')
819
820 def setup_tried_fwb(self, tried_fwb):
821 """Setup for fw B tried state.
822
823 It makes sure the system in the requested fw B tried state. If not, it
824 tries to do so.
825
826 @param tried_fwb: True if requested in tried_fwb=1;
827 False if tried_fwb=0.
828 """
829 if tried_fwb:
830 if not self.checkers.crossystem_checker({'tried_fwb': '1'}):
831 logging.info(
832 'Firmware is not booted with tried_fwb. Reboot into it.')
Yusuf Mohsinallyab1b5fc2014-05-08 12:46:10 -0700833 self.faft_client.system.set_try_fw_b()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700834 else:
835 if not self.checkers.crossystem_checker({'tried_fwb': '0'}):
836 logging.info(
837 'Firmware is booted with tried_fwb. Reboot to clear.')
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700838
839 def power_on(self):
840 """Switch DUT AC power on."""
841 self._client.power_on(self.power_control)
842
843 def power_off(self):
844 """Switch DUT AC power off."""
845 self._client.power_off(self.power_control)
846
847 def power_cycle(self):
848 """Power cycle DUT AC power."""
849 self._client.power_cycle(self.power_control)
850
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700851 def setup_rw_boot(self, section='a'):
852 """Make sure firmware is in RW-boot mode.
853
854 If the given firmware section is in RO-boot mode, turn off the RO-boot
855 flag and reboot DUT into RW-boot mode.
856
857 @param section: A firmware section, either 'a' or 'b'.
858 """
859 flags = self.faft_client.bios.get_preamble_flags(section)
860 if flags & vboot.PREAMBLE_USE_RO_NORMAL:
861 flags = flags ^ vboot.PREAMBLE_USE_RO_NORMAL
Yusuf Mohsinallyab1b5fc2014-05-08 12:46:10 -0700862 self.faft_client.bios.set_preamble_flags(section, flags)
Tom Wai-Hong Tam47776242015-05-07 02:45:32 +0800863 self.switcher.mode_aware_reboot()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700864
865 def setup_kernel(self, part):
866 """Setup for kernel test.
867
868 It makes sure both kernel A and B bootable and the current boot is
869 the requested kernel part.
870
871 @param part: A string of kernel partition number or 'a'/'b'.
872 """
873 self.ensure_kernel_boot(part)
874 logging.info('Checking the integrity of kernel B and rootfs B...')
875 if (self.faft_client.kernel.diff_a_b() or
876 not self.faft_client.rootfs.verify_rootfs('B')):
877 logging.info('Copying kernel and rootfs from A to B...')
878 self.copy_kernel_and_rootfs(from_part=part,
879 to_part=self.OTHER_KERNEL_MAP[part])
880 self.reset_and_prioritize_kernel(part)
881
882 def reset_and_prioritize_kernel(self, part):
883 """Make the requested partition highest priority.
884
885 This function also reset kerenl A and B to bootable.
886
887 @param part: A string of partition number to be prioritized.
888 """
889 root_dev = self.faft_client.system.get_root_dev()
890 # Reset kernel A and B to bootable.
891 self.faft_client.system.run_shell_command(
892 'cgpt add -i%s -P1 -S1 -T0 %s' % (self.KERNEL_MAP['a'], root_dev))
893 self.faft_client.system.run_shell_command(
894 'cgpt add -i%s -P1 -S1 -T0 %s' % (self.KERNEL_MAP['b'], root_dev))
895 # Set kernel part highest priority.
896 self.faft_client.system.run_shell_command('cgpt prioritize -i%s %s' %
897 (self.KERNEL_MAP[part], root_dev))
898
Yusuf Mohsinally1bacc962014-08-14 11:37:32 -0700899 def blocking_sync(self):
900 """Run a blocking sync command."""
901 # The double calls to sync fakes a blocking call
902 # since the first call returns before the flush
903 # is complete, but the second will wait for the
904 # first to finish.
905 self.faft_client.system.run_shell_command('sync')
906 self.faft_client.system.run_shell_command('sync')
907
Ryan Lin5bee6102014-09-16 13:17:02 -0700908 # sync only sends SYNCHRONIZE_CACHE but doesn't
Steve Fungb5752422015-01-09 16:45:32 -0800909 # check the status. For mmc devices, use `mmc
910 # status get` command to send an empty command to
911 # wait for the disk to be available again. For
912 # other devices, hdparm sends TUR to check if
Ryan Lin5bee6102014-09-16 13:17:02 -0700913 # a device is ready for transfer operation.
914 root_dev = self.faft_client.system.get_root_dev()
Steve Fungb5752422015-01-09 16:45:32 -0800915 if 'mmcblk' in root_dev:
916 self.faft_client.system.run_shell_command('mmc status get %s' %
917 root_dev)
918 else:
919 self.faft_client.system.run_shell_command('hdparm -f %s' % root_dev)
Yusuf Mohsinallyab1b5fc2014-05-08 12:46:10 -0700920
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700921 def sync_and_ec_reboot(self, flags=''):
922 """Request the client sync and do a EC triggered reboot.
923
924 @param flags: Optional, a space-separated string of flags passed to EC
925 reboot command, including:
926 default: EC soft reboot;
927 'hard': EC cold/hard reboot.
928 """
Yusuf Mohsinally1bacc962014-08-14 11:37:32 -0700929 self.blocking_sync()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700930 self.ec.reboot(flags)
931 time.sleep(self.faft_config.ec_boot_to_console)
932 self.check_lid_and_power_on()
933
Julius Werner18c4e162015-07-07 13:02:08 -0700934 def reboot_and_reset_tpm(self):
935 """Reboot into recovery mode, reset TPM, then reboot back to disk."""
936 self.switcher.reboot_to_mode(to_mode='rec')
937 self.faft_client.system.run_shell_command('chromeos-tpm-recovery')
Tom Wai-Hong Tama704f182015-05-06 06:12:55 +0800938 self.switcher.mode_aware_reboot()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700939
940 def full_power_off_and_on(self):
941 """Shutdown the device by pressing power button and power on again."""
Danny Chan101b0b22014-11-06 10:08:54 -0800942 boot_id = self.get_bootid()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700943 # Press power button to trigger Chrome OS normal shutdown process.
944 # We use a customized delay since the normal-press 1.2s is not enough.
David Hendricks25d703a2015-08-21 14:37:51 -0700945 self.servo.power_key(self.faft_config.hold_pwr_button_poweroff)
Danny Chan101b0b22014-11-06 10:08:54 -0800946 # device can take 44-51 seconds to restart,
947 # add buffer from the default timeout of 60 seconds.
Tom Wai-Hong Tam19bfb6e2015-08-20 06:05:36 +0800948 self.switcher.wait_for_client_offline(timeout=100, orig_boot_id=boot_id)
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700949 time.sleep(self.faft_config.shutdown)
950 # Short press power button to boot DUT again.
David Hendricks25d703a2015-08-21 14:37:51 -0700951 self.servo.power_key(self.faft_config.hold_pwr_button_poweron)
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700952
953 def check_lid_and_power_on(self):
954 """
955 On devices with EC software sync, system powers on after EC reboots if
956 lid is open. Otherwise, the EC shuts down CPU after about 3 seconds.
957 This method checks lid switch state and presses power button if
958 necessary.
959 """
960 if self.servo.get("lid_open") == "no":
961 time.sleep(self.faft_config.software_sync)
962 self.servo.power_short_press()
963
964 def _modify_usb_kernel(self, usb_dev, from_magic, to_magic):
965 """Modify the kernel header magic in USB stick.
966
967 The kernel header magic is the first 8-byte of kernel partition.
968 We modify it to make it fail on kernel verification check.
969
970 @param usb_dev: A string of USB stick path on the host, like '/dev/sdc'.
971 @param from_magic: A string of magic which we change it from.
972 @param to_magic: A string of magic which we change it to.
973 @raise TestError: if failed to change magic.
974 """
975 assert len(from_magic) == 8
976 assert len(to_magic) == 8
977 # USB image only contains one kernel.
978 kernel_part = self._join_part(usb_dev, self.KERNEL_MAP['a'])
979 read_cmd = "sudo dd if=%s bs=8 count=1 2>/dev/null" % kernel_part
980 current_magic = self.servo.system_output(read_cmd)
981 if current_magic == to_magic:
982 logging.info("The kernel magic is already %s.", current_magic)
983 return
984 if current_magic != from_magic:
985 raise error.TestError("Invalid kernel image on USB: wrong magic.")
986
987 logging.info('Modify the kernel magic in USB, from %s to %s.',
988 from_magic, to_magic)
989 write_cmd = ("echo -n '%s' | sudo dd of=%s oflag=sync conv=notrunc "
990 " 2>/dev/null" % (to_magic, kernel_part))
991 self.servo.system(write_cmd)
992
993 if self.servo.system_output(read_cmd) != to_magic:
994 raise error.TestError("Failed to write new magic.")
995
996 def corrupt_usb_kernel(self, usb_dev):
997 """Corrupt USB kernel by modifying its magic from CHROMEOS to CORRUPTD.
998
999 @param usb_dev: A string of USB stick path on the host, like '/dev/sdc'.
1000 """
1001 self._modify_usb_kernel(usb_dev, self.CHROMEOS_MAGIC,
1002 self.CORRUPTED_MAGIC)
1003
1004 def restore_usb_kernel(self, usb_dev):
1005 """Restore USB kernel by modifying its magic from CORRUPTD to CHROMEOS.
1006
1007 @param usb_dev: A string of USB stick path on the host, like '/dev/sdc'.
1008 """
1009 self._modify_usb_kernel(usb_dev, self.CORRUPTED_MAGIC,
1010 self.CHROMEOS_MAGIC)
1011
1012 def _call_action(self, action_tuple, check_status=False):
1013 """Call the action function with/without arguments.
1014
1015 @param action_tuple: A function, or a tuple (function, args, error_msg),
1016 in which, args and error_msg are optional. args is
1017 either a value or a tuple if multiple arguments.
1018 This can also be a list containing multiple
1019 function or tuple. In this case, these actions are
1020 called in sequence.
1021 @param check_status: Check the return value of action function. If not
1022 succeed, raises a TestFail exception.
1023 @return: The result value of the action function.
1024 @raise TestError: An error when the action function is not callable.
1025 @raise TestFail: When check_status=True, action function not succeed.
1026 """
1027 if isinstance(action_tuple, list):
1028 return all([self._call_action(action, check_status=check_status)
1029 for action in action_tuple])
1030
1031 action = action_tuple
1032 args = ()
1033 error_msg = 'Not succeed'
1034 if isinstance(action_tuple, tuple):
1035 action = action_tuple[0]
1036 if len(action_tuple) >= 2:
1037 args = action_tuple[1]
1038 if not isinstance(args, tuple):
1039 args = (args,)
1040 if len(action_tuple) >= 3:
1041 error_msg = action_tuple[2]
1042
1043 if action is None:
1044 return
1045
1046 if not callable(action):
1047 raise error.TestError('action is not callable!')
1048
1049 info_msg = 'calling %s' % str(action)
1050 if args:
1051 info_msg += ' with args %s' % str(args)
1052 logging.info(info_msg)
1053 ret = action(*args)
1054
1055 if check_status and not ret:
1056 raise error.TestFail('%s: %s returning %s' %
1057 (error_msg, info_msg, str(ret)))
1058 return ret
1059
1060 def run_shutdown_process(self, shutdown_action, pre_power_action=None,
Aseda Aboagye795cf352016-12-12 16:35:17 -08001061 run_power_action=True, post_power_action=None,
1062 shutdown_timeout=None):
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001063 """Run shutdown_action(), which makes DUT shutdown, and power it on.
1064
1065 @param shutdown_action: function which makes DUT shutdown, like
1066 pressing power key.
1067 @param pre_power_action: function which is called before next power on.
Aseda Aboagye795cf352016-12-12 16:35:17 -08001068 @param run_power_action: power_key press by default, set to None to skip.
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001069 @param post_power_action: function which is called after next power on.
1070 @param shutdown_timeout: a timeout to confirm DUT shutdown.
1071 @raise TestFail: if the shutdown_action() failed to turn DUT off.
1072 """
1073 self._call_action(shutdown_action)
1074 logging.info('Wait to ensure DUT shut down...')
1075 try:
1076 if shutdown_timeout is None:
1077 shutdown_timeout = self.faft_config.shutdown_timeout
Tom Wai-Hong Tam19bfb6e2015-08-20 06:05:36 +08001078 self.switcher.wait_for_client(timeout=shutdown_timeout)
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001079 raise error.TestFail(
1080 'Should shut the device down after calling %s.' %
1081 str(shutdown_action))
1082 except ConnectionError:
1083 logging.info(
1084 'DUT is surely shutdown. We are going to power it on again...')
1085
1086 if pre_power_action:
1087 self._call_action(pre_power_action)
Danny Chana7db9b52015-12-04 16:12:48 -08001088 if run_power_action:
1089 self.servo.power_key(self.faft_config.hold_pwr_button_poweron)
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001090 if post_power_action:
1091 self._call_action(post_power_action)
1092
Yusuf Mohsinallyab1b5fc2014-05-08 12:46:10 -07001093 def get_bootid(self, retry=3):
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001094 """
Yusuf Mohsinallyab1b5fc2014-05-08 12:46:10 -07001095 Return the bootid.
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001096 """
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001097 boot_id = None
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001098 while retry:
1099 try:
1100 boot_id = self._client.get_boot_id()
1101 break
1102 except error.AutoservRunError:
1103 retry -= 1
1104 if retry:
1105 logging.info('Retry to get boot_id...')
1106 else:
1107 logging.warning('Failed to get boot_id.')
1108 logging.info('boot_id: %s', boot_id)
Yusuf Mohsinallyab1b5fc2014-05-08 12:46:10 -07001109 return boot_id
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001110
Yusuf Mohsinallyab1b5fc2014-05-08 12:46:10 -07001111 def check_state(self, func):
1112 """
1113 Wrapper around _call_action with check_status set to True. This is a
1114 helper function to be used by tests and is currently implemented by
1115 calling _call_action with check_status=True.
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001116
Yusuf Mohsinallyab1b5fc2014-05-08 12:46:10 -07001117 TODO: This function's arguments need to be made more stringent. And
1118 its functionality should be moved over to check functions directly in
1119 the future.
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001120
Yusuf Mohsinallyab1b5fc2014-05-08 12:46:10 -07001121 @param func: A function, or a tuple (function, args, error_msg),
1122 in which, args and error_msg are optional. args is
1123 either a value or a tuple if multiple arguments.
1124 This can also be a list containing multiple
1125 function or tuple. In this case, these actions are
1126 called in sequence.
1127 @return: The result value of the action function.
1128 @raise TestFail: If the function does notsucceed.
1129 """
1130 logging.info("-[FAFT]-[ start stepstate_checker ]----------")
1131 self._call_action(func, check_status=True)
1132 logging.info("-[FAFT]-[ end state_checker ]----------------")
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001133
1134 def get_current_firmware_sha(self):
1135 """Get current firmware sha of body and vblock.
1136
1137 @return: Current firmware sha follows the order (
1138 vblock_a_sha, body_a_sha, vblock_b_sha, body_b_sha)
1139 """
1140 current_firmware_sha = (self.faft_client.bios.get_sig_sha('a'),
1141 self.faft_client.bios.get_body_sha('a'),
1142 self.faft_client.bios.get_sig_sha('b'),
1143 self.faft_client.bios.get_body_sha('b'))
1144 if not all(current_firmware_sha):
1145 raise error.TestError('Failed to get firmware sha.')
1146 return current_firmware_sha
1147
1148 def is_firmware_changed(self):
1149 """Check if the current firmware changed, by comparing its SHA.
1150
1151 @return: True if it is changed, otherwise Flase.
1152 """
1153 # Device may not be rebooted after test.
1154 self.faft_client.bios.reload()
1155
1156 current_sha = self.get_current_firmware_sha()
1157
1158 if current_sha == self._backup_firmware_sha:
1159 return False
1160 else:
1161 corrupt_VBOOTA = (current_sha[0] != self._backup_firmware_sha[0])
1162 corrupt_FVMAIN = (current_sha[1] != self._backup_firmware_sha[1])
1163 corrupt_VBOOTB = (current_sha[2] != self._backup_firmware_sha[2])
1164 corrupt_FVMAINB = (current_sha[3] != self._backup_firmware_sha[3])
1165 logging.info("Firmware changed:")
1166 logging.info('VBOOTA is changed: %s', corrupt_VBOOTA)
1167 logging.info('VBOOTB is changed: %s', corrupt_VBOOTB)
1168 logging.info('FVMAIN is changed: %s', corrupt_FVMAIN)
1169 logging.info('FVMAINB is changed: %s', corrupt_FVMAINB)
1170 return True
1171
1172 def backup_firmware(self, suffix='.original'):
1173 """Backup firmware to file, and then send it to host.
1174
1175 @param suffix: a string appended to backup file name
1176 """
1177 remote_temp_dir = self.faft_client.system.create_temp_dir()
Tom Wai-Hong Tame1d5e662015-08-26 05:29:54 +08001178 remote_bios_path = os.path.join(remote_temp_dir, 'bios')
1179 self.faft_client.bios.dump_whole(remote_bios_path)
1180 self._client.get_file(remote_bios_path,
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001181 os.path.join(self.resultsdir, 'bios' + suffix))
Tom Wai-Hong Tame1d5e662015-08-26 05:29:54 +08001182 self._client.run('rm -rf %s' % remote_temp_dir)
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001183 logging.info('Backup firmware stored in %s with suffix %s',
1184 self.resultsdir, suffix)
1185
Tom Wai-Hong Tame1d5e662015-08-26 05:29:54 +08001186 self._backup_firmware_sha = self.get_current_firmware_sha()
1187
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001188 def is_firmware_saved(self):
1189 """Check if a firmware saved (called backup_firmware before).
1190
1191 @return: True if the firmware is backuped; otherwise False.
1192 """
1193 return self._backup_firmware_sha != ()
1194
1195 def clear_saved_firmware(self):
1196 """Clear the firmware saved by the method backup_firmware."""
1197 self._backup_firmware_sha = ()
1198
1199 def restore_firmware(self, suffix='.original'):
1200 """Restore firmware from host in resultsdir.
1201
1202 @param suffix: a string appended to backup file name
1203 """
1204 if not self.is_firmware_changed():
1205 return
1206
1207 # Backup current corrupted firmware.
1208 self.backup_firmware(suffix='.corrupt')
1209
1210 # Restore firmware.
1211 remote_temp_dir = self.faft_client.system.create_temp_dir()
1212 self._client.send_file(os.path.join(self.resultsdir, 'bios' + suffix),
1213 os.path.join(remote_temp_dir, 'bios'))
1214
1215 self.faft_client.bios.write_whole(
1216 os.path.join(remote_temp_dir, 'bios'))
Tom Wai-Hong Tama704f182015-05-06 06:12:55 +08001217 self.switcher.mode_aware_reboot()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001218 logging.info('Successfully restore firmware.')
1219
1220 def setup_firmwareupdate_shellball(self, shellball=None):
Tom Wai-Hong Tam28d82a82016-03-04 06:44:43 +08001221 """Setup a shellball to use in firmware update test.
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001222
1223 Check if there is a given shellball, and it is a shell script. Then,
Tom Wai-Hong Tam28d82a82016-03-04 06:44:43 +08001224 send it to the remote host. Otherwise, use the
1225 /usr/sbin/chromeos-firmwareupdate in the image and replace its inside
1226 BIOS and EC images with the active firmware images.
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001227
1228 @param shellball: path of a shellball or default to None.
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001229 """
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001230 if shellball:
1231 # Determine the firmware file is a shellball or a raw binary.
1232 is_shellball = (utils.system_output("file %s" % shellball).find(
1233 "shell script") != -1)
1234 if is_shellball:
1235 logging.info('Device will update firmware with shellball %s',
1236 shellball)
Tom Wai-Hong Tam28d82a82016-03-04 06:44:43 +08001237 temp_path = self.faft_client.updater.get_temp_path()
1238 working_shellball = os.path.join(temp_path,
1239 'chromeos-firmwareupdate')
1240 self._client.send_file(shellball, working_shellball)
1241 self.faft_client.updater.extract_shellball()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001242 else:
1243 raise error.TestFail(
1244 'The given shellball is not a shell script.')
Tom Wai-Hong Tam28d82a82016-03-04 06:44:43 +08001245 else:
1246 logging.info('No shellball given, use the original shellball and '
1247 'replace its BIOS and EC images.')
1248 work_path = self.faft_client.updater.get_work_path()
1249 bios_in_work_path = os.path.join(work_path, 'bios.bin')
1250 ec_in_work_path = os.path.join(work_path, 'ec.bin')
1251 self.faft_client.bios.dump_whole(bios_in_work_path)
Tom Wai-Hong Tam133b00c2016-06-22 04:06:19 +08001252 if self.faft_config.chrome_ec:
1253 self.faft_client.ec.dump_firmware(ec_in_work_path)
Tom Wai-Hong Tam28d82a82016-03-04 06:44:43 +08001254 self.faft_client.updater.repack_shellball()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001255
1256 def is_kernel_changed(self):
1257 """Check if the current kernel is changed, by comparing its SHA1 hash.
1258
1259 @return: True if it is changed; otherwise, False.
1260 """
1261 changed = False
1262 for p in ('A', 'B'):
1263 backup_sha = self._backup_kernel_sha.get(p, None)
1264 current_sha = self.faft_client.kernel.get_sha(p)
1265 if backup_sha != current_sha:
1266 changed = True
1267 logging.info('Kernel %s is changed', p)
1268 return changed
1269
1270 def backup_kernel(self, suffix='.original'):
1271 """Backup kernel to files, and the send them to host.
1272
1273 @param suffix: a string appended to backup file name.
1274 """
1275 remote_temp_dir = self.faft_client.system.create_temp_dir()
1276 for p in ('A', 'B'):
1277 remote_path = os.path.join(remote_temp_dir, 'kernel_%s' % p)
1278 self.faft_client.kernel.dump(p, remote_path)
1279 self._client.get_file(
1280 remote_path,
1281 os.path.join(self.resultsdir, 'kernel_%s%s' % (p, suffix)))
1282 self._backup_kernel_sha[p] = self.faft_client.kernel.get_sha(p)
1283 logging.info('Backup kernel stored in %s with suffix %s',
1284 self.resultsdir, suffix)
1285
1286 def is_kernel_saved(self):
1287 """Check if kernel images are saved (backup_kernel called before).
1288
1289 @return: True if the kernel is saved; otherwise, False.
1290 """
1291 return len(self._backup_kernel_sha) != 0
1292
1293 def clear_saved_kernel(self):
1294 """Clear the kernel saved by backup_kernel()."""
1295 self._backup_kernel_sha = dict()
1296
1297 def restore_kernel(self, suffix='.original'):
1298 """Restore kernel from host in resultsdir.
1299
1300 @param suffix: a string appended to backup file name.
1301 """
1302 if not self.is_kernel_changed():
1303 return
1304
1305 # Backup current corrupted kernel.
1306 self.backup_kernel(suffix='.corrupt')
1307
1308 # Restore kernel.
1309 remote_temp_dir = self.faft_client.system.create_temp_dir()
1310 for p in ('A', 'B'):
1311 remote_path = os.path.join(remote_temp_dir, 'kernel_%s' % p)
1312 self._client.send_file(
1313 os.path.join(self.resultsdir, 'kernel_%s%s' % (p, suffix)),
1314 remote_path)
1315 self.faft_client.kernel.write(p, remote_path)
1316
Tom Wai-Hong Tama704f182015-05-06 06:12:55 +08001317 self.switcher.mode_aware_reboot()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001318 logging.info('Successfully restored kernel.')
1319
1320 def backup_cgpt_attributes(self):
1321 """Backup CGPT partition table attributes."""
1322 self._backup_cgpt_attr = self.faft_client.cgpt.get_attributes()
1323
1324 def restore_cgpt_attributes(self):
1325 """Restore CGPT partition table attributes."""
1326 current_table = self.faft_client.cgpt.get_attributes()
1327 if current_table == self._backup_cgpt_attr:
1328 return
1329 logging.info('CGPT table is changed. Original: %r. Current: %r.',
1330 self._backup_cgpt_attr,
1331 current_table)
1332 self.faft_client.cgpt.set_attributes(self._backup_cgpt_attr)
1333
Tom Wai-Hong Tama704f182015-05-06 06:12:55 +08001334 self.switcher.mode_aware_reboot()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001335 logging.info('Successfully restored CGPT table.')
Shelley Chen3edea982014-12-30 14:54:21 -08001336
1337 def try_fwb(self, count=0):
1338 """set to try booting FWB count # times
1339
1340 Wrapper to set fwb_tries for vboot1 and fw_try_count,fw_try_next for
1341 vboot2
1342
1343 @param count: an integer specifying value to program into
1344 fwb_tries(vb1)/fw_try_next(vb2)
1345 """
1346 if self.fw_vboot2:
1347 self.faft_client.system.set_fw_try_next('B', count)
1348 else:
1349 # vboot1: we need to boot into fwb at least once
1350 if not count:
1351 count = count + 1
1352 self.faft_client.system.set_try_fw_b(count)
1353