blob: 0f43a8fd76d0765261f4a64701d15f2e58ffce08 [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')
Andrey Pronin3a1d5092016-07-12 15:55:34 -0700182 # FAFT client stopped tcsd or trunksd at startup. Restart it.
Andrey Pronin96a9dec2016-07-20 15:34:00 -0700183 # Only one is present, so start the one known to initctl.
184 status = self.faft_client.system.run_shell_command_get_output(
185 'initctl status trunksd') or ['']
186 if status[0].startswith('trunksd'):
187 self._start_service('trunksd')
188 else:
189 self._start_service('tcsd')
Duncan Laurie10eb6182014-10-07 15:39:05 -0700190 self._remove_faft_lockfile()
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700191 self._record_servo_log()
192 self._record_faft_client_log()
193 self._cleanup_uart_capture()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700194 super(FirmwareTest, self).cleanup()
195 logging.info('FirmwareTest cleanup done (id=%s)', self.run_id)
196
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700197 def _record_system_info(self):
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700198 """Record some critical system info to the attr keyval.
199
Christopher Wiley004a8cd2015-05-19 11:49:13 -0700200 This info is used by generate_test_report later.
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700201 """
Tom Wai-Hong Tam9a10b9b2015-09-28 16:29:28 +0800202 system_info = {
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700203 'hwid': self.faft_client.system.get_crossystem_value('hwid'),
Tom Wai-Hong Tam32075c12016-01-20 07:09:47 +0800204 'ec_version': self.faft_client.ec.get_version(),
205 'ro_fwid': self.faft_client.system.get_crossystem_value('ro_fwid'),
206 'rw_fwid': self.faft_client.system.get_crossystem_value('fwid'),
Tom Wai-Hong Tam9a10b9b2015-09-28 16:29:28 +0800207 'servod_version': self._client._servo_host.run(
208 'servod --version').stdout.strip(),
209 }
210 logging.info('System info:\n' + pprint.pformat(system_info))
211 self.write_attr_keyval(system_info)
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700212
213 def invalidate_firmware_setup(self):
214 """Invalidate all firmware related setup state.
215
216 This method is called when the firmware is re-flashed. It resets all
217 firmware related setup states so that the next test setup properly
218 again.
219 """
220 self.unmark_setup_done('gbb_flags')
221
222 def _retrieve_recovery_reason_from_trap(self):
223 """Try to retrieve the recovery reason from a trapped recovery screen.
224
225 @return: The recovery_reason, 0 if any error.
226 """
227 recovery_reason = 0
228 logging.info('Try to retrieve recovery reason...')
229 if self.servo.get_usbkey_direction() == 'dut':
Tom Wai-Hong Tam04302882015-05-14 06:08:34 +0800230 self.switcher.bypass_rec_mode()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700231 else:
232 self.servo.switch_usbkey('dut')
233
234 try:
Tom Wai-Hong Tam19bfb6e2015-08-20 06:05:36 +0800235 self.switcher.wait_for_client()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700236 lines = self.faft_client.system.run_shell_command_get_output(
237 'crossystem recovery_reason')
238 recovery_reason = int(lines[0])
239 logging.info('Got the recovery reason %d.', recovery_reason)
240 except ConnectionError:
241 logging.error('Failed to get the recovery reason due to connection '
242 'error.')
243 return recovery_reason
244
245 def _reset_client(self):
246 """Reset client to a workable state.
247
248 This method is called when the client is not responsive. It may be
249 caused by the following cases:
250 - halt on a firmware screen without timeout, e.g. REC_INSERT screen;
251 - corrupted firmware;
252 - corrutped OS image.
253 """
254 # DUT may halt on a firmware screen. Try cold reboot.
255 logging.info('Try cold reboot...')
Tom Wai-Hong Tama704f182015-05-06 06:12:55 +0800256 self.switcher.mode_aware_reboot(reboot_type='cold',
257 sync_before_boot=False,
258 wait_for_dut_up=False)
Tom Wai-Hong Tam19bfb6e2015-08-20 06:05:36 +0800259 self.switcher.wait_for_client_offline()
Tom Wai-Hong Tam04302882015-05-14 06:08:34 +0800260 self.switcher.bypass_dev_mode()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700261 try:
Tom Wai-Hong Tam19bfb6e2015-08-20 06:05:36 +0800262 self.switcher.wait_for_client()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700263 return
264 except ConnectionError:
265 logging.warn('Cold reboot doesn\'t help, still connection error.')
266
267 # DUT may be broken by a corrupted firmware. Restore firmware.
268 # We assume the recovery boot still works fine. Since the recovery
269 # code is in RO region and all FAFT tests don't change the RO region
270 # except GBB.
271 if self.is_firmware_saved():
272 self._ensure_client_in_recovery()
273 logging.info('Try restore the original firmware...')
274 if self.is_firmware_changed():
275 try:
276 self.restore_firmware()
277 return
278 except ConnectionError:
279 logging.warn('Restoring firmware doesn\'t help, still '
280 'connection error.')
281
282 # Perhaps it's kernel that's broken. Let's try restoring it.
283 if self.is_kernel_saved():
284 self._ensure_client_in_recovery()
285 logging.info('Try restore the original kernel...')
286 if self.is_kernel_changed():
287 try:
288 self.restore_kernel()
289 return
290 except ConnectionError:
291 logging.warn('Restoring kernel doesn\'t help, still '
292 'connection error.')
293
294 # DUT may be broken by a corrupted OS image. Restore OS image.
295 self._ensure_client_in_recovery()
296 logging.info('Try restore the OS image...')
297 self.faft_client.system.run_shell_command('chromeos-install --yes')
Tom Wai-Hong Tama704f182015-05-06 06:12:55 +0800298 self.switcher.mode_aware_reboot(wait_for_dut_up=False)
Tom Wai-Hong Tam19bfb6e2015-08-20 06:05:36 +0800299 self.switcher.wait_for_client_offline()
Tom Wai-Hong Tam04302882015-05-14 06:08:34 +0800300 self.switcher.bypass_dev_mode()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700301 try:
Tom Wai-Hong Tam19bfb6e2015-08-20 06:05:36 +0800302 self.switcher.wait_for_client()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700303 logging.info('Successfully restore OS image.')
304 return
305 except ConnectionError:
306 logging.warn('Restoring OS image doesn\'t help, still connection '
307 'error.')
308
309 def _ensure_client_in_recovery(self):
310 """Ensure client in recovery boot; reboot into it if necessary.
311
312 @raise TestError: if failed to boot the USB image.
313 """
314 logging.info('Try boot into USB image...')
Tom Wai-Hong Tamd7a0d052015-05-14 02:18:23 +0800315 self.switcher.reboot_to_mode(to_mode='rec', sync_before_boot=False,
316 wait_for_dut_up=False)
Tom Wai-Hong Tamf2de4de2015-05-02 02:48:08 +0800317 self.servo.switch_usbkey('host')
Tom Wai-Hong Tam04302882015-05-14 06:08:34 +0800318 self.switcher.bypass_rec_mode()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700319 try:
Tom Wai-Hong Tam19bfb6e2015-08-20 06:05:36 +0800320 self.switcher.wait_for_client()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700321 except ConnectionError:
322 raise error.TestError('Failed to boot the USB image.')
323
Yusuf Mohsinally64ee3a72014-06-26 10:24:27 -0700324 def _restore_routine_from_timeout(self):
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700325 """A routine to try to restore the system from a timeout error.
326
327 This method is called when FAFT failed to connect DUT after reboot.
328
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700329 @raise TestFail: This exception is already raised, with a decription
330 why it failed.
331 """
332 # DUT is disconnected. Capture the UART output for debug.
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700333 self._record_uart_capture()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700334
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700335 # TODO(waihong@chromium.org): Implement replugging the Ethernet to
336 # identify if it is a network flaky.
337
338 recovery_reason = self._retrieve_recovery_reason_from_trap()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700339
340 # Reset client to a workable state.
341 self._reset_client()
342
343 # Raise the proper TestFail exception.
Yusuf Mohsinally64ee3a72014-06-26 10:24:27 -0700344 if recovery_reason:
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700345 raise error.TestFail('Trapped in the recovery screen (reason: %d) '
346 'and timed out' % recovery_reason)
347 else:
348 raise error.TestFail('Timed out waiting for DUT reboot')
349
Julius Werner18c4e162015-07-07 13:02:08 -0700350 def assert_test_image_in_usb_disk(self, usb_dev=None):
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700351 """Assert an USB disk plugged-in on servo and a test image inside.
352
353 @param usb_dev: A string of USB stick path on the host, like '/dev/sdc'.
354 If None, it is detected automatically.
Julius Werner18c4e162015-07-07 13:02:08 -0700355 @raise TestError: if USB disk not detected or not a test image.
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700356 """
357 if self.check_setup_done('usb_check'):
358 return
359 if usb_dev:
360 assert self.servo.get_usbkey_direction() == 'host'
361 else:
362 self.servo.switch_usbkey('host')
363 usb_dev = self.servo.probe_host_usb_dev()
364 if not usb_dev:
365 raise error.TestError(
366 'An USB disk should be plugged in the servo board.')
367
368 rootfs = '%s%s' % (usb_dev, self._ROOTFS_PARTITION_NUMBER)
369 logging.info('usb dev is %s', usb_dev)
370 tmpd = self.servo.system_output('mktemp -d -t usbcheck.XXXX')
371 self.servo.system('mount -o ro %s %s' % (rootfs, tmpd))
372
Julius Wernerdc535df2015-02-26 16:42:38 -0800373 try:
Julius Werner18c4e162015-07-07 13:02:08 -0700374 usb_lsb = self.servo.system_output('cat %s' %
375 os.path.join(tmpd, 'etc/lsb-release'))
376 logging.debug('Dumping lsb-release on USB stick:\n%s', usb_lsb)
377 dut_lsb = '\n'.join(self.faft_client.system.
378 run_shell_command_get_output('cat /etc/lsb-release'))
379 logging.debug('Dumping lsb-release on DUT:\n%s', dut_lsb)
Julius Wernere6adca42015-08-13 11:10:59 -0700380 if not re.search(r'RELEASE_TRACK=.*test', usb_lsb):
Julius Werner18c4e162015-07-07 13:02:08 -0700381 raise error.TestError('USB stick in servo is no test image')
382 usb_board = re.search(r'BOARD=(.*)', usb_lsb).group(1)
383 dut_board = re.search(r'BOARD=(.*)', dut_lsb).group(1)
384 if usb_board != dut_board:
385 raise error.TestError('USB stick in servo contains a %s '
386 'image, but DUT is a %s' % (usb_board, dut_board))
Julius Wernerdc535df2015-02-26 16:42:38 -0800387 finally:
388 for cmd in ('umount %s' % rootfs, 'sync', 'rm -rf %s' % tmpd):
389 self.servo.system(cmd)
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700390
391 self.mark_setup_done('usb_check')
392
Julius Werner18c4e162015-07-07 13:02:08 -0700393 def setup_usbkey(self, usbkey, host=None):
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700394 """Setup the USB disk for the test.
395
396 It checks the setup of USB disk and a valid ChromeOS test image inside.
397 It also muxes the USB disk to either the host or DUT by request.
398
399 @param usbkey: True if the USB disk is required for the test, False if
400 not required.
401 @param host: Optional, True to mux the USB disk to host, False to mux it
402 to DUT, default to do nothing.
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700403 """
404 if usbkey:
Julius Werner18c4e162015-07-07 13:02:08 -0700405 self.assert_test_image_in_usb_disk()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700406 elif host is None:
407 # USB disk is not required for the test. Better to mux it to host.
408 host = True
409
410 if host is True:
411 self.servo.switch_usbkey('host')
412 elif host is False:
413 self.servo.switch_usbkey('dut')
414
415 def get_usbdisk_path_on_dut(self):
416 """Get the path of the USB disk device plugged-in the servo on DUT.
417
418 Returns:
419 A string representing USB disk path, like '/dev/sdb', or None if
420 no USB disk is found.
421 """
422 cmd = 'ls -d /dev/s*[a-z]'
423 original_value = self.servo.get_usbkey_direction()
424
425 # Make the dut unable to see the USB disk.
426 self.servo.switch_usbkey('off')
427 no_usb_set = set(
428 self.faft_client.system.run_shell_command_get_output(cmd))
429
430 # Make the dut able to see the USB disk.
431 self.servo.switch_usbkey('dut')
Tom Wai-Hong Tamb0314a02015-05-20 05:25:22 +0800432 time.sleep(self.faft_config.usb_plug)
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700433 has_usb_set = set(
434 self.faft_client.system.run_shell_command_get_output(cmd))
435
436 # Back to its original value.
437 if original_value != self.servo.get_usbkey_direction():
438 self.servo.switch_usbkey(original_value)
439
440 diff_set = has_usb_set - no_usb_set
441 if len(diff_set) == 1:
442 return diff_set.pop()
443 else:
444 return None
445
Duncan Laurie10eb6182014-10-07 15:39:05 -0700446 def _create_faft_lockfile(self):
447 """Creates the FAFT lockfile."""
448 logging.info('Creating FAFT lockfile...')
449 command = 'touch %s' % (self.lockfile)
450 self.faft_client.system.run_shell_command(command)
451
452 def _remove_faft_lockfile(self):
453 """Removes the FAFT lockfile."""
454 logging.info('Removing FAFT lockfile...')
455 command = 'rm -f %s' % (self.lockfile)
456 self.faft_client.system.run_shell_command(command)
457
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700458 def _stop_service(self, service):
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700459 """Stops a upstart service on the client.
460
461 @param service: The name of the upstart service.
462 """
463 logging.info('Stopping %s...', service)
464 command = 'status %s | grep stop || stop %s' % (service, service)
465 self.faft_client.system.run_shell_command(command)
466
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700467 def _start_service(self, service):
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700468 """Starts a upstart service on the client.
469
470 @param service: The name of the upstart service.
471 """
472 logging.info('Starting %s...', service)
473 command = 'status %s | grep start || start %s' % (service, service)
474 self.faft_client.system.run_shell_command(command)
475
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700476 def clear_set_gbb_flags(self, clear_mask, set_mask):
477 """Clear and set the GBB flags in the current flashrom.
478
479 @param clear_mask: A mask of flags to be cleared.
480 @param set_mask: A mask of flags to be set.
481 """
482 gbb_flags = self.faft_client.bios.get_gbb_flags()
483 new_flags = gbb_flags & ctypes.c_uint32(~clear_mask).value | set_mask
Tom Wai-Hong Tamfc0c7702015-09-12 03:43:53 +0800484 if new_flags != gbb_flags:
485 self._backup_gbb_flags = gbb_flags
486 logging.info('Changing GBB flags from 0x%x to 0x%x.',
487 gbb_flags, new_flags)
488 self.faft_client.bios.set_gbb_flags(new_flags)
489 # If changing FORCE_DEV_SWITCH_ON flag, reboot to get a clear state
490 if ((gbb_flags ^ new_flags) & vboot.GBB_FLAG_FORCE_DEV_SWITCH_ON):
491 self.switcher.mode_aware_reboot()
492 else:
493 logging.info('Current GBB flags look good for test: 0x%x.',
494 gbb_flags)
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700495
496 def check_ec_capability(self, required_cap=None, suppress_warning=False):
497 """Check if current platform has required EC capabilities.
498
499 @param required_cap: A list containing required EC capabilities. Pass in
500 None to only check for presence of Chrome EC.
501 @param suppress_warning: True to suppress any warning messages.
502 @return: True if requirements are met. Otherwise, False.
503 """
504 if not self.faft_config.chrome_ec:
505 if not suppress_warning:
506 logging.warn('Requires Chrome EC to run this test.')
507 return False
508
509 if not required_cap:
510 return True
511
512 for cap in required_cap:
513 if cap not in self.faft_config.ec_capability:
514 if not suppress_warning:
515 logging.warn('Requires EC capability "%s" to run this '
516 'test.', cap)
517 return False
518
519 return True
520
521 def check_root_part_on_non_recovery(self, part):
522 """Check the partition number of root device and on normal/dev boot.
523
524 @param part: A string of partition number, e.g.'3'.
525 @return: True if the root device matched and on normal/dev boot;
526 otherwise, False.
527 """
528 return self.checkers.root_part_checker(part) and \
529 self.checkers.crossystem_checker({
530 'mainfw_type': ('normal', 'developer'),
531 })
532
533 def _join_part(self, dev, part):
534 """Return a concatenated string of device and partition number.
535
536 @param dev: A string of device, e.g.'/dev/sda'.
537 @param part: A string of partition number, e.g.'3'.
538 @return: A concatenated string of device and partition number,
539 e.g.'/dev/sda3'.
540
541 >>> seq = FirmwareTest()
542 >>> seq._join_part('/dev/sda', '3')
543 '/dev/sda3'
544 >>> seq._join_part('/dev/mmcblk0', '2')
545 '/dev/mmcblk0p2'
546 """
547 if 'mmcblk' in dev:
548 return dev + 'p' + part
549 else:
550 return dev + part
551
552 def copy_kernel_and_rootfs(self, from_part, to_part):
553 """Copy kernel and rootfs from from_part to to_part.
554
555 @param from_part: A string of partition number to be copied from.
556 @param to_part: A string of partition number to be copied to.
557 """
558 root_dev = self.faft_client.system.get_root_dev()
559 logging.info('Copying kernel from %s to %s. Please wait...',
560 from_part, to_part)
561 self.faft_client.system.run_shell_command('dd if=%s of=%s bs=4M' %
562 (self._join_part(root_dev, self.KERNEL_MAP[from_part]),
563 self._join_part(root_dev, self.KERNEL_MAP[to_part])))
564 logging.info('Copying rootfs from %s to %s. Please wait...',
565 from_part, to_part)
566 self.faft_client.system.run_shell_command('dd if=%s of=%s bs=4M' %
567 (self._join_part(root_dev, self.ROOTFS_MAP[from_part]),
568 self._join_part(root_dev, self.ROOTFS_MAP[to_part])))
569
570 def ensure_kernel_boot(self, part):
571 """Ensure the request kernel boot.
572
573 If not, it duplicates the current kernel to the requested kernel
574 and sets the requested higher priority to ensure it boot.
575
576 @param part: A string of kernel partition number or 'a'/'b'.
577 """
578 if not self.checkers.root_part_checker(part):
579 if self.faft_client.kernel.diff_a_b():
580 self.copy_kernel_and_rootfs(
581 from_part=self.OTHER_KERNEL_MAP[part],
582 to_part=part)
Yusuf Mohsinallyab1b5fc2014-05-08 12:46:10 -0700583 self.reset_and_prioritize_kernel(part)
Tom Wai-Hong Tamb1a86012015-09-25 14:27:20 +0800584 self.switcher.mode_aware_reboot()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700585
586 def set_hardware_write_protect(self, enable):
587 """Set hardware write protect pin.
588
589 @param enable: True if asserting write protect pin. Otherwise, False.
590 """
591 self.servo.set('fw_wp_vref', self.faft_config.wp_voltage)
592 self.servo.set('fw_wp_en', 'on')
593 self.servo.set('fw_wp', 'on' if enable else 'off')
594
595 def set_ec_write_protect_and_reboot(self, enable):
596 """Set EC write protect status and reboot to take effect.
597
598 The write protect state is only activated if both hardware write
599 protect pin is asserted and software write protect flag is set.
600 This method asserts/deasserts hardware write protect pin first, and
601 set corresponding EC software write protect flag.
602
603 If the device uses non-Chrome EC, set the software write protect via
604 flashrom.
605
606 If the device uses Chrome EC, a reboot is required for write protect
607 to take effect. Since the software write protect flag cannot be unset
608 if hardware write protect pin is asserted, we need to deasserted the
609 pin first if we are deactivating write protect. Similarly, a reboot
610 is required before we can modify the software flag.
611
612 @param enable: True if activating EC write protect. Otherwise, False.
613 """
614 self.set_hardware_write_protect(enable)
615 if self.faft_config.chrome_ec:
616 self.set_chrome_ec_write_protect_and_reboot(enable)
617 else:
618 self.faft_client.ec.set_write_protect(enable)
Tom Wai-Hong Tama704f182015-05-06 06:12:55 +0800619 self.switcher.mode_aware_reboot()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700620
621 def set_chrome_ec_write_protect_and_reboot(self, enable):
622 """Set Chrome EC write protect status and reboot to take effect.
623
624 @param enable: True if activating EC write protect. Otherwise, False.
625 """
626 if enable:
627 # Set write protect flag and reboot to take effect.
628 self.ec.set_flash_write_protect(enable)
629 self.sync_and_ec_reboot()
630 else:
631 # Reboot after deasserting hardware write protect pin to deactivate
632 # write protect. And then remove software write protect flag.
633 self.sync_and_ec_reboot()
634 self.ec.set_flash_write_protect(enable)
635
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700636 def _setup_ec_write_protect(self, ec_wp):
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700637 """Setup for EC write-protection.
638
639 It makes sure the EC in the requested write-protection state. If not, it
640 flips the state. Flipping the write-protection requires DUT reboot.
641
642 @param ec_wp: True to request EC write-protected; False to request EC
643 not write-protected; None to do nothing.
644 """
645 if ec_wp is None:
646 self._old_ec_wp = None
647 return
648 self._old_ec_wp = self.checkers.crossystem_checker({'wpsw_boot': '1'})
649 if ec_wp != self._old_ec_wp:
650 logging.info('The test required EC is %swrite-protected. Reboot '
651 'and flip the state.', '' if ec_wp else 'not ')
Tom Wai-Hong Tam3e92b8e2015-05-07 06:29:57 +0800652 self.switcher.mode_aware_reboot(
653 'custom',
654 lambda:self.set_ec_write_protect_and_reboot(ec_wp))
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700655
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700656 def _restore_ec_write_protect(self):
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700657 """Restore the original EC write-protection."""
658 if (not hasattr(self, '_old_ec_wp')) or (self._old_ec_wp is None):
659 return
660 if not self.checkers.crossystem_checker(
661 {'wpsw_boot': '1' if self._old_ec_wp else '0'}):
Yusuf Mohsinallyab1b5fc2014-05-08 12:46:10 -0700662 logging.info('Restore original EC write protection and reboot.')
Tom Wai-Hong Tam3e92b8e2015-05-07 06:29:57 +0800663 self.switcher.mode_aware_reboot(
664 'custom',
665 lambda:self.set_ec_write_protect_and_reboot(
666 self._old_ec_wp))
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700667
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700668 def _setup_uart_capture(self):
Duncan Laurieaf61c1f2014-10-07 15:35:18 -0700669 """Setup the CPU/EC/PD UART capture."""
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700670 self.cpu_uart_file = os.path.join(self.resultsdir, 'cpu_uart.txt')
671 self.servo.set('cpu_uart_capture', 'on')
672 self.ec_uart_file = None
Duncan Laurieaf61c1f2014-10-07 15:35:18 -0700673 self.usbpd_uart_file = None
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700674 if self.faft_config.chrome_ec:
675 try:
676 self.servo.set('ec_uart_capture', 'on')
677 self.ec_uart_file = os.path.join(self.resultsdir, 'ec_uart.txt')
678 except error.TestFail as e:
679 if 'No control named' in str(e):
680 logging.warn('The servod is too old that ec_uart_capture '
681 'not supported.')
Duncan Laurieaf61c1f2014-10-07 15:35:18 -0700682 # Log separate PD console if supported
683 if self.check_ec_capability(['usbpd_uart'], suppress_warning=True):
684 try:
685 self.servo.set('usbpd_uart_capture', 'on')
686 self.usbpd_uart_file = os.path.join(self.resultsdir,
687 'usbpd_uart.txt')
688 except error.TestFail as e:
689 if 'No control named' in str(e):
690 logging.warn('The servod is too old that '
691 'usbpd_uart_capture is not supported.')
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700692 else:
693 logging.info('Not a Google EC, cannot capture ec console output.')
694
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700695 def _record_uart_capture(self):
Duncan Laurieaf61c1f2014-10-07 15:35:18 -0700696 """Record the CPU/EC/PD UART output stream to files."""
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700697 if self.cpu_uart_file:
698 with open(self.cpu_uart_file, 'a') as f:
699 f.write(ast.literal_eval(self.servo.get('cpu_uart_stream')))
700 if self.ec_uart_file and self.faft_config.chrome_ec:
701 with open(self.ec_uart_file, 'a') as f:
702 f.write(ast.literal_eval(self.servo.get('ec_uart_stream')))
Duncan Laurieaf61c1f2014-10-07 15:35:18 -0700703 if (self.usbpd_uart_file and self.faft_config.chrome_ec and
704 self.check_ec_capability(['usbpd_uart'], suppress_warning=True)):
705 with open(self.usbpd_uart_file, 'a') as f:
706 f.write(ast.literal_eval(self.servo.get('usbpd_uart_stream')))
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700707
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700708 def _cleanup_uart_capture(self):
Duncan Laurieaf61c1f2014-10-07 15:35:18 -0700709 """Cleanup the CPU/EC/PD UART capture."""
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700710 # Flush the remaining UART output.
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700711 self._record_uart_capture()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700712 self.servo.set('cpu_uart_capture', 'off')
713 if self.ec_uart_file and self.faft_config.chrome_ec:
714 self.servo.set('ec_uart_capture', 'off')
Duncan Laurieaf61c1f2014-10-07 15:35:18 -0700715 if (self.usbpd_uart_file and self.faft_config.chrome_ec and
716 self.check_ec_capability(['usbpd_uart'], suppress_warning=True)):
717 self.servo.set('usbpd_uart_capture', 'off')
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700718
Shelley Chenc0781c42015-10-05 10:52:42 -0700719 def _get_power_state(self, power_state):
720 """
721 Return the current power state of the AP
722 """
723 return self.ec.send_command_get_output("powerinfo", [power_state])
724
725 def wait_power_state(self, power_state, retries):
726 """
727 Wait for certain power state.
728
729 @param power_state: power state you are expecting
730 @param retries: retries. This is necessary if AP is powering down
731 and transitioning through different states.
732 """
733 logging.info('Checking power state "%s" maximum %d times.',
734 power_state, retries)
735 while retries > 0:
Aseda Aboagye795cf352016-12-12 16:35:17 -0800736 logging.info("try count: %d", retries)
Shelley Chenc0781c42015-10-05 10:52:42 -0700737 try:
738 retries = retries - 1
739 ret = self._get_power_state(power_state)
740 return True
741 except error.TestFail:
742 pass
743 return False
744
Aseda Aboagye795cf352016-12-12 16:35:17 -0800745 def suspend(self):
746 """Suspends the DUT."""
Shelley Chen530490a2015-10-21 10:43:06 -0700747 cmd = '(sleep %d; powerd_dbus_suspend) &' % self.EC_SUSPEND_DELAY
748 self.faft_client.system.run_shell_command(cmd)
Shelley Chen530490a2015-10-21 10:43:06 -0700749 time.sleep(self.EC_SUSPEND_DELAY)
Shelley Chen530490a2015-10-21 10:43:06 -0700750
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700751 def _fetch_servo_log(self):
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700752 """Fetch the servo log."""
753 cmd = '[ -e %s ] && cat %s || echo NOTFOUND' % ((self._SERVOD_LOG,) * 2)
754 servo_log = self.servo.system_output(cmd)
755 return None if servo_log == 'NOTFOUND' else servo_log
756
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700757 def _setup_servo_log(self):
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700758 """Setup the servo log capturing."""
759 self.servo_log_original_len = -1
760 if self.servo.is_localhost():
761 # No servo log recorded when servod runs locally.
762 return
763
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700764 servo_log = self._fetch_servo_log()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700765 if servo_log:
766 self.servo_log_original_len = len(servo_log)
767 else:
768 logging.warn('Servo log file not found.')
769
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700770 def _record_servo_log(self):
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700771 """Record the servo log to the results directory."""
772 if self.servo_log_original_len != -1:
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700773 servo_log = self._fetch_servo_log()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700774 servo_log_file = os.path.join(self.resultsdir, 'servod.log')
775 with open(servo_log_file, 'a') as f:
776 f.write(servo_log[self.servo_log_original_len:])
777
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700778 def _record_faft_client_log(self):
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700779 """Record the faft client log to the results directory."""
780 client_log = self.faft_client.system.dump_log(True)
781 client_log_file = os.path.join(self.resultsdir, 'faft_client.log')
782 with open(client_log_file, 'w') as f:
783 f.write(client_log)
784
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700785 def _setup_gbb_flags(self):
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700786 """Setup the GBB flags for FAFT test."""
787 if self.faft_config.gbb_version < 1.1:
788 logging.info('Skip modifying GBB on versions older than 1.1.')
789 return
790
791 if self.check_setup_done('gbb_flags'):
792 return
793
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700794 logging.info('Set proper GBB flags for test.')
795 self.clear_set_gbb_flags(vboot.GBB_FLAG_DEV_SCREEN_SHORT_DELAY |
796 vboot.GBB_FLAG_FORCE_DEV_SWITCH_ON |
797 vboot.GBB_FLAG_FORCE_DEV_BOOT_USB |
Tom Wai-Hong Tam46ebbb12015-08-28 07:59:32 +0800798 vboot.GBB_FLAG_DISABLE_FW_ROLLBACK_CHECK |
799 vboot.GBB_FLAG_FORCE_DEV_BOOT_FASTBOOT_FULL_CAP,
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700800 vboot.GBB_FLAG_ENTER_TRIGGERS_TONORM |
801 vboot.GBB_FLAG_FAFT_KEY_OVERIDE)
802 self.mark_setup_done('gbb_flags')
803
804 def drop_backup_gbb_flags(self):
805 """Drops the backup GBB flags.
806
807 This can be used when a test intends to permanently change GBB flags.
808 """
809 self._backup_gbb_flags = None
810
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700811 def _restore_gbb_flags(self):
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700812 """Restore GBB flags to their original state."""
Tom Wai-Hong Tamfc0c7702015-09-12 03:43:53 +0800813 if self._backup_gbb_flags is None:
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700814 return
Tom Wai-Hong Tam2e3db4a2015-08-27 06:26:32 +0800815 # Setting up and restoring the GBB flags take a lot of time. For
816 # speed-up purpose, don't restore it.
817 logging.info('***')
818 logging.info('*** Please manually restore the original GBB flags to: '
819 '0x%x ***', self._backup_gbb_flags)
820 logging.info('***')
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700821 self.unmark_setup_done('gbb_flags')
822
823 def setup_tried_fwb(self, tried_fwb):
824 """Setup for fw B tried state.
825
826 It makes sure the system in the requested fw B tried state. If not, it
827 tries to do so.
828
829 @param tried_fwb: True if requested in tried_fwb=1;
830 False if tried_fwb=0.
831 """
832 if tried_fwb:
833 if not self.checkers.crossystem_checker({'tried_fwb': '1'}):
834 logging.info(
835 'Firmware is not booted with tried_fwb. Reboot into it.')
Yusuf Mohsinallyab1b5fc2014-05-08 12:46:10 -0700836 self.faft_client.system.set_try_fw_b()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700837 else:
838 if not self.checkers.crossystem_checker({'tried_fwb': '0'}):
839 logging.info(
840 'Firmware is booted with tried_fwb. Reboot to clear.')
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700841
842 def power_on(self):
843 """Switch DUT AC power on."""
844 self._client.power_on(self.power_control)
845
846 def power_off(self):
847 """Switch DUT AC power off."""
848 self._client.power_off(self.power_control)
849
850 def power_cycle(self):
851 """Power cycle DUT AC power."""
852 self._client.power_cycle(self.power_control)
853
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700854 def setup_rw_boot(self, section='a'):
855 """Make sure firmware is in RW-boot mode.
856
857 If the given firmware section is in RO-boot mode, turn off the RO-boot
858 flag and reboot DUT into RW-boot mode.
859
860 @param section: A firmware section, either 'a' or 'b'.
861 """
862 flags = self.faft_client.bios.get_preamble_flags(section)
863 if flags & vboot.PREAMBLE_USE_RO_NORMAL:
864 flags = flags ^ vboot.PREAMBLE_USE_RO_NORMAL
Yusuf Mohsinallyab1b5fc2014-05-08 12:46:10 -0700865 self.faft_client.bios.set_preamble_flags(section, flags)
Tom Wai-Hong Tam47776242015-05-07 02:45:32 +0800866 self.switcher.mode_aware_reboot()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700867
868 def setup_kernel(self, part):
869 """Setup for kernel test.
870
871 It makes sure both kernel A and B bootable and the current boot is
872 the requested kernel part.
873
874 @param part: A string of kernel partition number or 'a'/'b'.
875 """
876 self.ensure_kernel_boot(part)
877 logging.info('Checking the integrity of kernel B and rootfs B...')
878 if (self.faft_client.kernel.diff_a_b() or
879 not self.faft_client.rootfs.verify_rootfs('B')):
880 logging.info('Copying kernel and rootfs from A to B...')
881 self.copy_kernel_and_rootfs(from_part=part,
882 to_part=self.OTHER_KERNEL_MAP[part])
883 self.reset_and_prioritize_kernel(part)
884
885 def reset_and_prioritize_kernel(self, part):
886 """Make the requested partition highest priority.
887
888 This function also reset kerenl A and B to bootable.
889
890 @param part: A string of partition number to be prioritized.
891 """
892 root_dev = self.faft_client.system.get_root_dev()
893 # Reset kernel A and B to bootable.
894 self.faft_client.system.run_shell_command(
895 'cgpt add -i%s -P1 -S1 -T0 %s' % (self.KERNEL_MAP['a'], root_dev))
896 self.faft_client.system.run_shell_command(
897 'cgpt add -i%s -P1 -S1 -T0 %s' % (self.KERNEL_MAP['b'], root_dev))
898 # Set kernel part highest priority.
899 self.faft_client.system.run_shell_command('cgpt prioritize -i%s %s' %
900 (self.KERNEL_MAP[part], root_dev))
901
Yusuf Mohsinally1bacc962014-08-14 11:37:32 -0700902 def blocking_sync(self):
903 """Run a blocking sync command."""
904 # The double calls to sync fakes a blocking call
905 # since the first call returns before the flush
906 # is complete, but the second will wait for the
907 # first to finish.
908 self.faft_client.system.run_shell_command('sync')
909 self.faft_client.system.run_shell_command('sync')
910
Ryan Lin5bee6102014-09-16 13:17:02 -0700911 # sync only sends SYNCHRONIZE_CACHE but doesn't
Steve Fungb5752422015-01-09 16:45:32 -0800912 # check the status. For mmc devices, use `mmc
913 # status get` command to send an empty command to
914 # wait for the disk to be available again. For
915 # other devices, hdparm sends TUR to check if
Ryan Lin5bee6102014-09-16 13:17:02 -0700916 # a device is ready for transfer operation.
917 root_dev = self.faft_client.system.get_root_dev()
Steve Fungb5752422015-01-09 16:45:32 -0800918 if 'mmcblk' in root_dev:
919 self.faft_client.system.run_shell_command('mmc status get %s' %
920 root_dev)
921 else:
922 self.faft_client.system.run_shell_command('hdparm -f %s' % root_dev)
Yusuf Mohsinallyab1b5fc2014-05-08 12:46:10 -0700923
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700924 def sync_and_ec_reboot(self, flags=''):
925 """Request the client sync and do a EC triggered reboot.
926
927 @param flags: Optional, a space-separated string of flags passed to EC
928 reboot command, including:
929 default: EC soft reboot;
930 'hard': EC cold/hard reboot.
931 """
Yusuf Mohsinally1bacc962014-08-14 11:37:32 -0700932 self.blocking_sync()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700933 self.ec.reboot(flags)
934 time.sleep(self.faft_config.ec_boot_to_console)
935 self.check_lid_and_power_on()
936
Julius Werner18c4e162015-07-07 13:02:08 -0700937 def reboot_and_reset_tpm(self):
938 """Reboot into recovery mode, reset TPM, then reboot back to disk."""
939 self.switcher.reboot_to_mode(to_mode='rec')
940 self.faft_client.system.run_shell_command('chromeos-tpm-recovery')
Tom Wai-Hong Tama704f182015-05-06 06:12:55 +0800941 self.switcher.mode_aware_reboot()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700942
943 def full_power_off_and_on(self):
944 """Shutdown the device by pressing power button and power on again."""
Danny Chan101b0b22014-11-06 10:08:54 -0800945 boot_id = self.get_bootid()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700946 # Press power button to trigger Chrome OS normal shutdown process.
947 # We use a customized delay since the normal-press 1.2s is not enough.
David Hendricks25d703a2015-08-21 14:37:51 -0700948 self.servo.power_key(self.faft_config.hold_pwr_button_poweroff)
Danny Chan101b0b22014-11-06 10:08:54 -0800949 # device can take 44-51 seconds to restart,
950 # add buffer from the default timeout of 60 seconds.
Tom Wai-Hong Tam19bfb6e2015-08-20 06:05:36 +0800951 self.switcher.wait_for_client_offline(timeout=100, orig_boot_id=boot_id)
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700952 time.sleep(self.faft_config.shutdown)
953 # Short press power button to boot DUT again.
David Hendricks25d703a2015-08-21 14:37:51 -0700954 self.servo.power_key(self.faft_config.hold_pwr_button_poweron)
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700955
956 def check_lid_and_power_on(self):
957 """
958 On devices with EC software sync, system powers on after EC reboots if
959 lid is open. Otherwise, the EC shuts down CPU after about 3 seconds.
960 This method checks lid switch state and presses power button if
961 necessary.
962 """
963 if self.servo.get("lid_open") == "no":
964 time.sleep(self.faft_config.software_sync)
965 self.servo.power_short_press()
966
967 def _modify_usb_kernel(self, usb_dev, from_magic, to_magic):
968 """Modify the kernel header magic in USB stick.
969
970 The kernel header magic is the first 8-byte of kernel partition.
971 We modify it to make it fail on kernel verification check.
972
973 @param usb_dev: A string of USB stick path on the host, like '/dev/sdc'.
974 @param from_magic: A string of magic which we change it from.
975 @param to_magic: A string of magic which we change it to.
976 @raise TestError: if failed to change magic.
977 """
978 assert len(from_magic) == 8
979 assert len(to_magic) == 8
980 # USB image only contains one kernel.
981 kernel_part = self._join_part(usb_dev, self.KERNEL_MAP['a'])
982 read_cmd = "sudo dd if=%s bs=8 count=1 2>/dev/null" % kernel_part
983 current_magic = self.servo.system_output(read_cmd)
984 if current_magic == to_magic:
985 logging.info("The kernel magic is already %s.", current_magic)
986 return
987 if current_magic != from_magic:
988 raise error.TestError("Invalid kernel image on USB: wrong magic.")
989
990 logging.info('Modify the kernel magic in USB, from %s to %s.',
991 from_magic, to_magic)
992 write_cmd = ("echo -n '%s' | sudo dd of=%s oflag=sync conv=notrunc "
993 " 2>/dev/null" % (to_magic, kernel_part))
994 self.servo.system(write_cmd)
995
996 if self.servo.system_output(read_cmd) != to_magic:
997 raise error.TestError("Failed to write new magic.")
998
999 def corrupt_usb_kernel(self, usb_dev):
1000 """Corrupt USB kernel by modifying its magic from CHROMEOS to CORRUPTD.
1001
1002 @param usb_dev: A string of USB stick path on the host, like '/dev/sdc'.
1003 """
1004 self._modify_usb_kernel(usb_dev, self.CHROMEOS_MAGIC,
1005 self.CORRUPTED_MAGIC)
1006
1007 def restore_usb_kernel(self, usb_dev):
1008 """Restore USB kernel by modifying its magic from CORRUPTD to CHROMEOS.
1009
1010 @param usb_dev: A string of USB stick path on the host, like '/dev/sdc'.
1011 """
1012 self._modify_usb_kernel(usb_dev, self.CORRUPTED_MAGIC,
1013 self.CHROMEOS_MAGIC)
1014
1015 def _call_action(self, action_tuple, check_status=False):
1016 """Call the action function with/without arguments.
1017
1018 @param action_tuple: A function, or a tuple (function, args, error_msg),
1019 in which, args and error_msg are optional. args is
1020 either a value or a tuple if multiple arguments.
1021 This can also be a list containing multiple
1022 function or tuple. In this case, these actions are
1023 called in sequence.
1024 @param check_status: Check the return value of action function. If not
1025 succeed, raises a TestFail exception.
1026 @return: The result value of the action function.
1027 @raise TestError: An error when the action function is not callable.
1028 @raise TestFail: When check_status=True, action function not succeed.
1029 """
1030 if isinstance(action_tuple, list):
1031 return all([self._call_action(action, check_status=check_status)
1032 for action in action_tuple])
1033
1034 action = action_tuple
1035 args = ()
1036 error_msg = 'Not succeed'
1037 if isinstance(action_tuple, tuple):
1038 action = action_tuple[0]
1039 if len(action_tuple) >= 2:
1040 args = action_tuple[1]
1041 if not isinstance(args, tuple):
1042 args = (args,)
1043 if len(action_tuple) >= 3:
1044 error_msg = action_tuple[2]
1045
1046 if action is None:
1047 return
1048
1049 if not callable(action):
1050 raise error.TestError('action is not callable!')
1051
1052 info_msg = 'calling %s' % str(action)
1053 if args:
1054 info_msg += ' with args %s' % str(args)
1055 logging.info(info_msg)
1056 ret = action(*args)
1057
1058 if check_status and not ret:
1059 raise error.TestFail('%s: %s returning %s' %
1060 (error_msg, info_msg, str(ret)))
1061 return ret
1062
1063 def run_shutdown_process(self, shutdown_action, pre_power_action=None,
Aseda Aboagye795cf352016-12-12 16:35:17 -08001064 run_power_action=True, post_power_action=None,
1065 shutdown_timeout=None):
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001066 """Run shutdown_action(), which makes DUT shutdown, and power it on.
1067
1068 @param shutdown_action: function which makes DUT shutdown, like
1069 pressing power key.
1070 @param pre_power_action: function which is called before next power on.
Aseda Aboagye795cf352016-12-12 16:35:17 -08001071 @param run_power_action: power_key press by default, set to None to skip.
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001072 @param post_power_action: function which is called after next power on.
1073 @param shutdown_timeout: a timeout to confirm DUT shutdown.
1074 @raise TestFail: if the shutdown_action() failed to turn DUT off.
1075 """
1076 self._call_action(shutdown_action)
1077 logging.info('Wait to ensure DUT shut down...')
1078 try:
1079 if shutdown_timeout is None:
1080 shutdown_timeout = self.faft_config.shutdown_timeout
Tom Wai-Hong Tam19bfb6e2015-08-20 06:05:36 +08001081 self.switcher.wait_for_client(timeout=shutdown_timeout)
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001082 raise error.TestFail(
1083 'Should shut the device down after calling %s.' %
1084 str(shutdown_action))
1085 except ConnectionError:
1086 logging.info(
1087 'DUT is surely shutdown. We are going to power it on again...')
1088
1089 if pre_power_action:
1090 self._call_action(pre_power_action)
Danny Chana7db9b52015-12-04 16:12:48 -08001091 if run_power_action:
1092 self.servo.power_key(self.faft_config.hold_pwr_button_poweron)
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001093 if post_power_action:
1094 self._call_action(post_power_action)
1095
Yusuf Mohsinallyab1b5fc2014-05-08 12:46:10 -07001096 def get_bootid(self, retry=3):
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001097 """
Yusuf Mohsinallyab1b5fc2014-05-08 12:46:10 -07001098 Return the bootid.
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001099 """
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001100 boot_id = None
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001101 while retry:
1102 try:
1103 boot_id = self._client.get_boot_id()
1104 break
1105 except error.AutoservRunError:
1106 retry -= 1
1107 if retry:
1108 logging.info('Retry to get boot_id...')
1109 else:
1110 logging.warning('Failed to get boot_id.')
1111 logging.info('boot_id: %s', boot_id)
Yusuf Mohsinallyab1b5fc2014-05-08 12:46:10 -07001112 return boot_id
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001113
Yusuf Mohsinallyab1b5fc2014-05-08 12:46:10 -07001114 def check_state(self, func):
1115 """
1116 Wrapper around _call_action with check_status set to True. This is a
1117 helper function to be used by tests and is currently implemented by
1118 calling _call_action with check_status=True.
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001119
Yusuf Mohsinallyab1b5fc2014-05-08 12:46:10 -07001120 TODO: This function's arguments need to be made more stringent. And
1121 its functionality should be moved over to check functions directly in
1122 the future.
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001123
Yusuf Mohsinallyab1b5fc2014-05-08 12:46:10 -07001124 @param func: A function, or a tuple (function, args, error_msg),
1125 in which, args and error_msg are optional. args is
1126 either a value or a tuple if multiple arguments.
1127 This can also be a list containing multiple
1128 function or tuple. In this case, these actions are
1129 called in sequence.
1130 @return: The result value of the action function.
1131 @raise TestFail: If the function does notsucceed.
1132 """
1133 logging.info("-[FAFT]-[ start stepstate_checker ]----------")
1134 self._call_action(func, check_status=True)
1135 logging.info("-[FAFT]-[ end state_checker ]----------------")
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001136
1137 def get_current_firmware_sha(self):
1138 """Get current firmware sha of body and vblock.
1139
1140 @return: Current firmware sha follows the order (
1141 vblock_a_sha, body_a_sha, vblock_b_sha, body_b_sha)
1142 """
1143 current_firmware_sha = (self.faft_client.bios.get_sig_sha('a'),
1144 self.faft_client.bios.get_body_sha('a'),
1145 self.faft_client.bios.get_sig_sha('b'),
1146 self.faft_client.bios.get_body_sha('b'))
1147 if not all(current_firmware_sha):
1148 raise error.TestError('Failed to get firmware sha.')
1149 return current_firmware_sha
1150
1151 def is_firmware_changed(self):
1152 """Check if the current firmware changed, by comparing its SHA.
1153
1154 @return: True if it is changed, otherwise Flase.
1155 """
1156 # Device may not be rebooted after test.
1157 self.faft_client.bios.reload()
1158
1159 current_sha = self.get_current_firmware_sha()
1160
1161 if current_sha == self._backup_firmware_sha:
1162 return False
1163 else:
1164 corrupt_VBOOTA = (current_sha[0] != self._backup_firmware_sha[0])
1165 corrupt_FVMAIN = (current_sha[1] != self._backup_firmware_sha[1])
1166 corrupt_VBOOTB = (current_sha[2] != self._backup_firmware_sha[2])
1167 corrupt_FVMAINB = (current_sha[3] != self._backup_firmware_sha[3])
1168 logging.info("Firmware changed:")
1169 logging.info('VBOOTA is changed: %s', corrupt_VBOOTA)
1170 logging.info('VBOOTB is changed: %s', corrupt_VBOOTB)
1171 logging.info('FVMAIN is changed: %s', corrupt_FVMAIN)
1172 logging.info('FVMAINB is changed: %s', corrupt_FVMAINB)
1173 return True
1174
1175 def backup_firmware(self, suffix='.original'):
1176 """Backup firmware to file, and then send it to host.
1177
1178 @param suffix: a string appended to backup file name
1179 """
1180 remote_temp_dir = self.faft_client.system.create_temp_dir()
Tom Wai-Hong Tame1d5e662015-08-26 05:29:54 +08001181 remote_bios_path = os.path.join(remote_temp_dir, 'bios')
1182 self.faft_client.bios.dump_whole(remote_bios_path)
1183 self._client.get_file(remote_bios_path,
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001184 os.path.join(self.resultsdir, 'bios' + suffix))
Tom Wai-Hong Tame1d5e662015-08-26 05:29:54 +08001185 self._client.run('rm -rf %s' % remote_temp_dir)
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001186 logging.info('Backup firmware stored in %s with suffix %s',
1187 self.resultsdir, suffix)
1188
Tom Wai-Hong Tame1d5e662015-08-26 05:29:54 +08001189 self._backup_firmware_sha = self.get_current_firmware_sha()
1190
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001191 def is_firmware_saved(self):
1192 """Check if a firmware saved (called backup_firmware before).
1193
1194 @return: True if the firmware is backuped; otherwise False.
1195 """
1196 return self._backup_firmware_sha != ()
1197
1198 def clear_saved_firmware(self):
1199 """Clear the firmware saved by the method backup_firmware."""
1200 self._backup_firmware_sha = ()
1201
1202 def restore_firmware(self, suffix='.original'):
1203 """Restore firmware from host in resultsdir.
1204
1205 @param suffix: a string appended to backup file name
1206 """
1207 if not self.is_firmware_changed():
1208 return
1209
1210 # Backup current corrupted firmware.
1211 self.backup_firmware(suffix='.corrupt')
1212
1213 # Restore firmware.
1214 remote_temp_dir = self.faft_client.system.create_temp_dir()
1215 self._client.send_file(os.path.join(self.resultsdir, 'bios' + suffix),
1216 os.path.join(remote_temp_dir, 'bios'))
1217
1218 self.faft_client.bios.write_whole(
1219 os.path.join(remote_temp_dir, 'bios'))
Tom Wai-Hong Tama704f182015-05-06 06:12:55 +08001220 self.switcher.mode_aware_reboot()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001221 logging.info('Successfully restore firmware.')
1222
1223 def setup_firmwareupdate_shellball(self, shellball=None):
Tom Wai-Hong Tam28d82a82016-03-04 06:44:43 +08001224 """Setup a shellball to use in firmware update test.
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001225
1226 Check if there is a given shellball, and it is a shell script. Then,
Tom Wai-Hong Tam28d82a82016-03-04 06:44:43 +08001227 send it to the remote host. Otherwise, use the
1228 /usr/sbin/chromeos-firmwareupdate in the image and replace its inside
1229 BIOS and EC images with the active firmware images.
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001230
1231 @param shellball: path of a shellball or default to None.
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001232 """
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001233 if shellball:
1234 # Determine the firmware file is a shellball or a raw binary.
1235 is_shellball = (utils.system_output("file %s" % shellball).find(
1236 "shell script") != -1)
1237 if is_shellball:
1238 logging.info('Device will update firmware with shellball %s',
1239 shellball)
Tom Wai-Hong Tam28d82a82016-03-04 06:44:43 +08001240 temp_path = self.faft_client.updater.get_temp_path()
1241 working_shellball = os.path.join(temp_path,
1242 'chromeos-firmwareupdate')
1243 self._client.send_file(shellball, working_shellball)
1244 self.faft_client.updater.extract_shellball()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001245 else:
1246 raise error.TestFail(
1247 'The given shellball is not a shell script.')
Tom Wai-Hong Tam28d82a82016-03-04 06:44:43 +08001248 else:
1249 logging.info('No shellball given, use the original shellball and '
1250 'replace its BIOS and EC images.')
1251 work_path = self.faft_client.updater.get_work_path()
1252 bios_in_work_path = os.path.join(work_path, 'bios.bin')
1253 ec_in_work_path = os.path.join(work_path, 'ec.bin')
1254 self.faft_client.bios.dump_whole(bios_in_work_path)
Tom Wai-Hong Tam133b00c2016-06-22 04:06:19 +08001255 if self.faft_config.chrome_ec:
1256 self.faft_client.ec.dump_firmware(ec_in_work_path)
Tom Wai-Hong Tam28d82a82016-03-04 06:44:43 +08001257 self.faft_client.updater.repack_shellball()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001258
1259 def is_kernel_changed(self):
1260 """Check if the current kernel is changed, by comparing its SHA1 hash.
1261
1262 @return: True if it is changed; otherwise, False.
1263 """
1264 changed = False
1265 for p in ('A', 'B'):
1266 backup_sha = self._backup_kernel_sha.get(p, None)
1267 current_sha = self.faft_client.kernel.get_sha(p)
1268 if backup_sha != current_sha:
1269 changed = True
1270 logging.info('Kernel %s is changed', p)
1271 return changed
1272
1273 def backup_kernel(self, suffix='.original'):
1274 """Backup kernel to files, and the send them to host.
1275
1276 @param suffix: a string appended to backup file name.
1277 """
1278 remote_temp_dir = self.faft_client.system.create_temp_dir()
1279 for p in ('A', 'B'):
1280 remote_path = os.path.join(remote_temp_dir, 'kernel_%s' % p)
1281 self.faft_client.kernel.dump(p, remote_path)
1282 self._client.get_file(
1283 remote_path,
1284 os.path.join(self.resultsdir, 'kernel_%s%s' % (p, suffix)))
1285 self._backup_kernel_sha[p] = self.faft_client.kernel.get_sha(p)
1286 logging.info('Backup kernel stored in %s with suffix %s',
1287 self.resultsdir, suffix)
1288
1289 def is_kernel_saved(self):
1290 """Check if kernel images are saved (backup_kernel called before).
1291
1292 @return: True if the kernel is saved; otherwise, False.
1293 """
1294 return len(self._backup_kernel_sha) != 0
1295
1296 def clear_saved_kernel(self):
1297 """Clear the kernel saved by backup_kernel()."""
1298 self._backup_kernel_sha = dict()
1299
1300 def restore_kernel(self, suffix='.original'):
1301 """Restore kernel from host in resultsdir.
1302
1303 @param suffix: a string appended to backup file name.
1304 """
1305 if not self.is_kernel_changed():
1306 return
1307
1308 # Backup current corrupted kernel.
1309 self.backup_kernel(suffix='.corrupt')
1310
1311 # Restore kernel.
1312 remote_temp_dir = self.faft_client.system.create_temp_dir()
1313 for p in ('A', 'B'):
1314 remote_path = os.path.join(remote_temp_dir, 'kernel_%s' % p)
1315 self._client.send_file(
1316 os.path.join(self.resultsdir, 'kernel_%s%s' % (p, suffix)),
1317 remote_path)
1318 self.faft_client.kernel.write(p, remote_path)
1319
Tom Wai-Hong Tama704f182015-05-06 06:12:55 +08001320 self.switcher.mode_aware_reboot()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001321 logging.info('Successfully restored kernel.')
1322
1323 def backup_cgpt_attributes(self):
1324 """Backup CGPT partition table attributes."""
1325 self._backup_cgpt_attr = self.faft_client.cgpt.get_attributes()
1326
1327 def restore_cgpt_attributes(self):
1328 """Restore CGPT partition table attributes."""
1329 current_table = self.faft_client.cgpt.get_attributes()
1330 if current_table == self._backup_cgpt_attr:
1331 return
1332 logging.info('CGPT table is changed. Original: %r. Current: %r.',
1333 self._backup_cgpt_attr,
1334 current_table)
1335 self.faft_client.cgpt.set_attributes(self._backup_cgpt_attr)
1336
Tom Wai-Hong Tama704f182015-05-06 06:12:55 +08001337 self.switcher.mode_aware_reboot()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001338 logging.info('Successfully restored CGPT table.')
Shelley Chen3edea982014-12-30 14:54:21 -08001339
1340 def try_fwb(self, count=0):
1341 """set to try booting FWB count # times
1342
1343 Wrapper to set fwb_tries for vboot1 and fw_try_count,fw_try_next for
1344 vboot2
1345
1346 @param count: an integer specifying value to program into
1347 fwb_tries(vb1)/fw_try_next(vb2)
1348 """
1349 if self.fw_vboot2:
1350 self.faft_client.system.set_fw_try_next('B', count)
1351 else:
1352 # vboot1: we need to boot into fwb at least once
1353 if not count:
1354 count = count + 1
1355 self.faft_client.system.set_try_fw_b(count)
1356