blob: 6de78437f4482f72d97bee7558f295e362bfa417 [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
Shelley Chen530490a2015-10-21 10:43:06 -070014from threading import Timer
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -070015from autotest_lib.client.bin import utils
16from autotest_lib.client.common_lib import error
J. Richard Barnettecab6be32014-07-17 13:07:39 -070017from autotest_lib.server import test
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -070018from autotest_lib.server.cros import vboot_constants as vboot
19from autotest_lib.server.cros.faft.config.config import Config as FAFTConfig
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -070020from autotest_lib.server.cros.faft.rpc_proxy import RPCProxy
Tom Wai-Hong Tamed4d67b2015-05-20 05:20:00 +080021from autotest_lib.server.cros.faft.utils import mode_switcher
J. Richard Barnettea57ff842014-06-05 10:00:31 -070022from autotest_lib.server.cros.faft.utils.faft_checkers import FAFTCheckers
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -070023from autotest_lib.server.cros.servo import chrome_ec
Scottfe06ed82015-11-05 17:15:01 -080024from autotest_lib.server.cros.servo import chrome_usbpd
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -070025
Tom Wai-Hong Tam19bfb6e2015-08-20 06:05:36 +080026ConnectionError = mode_switcher.ConnectionError
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -070027
28
J. Richard Barnettecab6be32014-07-17 13:07:39 -070029class FAFTBase(test.test):
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -070030 """The base class of FAFT classes.
31
32 It launches the FAFTClient on DUT, such that the test can access its
33 firmware functions and interfaces. It also provides some methods to
34 handle the reboot mechanism, in order to ensure FAFTClient is still
35 connected after reboot.
36 """
37 def initialize(self, host):
38 """Create a FAFTClient object and install the dependency."""
J. Richard Barnettecab6be32014-07-17 13:07:39 -070039 self.servo = host.servo
40 self.servo.initialize_dut()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -070041 self._client = host
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -070042 self.faft_client = RPCProxy(host)
Duncan Laurie10eb6182014-10-07 15:39:05 -070043 self.lockfile = '/var/tmp/faft/lock'
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -070044
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -070045
46class FirmwareTest(FAFTBase):
47 """
Yusuf Mohsinallyab1b5fc2014-05-08 12:46:10 -070048 Base class that sets up helper objects/functions for firmware tests.
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -070049
Yusuf Mohsinallyab1b5fc2014-05-08 12:46:10 -070050 TODO: add documentaion as the FAFT rework progresses.
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -070051 """
52 version = 1
53
54 # Mapping of partition number of kernel and rootfs.
55 KERNEL_MAP = {'a':'2', 'b':'4', '2':'2', '4':'4', '3':'2', '5':'4'}
56 ROOTFS_MAP = {'a':'3', 'b':'5', '2':'3', '4':'5', '3':'3', '5':'5'}
57 OTHER_KERNEL_MAP = {'a':'4', 'b':'2', '2':'4', '4':'2', '3':'4', '5':'2'}
58 OTHER_ROOTFS_MAP = {'a':'5', 'b':'3', '2':'5', '4':'3', '3':'5', '5':'3'}
59
60 CHROMEOS_MAGIC = "CHROMEOS"
61 CORRUPTED_MAGIC = "CORRUPTD"
62
Shelley Chen530490a2015-10-21 10:43:06 -070063 # Delay for waiting client to return before EC suspend
64 EC_SUSPEND_DELAY = 5
65
66 # Delay between EC suspend and wake
67 WAKE_DELAY = 10
68
69 # Delay between closing and opening lid
70 LID_DELAY = 1
71
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -070072 _SERVOD_LOG = '/var/log/servod.log'
73
74 _ROOTFS_PARTITION_NUMBER = 3
75
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -070076 _backup_firmware_sha = ()
77 _backup_kernel_sha = dict()
78 _backup_cgpt_attr = dict()
79 _backup_gbb_flags = None
80 _backup_dev_mode = None
81
82 # Class level variable, keep track the states of one time setup.
83 # This variable is preserved across tests which inherit this class.
84 _global_setup_done = {
85 'gbb_flags': False,
86 'reimage': False,
87 'usb_check': False,
88 }
89
90 @classmethod
91 def check_setup_done(cls, label):
92 """Check if the given setup is done.
93
94 @param label: The label of the setup.
95 """
96 return cls._global_setup_done[label]
97
98 @classmethod
99 def mark_setup_done(cls, label):
100 """Mark the given setup done.
101
102 @param label: The label of the setup.
103 """
104 cls._global_setup_done[label] = True
105
106 @classmethod
107 def unmark_setup_done(cls, label):
108 """Mark the given setup not done.
109
110 @param label: The label of the setup.
111 """
112 cls._global_setup_done[label] = False
113
114 def initialize(self, host, cmdline_args, ec_wp=None):
115 super(FirmwareTest, self).initialize(host)
116 self.run_id = str(uuid.uuid4())
117 logging.info('FirmwareTest initialize begin (id=%s)', self.run_id)
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700118 # Parse arguments from command line
119 args = {}
120 self.power_control = host.POWER_CONTROL_RPM
121 for arg in cmdline_args:
122 match = re.search("^(\w+)=(.+)", arg)
123 if match:
124 args[match.group(1)] = match.group(2)
125 if 'power_control' in args:
126 self.power_control = args['power_control']
127 if self.power_control not in host.POWER_CONTROL_VALID_ARGS:
128 raise error.TestError('Valid values for --args=power_control '
129 'are %s. But you entered wrong argument '
130 'as "%s".'
131 % (host.POWER_CONTROL_VALID_ARGS,
132 self.power_control))
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700133
134 self.faft_config = FAFTConfig(
135 self.faft_client.system.get_platform_name())
Tom Wai-Hong Tam0cc9a4f2015-05-02 05:12:39 +0800136 self.checkers = FAFTCheckers(self)
Tom Wai-Hong Tamed4d67b2015-05-20 05:20:00 +0800137 self.switcher = mode_switcher.create_mode_switcher(self)
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700138
139 if self.faft_config.chrome_ec:
140 self.ec = chrome_ec.ChromeEC(self.servo)
Scottfe06ed82015-11-05 17:15:01 -0800141 # Check for presence of a USBPD console
142 if self.faft_config.chrome_usbpd:
143 self.usbpd = chrome_usbpd.ChromeUSBPD(self.servo)
144 elif self.faft_config.chrome_ec:
145 # If no separate USBPD console, then PD exists on EC console
146 self.usbpd = self.ec
147 # Get plankton console
Scott07a848f2016-01-12 15:04:52 -0800148 self.plankton = host.plankton
149 self.plankton_host = host._plankton_host
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700150
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700151 self._setup_uart_capture()
152 self._setup_servo_log()
153 self._record_system_info()
Daisuke Nojiri682a6d62014-11-21 09:59:32 -0800154 self.fw_vboot2 = self.faft_client.system.get_fw_vboot2()
155 logging.info('vboot version: %d', 2 if self.fw_vboot2 else 1)
156 if self.fw_vboot2:
157 self.faft_client.system.set_fw_try_next('A')
158 if self.faft_client.system.get_crossystem_value('mainfw_act') == 'B':
159 logging.info('mainfw_act is B. rebooting to set it A')
Tom Wai-Hong Tam47776242015-05-07 02:45:32 +0800160 self.switcher.mode_aware_reboot()
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700161 self._setup_gbb_flags()
162 self._stop_service('update-engine')
Duncan Laurie10eb6182014-10-07 15:39:05 -0700163 self._create_faft_lockfile()
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700164 self._setup_ec_write_protect(ec_wp)
Yusuf Mohsinally1b7a48b2014-05-12 19:25:35 -0700165 # See chromium:239034 regarding needing this sync.
Yusuf Mohsinally1bacc962014-08-14 11:37:32 -0700166 self.blocking_sync()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700167 logging.info('FirmwareTest initialize done (id=%s)', self.run_id)
168
169 def cleanup(self):
170 """Autotest cleanup function."""
171 # Unset state checker in case it's set by subclass
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700172 logging.info('FirmwareTest cleaning up (id=%s)', self.run_id)
173 try:
174 self.faft_client.system.is_available()
175 except:
176 # Remote is not responding. Revive DUT so that subsequent tests
177 # don't fail.
178 self._restore_routine_from_timeout()
Tom Wai-Hong Tam0cc9a4f2015-05-02 05:12:39 +0800179 self.switcher.restore_mode()
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700180 self._restore_ec_write_protect()
181 self._restore_gbb_flags()
182 self._start_service('update-engine')
Andrey Pronin3a1d5092016-07-12 15:55:34 -0700183 # FAFT client stopped tcsd or trunksd at startup. Restart it.
Andrey Pronin96a9dec2016-07-20 15:34:00 -0700184 # Only one is present, so start the one known to initctl.
185 status = self.faft_client.system.run_shell_command_get_output(
186 'initctl status trunksd') or ['']
187 if status[0].startswith('trunksd'):
188 self._start_service('trunksd')
189 else:
190 self._start_service('tcsd')
Duncan Laurie10eb6182014-10-07 15:39:05 -0700191 self._remove_faft_lockfile()
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700192 self._record_servo_log()
193 self._record_faft_client_log()
194 self._cleanup_uart_capture()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700195 super(FirmwareTest, self).cleanup()
196 logging.info('FirmwareTest cleanup done (id=%s)', self.run_id)
197
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700198 def _record_system_info(self):
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700199 """Record some critical system info to the attr keyval.
200
Christopher Wiley004a8cd2015-05-19 11:49:13 -0700201 This info is used by generate_test_report later.
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700202 """
Tom Wai-Hong Tam9a10b9b2015-09-28 16:29:28 +0800203 system_info = {
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700204 'hwid': self.faft_client.system.get_crossystem_value('hwid'),
Tom Wai-Hong Tam32075c12016-01-20 07:09:47 +0800205 'ec_version': self.faft_client.ec.get_version(),
206 'ro_fwid': self.faft_client.system.get_crossystem_value('ro_fwid'),
207 'rw_fwid': self.faft_client.system.get_crossystem_value('fwid'),
Tom Wai-Hong Tam9a10b9b2015-09-28 16:29:28 +0800208 'servod_version': self._client._servo_host.run(
209 'servod --version').stdout.strip(),
210 }
211 logging.info('System info:\n' + pprint.pformat(system_info))
212 self.write_attr_keyval(system_info)
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700213
214 def invalidate_firmware_setup(self):
215 """Invalidate all firmware related setup state.
216
217 This method is called when the firmware is re-flashed. It resets all
218 firmware related setup states so that the next test setup properly
219 again.
220 """
221 self.unmark_setup_done('gbb_flags')
222
223 def _retrieve_recovery_reason_from_trap(self):
224 """Try to retrieve the recovery reason from a trapped recovery screen.
225
226 @return: The recovery_reason, 0 if any error.
227 """
228 recovery_reason = 0
229 logging.info('Try to retrieve recovery reason...')
230 if self.servo.get_usbkey_direction() == 'dut':
Tom Wai-Hong Tam04302882015-05-14 06:08:34 +0800231 self.switcher.bypass_rec_mode()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700232 else:
233 self.servo.switch_usbkey('dut')
234
235 try:
Tom Wai-Hong Tam19bfb6e2015-08-20 06:05:36 +0800236 self.switcher.wait_for_client()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700237 lines = self.faft_client.system.run_shell_command_get_output(
238 'crossystem recovery_reason')
239 recovery_reason = int(lines[0])
240 logging.info('Got the recovery reason %d.', recovery_reason)
241 except ConnectionError:
242 logging.error('Failed to get the recovery reason due to connection '
243 'error.')
244 return recovery_reason
245
246 def _reset_client(self):
247 """Reset client to a workable state.
248
249 This method is called when the client is not responsive. It may be
250 caused by the following cases:
251 - halt on a firmware screen without timeout, e.g. REC_INSERT screen;
252 - corrupted firmware;
253 - corrutped OS image.
254 """
255 # DUT may halt on a firmware screen. Try cold reboot.
256 logging.info('Try cold reboot...')
Tom Wai-Hong Tama704f182015-05-06 06:12:55 +0800257 self.switcher.mode_aware_reboot(reboot_type='cold',
258 sync_before_boot=False,
259 wait_for_dut_up=False)
Tom Wai-Hong Tam19bfb6e2015-08-20 06:05:36 +0800260 self.switcher.wait_for_client_offline()
Tom Wai-Hong Tam04302882015-05-14 06:08:34 +0800261 self.switcher.bypass_dev_mode()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700262 try:
Tom Wai-Hong Tam19bfb6e2015-08-20 06:05:36 +0800263 self.switcher.wait_for_client()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700264 return
265 except ConnectionError:
266 logging.warn('Cold reboot doesn\'t help, still connection error.')
267
268 # DUT may be broken by a corrupted firmware. Restore firmware.
269 # We assume the recovery boot still works fine. Since the recovery
270 # code is in RO region and all FAFT tests don't change the RO region
271 # except GBB.
272 if self.is_firmware_saved():
273 self._ensure_client_in_recovery()
274 logging.info('Try restore the original firmware...')
275 if self.is_firmware_changed():
276 try:
277 self.restore_firmware()
278 return
279 except ConnectionError:
280 logging.warn('Restoring firmware doesn\'t help, still '
281 'connection error.')
282
283 # Perhaps it's kernel that's broken. Let's try restoring it.
284 if self.is_kernel_saved():
285 self._ensure_client_in_recovery()
286 logging.info('Try restore the original kernel...')
287 if self.is_kernel_changed():
288 try:
289 self.restore_kernel()
290 return
291 except ConnectionError:
292 logging.warn('Restoring kernel doesn\'t help, still '
293 'connection error.')
294
295 # DUT may be broken by a corrupted OS image. Restore OS image.
296 self._ensure_client_in_recovery()
297 logging.info('Try restore the OS image...')
298 self.faft_client.system.run_shell_command('chromeos-install --yes')
Tom Wai-Hong Tama704f182015-05-06 06:12:55 +0800299 self.switcher.mode_aware_reboot(wait_for_dut_up=False)
Tom Wai-Hong Tam19bfb6e2015-08-20 06:05:36 +0800300 self.switcher.wait_for_client_offline()
Tom Wai-Hong Tam04302882015-05-14 06:08:34 +0800301 self.switcher.bypass_dev_mode()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700302 try:
Tom Wai-Hong Tam19bfb6e2015-08-20 06:05:36 +0800303 self.switcher.wait_for_client()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700304 logging.info('Successfully restore OS image.')
305 return
306 except ConnectionError:
307 logging.warn('Restoring OS image doesn\'t help, still connection '
308 'error.')
309
310 def _ensure_client_in_recovery(self):
311 """Ensure client in recovery boot; reboot into it if necessary.
312
313 @raise TestError: if failed to boot the USB image.
314 """
315 logging.info('Try boot into USB image...')
Tom Wai-Hong Tamd7a0d052015-05-14 02:18:23 +0800316 self.switcher.reboot_to_mode(to_mode='rec', sync_before_boot=False,
317 wait_for_dut_up=False)
Tom Wai-Hong Tamf2de4de2015-05-02 02:48:08 +0800318 self.servo.switch_usbkey('host')
Tom Wai-Hong Tam04302882015-05-14 06:08:34 +0800319 self.switcher.bypass_rec_mode()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700320 try:
Tom Wai-Hong Tam19bfb6e2015-08-20 06:05:36 +0800321 self.switcher.wait_for_client()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700322 except ConnectionError:
323 raise error.TestError('Failed to boot the USB image.')
324
Yusuf Mohsinally64ee3a72014-06-26 10:24:27 -0700325 def _restore_routine_from_timeout(self):
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700326 """A routine to try to restore the system from a timeout error.
327
328 This method is called when FAFT failed to connect DUT after reboot.
329
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700330 @raise TestFail: This exception is already raised, with a decription
331 why it failed.
332 """
333 # DUT is disconnected. Capture the UART output for debug.
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700334 self._record_uart_capture()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700335
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700336 # TODO(waihong@chromium.org): Implement replugging the Ethernet to
337 # identify if it is a network flaky.
338
339 recovery_reason = self._retrieve_recovery_reason_from_trap()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700340
341 # Reset client to a workable state.
342 self._reset_client()
343
344 # Raise the proper TestFail exception.
Yusuf Mohsinally64ee3a72014-06-26 10:24:27 -0700345 if recovery_reason:
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700346 raise error.TestFail('Trapped in the recovery screen (reason: %d) '
347 'and timed out' % recovery_reason)
348 else:
349 raise error.TestFail('Timed out waiting for DUT reboot')
350
Julius Werner18c4e162015-07-07 13:02:08 -0700351 def assert_test_image_in_usb_disk(self, usb_dev=None):
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700352 """Assert an USB disk plugged-in on servo and a test image inside.
353
354 @param usb_dev: A string of USB stick path on the host, like '/dev/sdc'.
355 If None, it is detected automatically.
Julius Werner18c4e162015-07-07 13:02:08 -0700356 @raise TestError: if USB disk not detected or not a test image.
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700357 """
358 if self.check_setup_done('usb_check'):
359 return
360 if usb_dev:
361 assert self.servo.get_usbkey_direction() == 'host'
362 else:
363 self.servo.switch_usbkey('host')
364 usb_dev = self.servo.probe_host_usb_dev()
365 if not usb_dev:
366 raise error.TestError(
367 'An USB disk should be plugged in the servo board.')
368
369 rootfs = '%s%s' % (usb_dev, self._ROOTFS_PARTITION_NUMBER)
370 logging.info('usb dev is %s', usb_dev)
371 tmpd = self.servo.system_output('mktemp -d -t usbcheck.XXXX')
372 self.servo.system('mount -o ro %s %s' % (rootfs, tmpd))
373
Julius Wernerdc535df2015-02-26 16:42:38 -0800374 try:
Julius Werner18c4e162015-07-07 13:02:08 -0700375 usb_lsb = self.servo.system_output('cat %s' %
376 os.path.join(tmpd, 'etc/lsb-release'))
377 logging.debug('Dumping lsb-release on USB stick:\n%s', usb_lsb)
378 dut_lsb = '\n'.join(self.faft_client.system.
379 run_shell_command_get_output('cat /etc/lsb-release'))
380 logging.debug('Dumping lsb-release on DUT:\n%s', dut_lsb)
Julius Wernere6adca42015-08-13 11:10:59 -0700381 if not re.search(r'RELEASE_TRACK=.*test', usb_lsb):
Julius Werner18c4e162015-07-07 13:02:08 -0700382 raise error.TestError('USB stick in servo is no test image')
383 usb_board = re.search(r'BOARD=(.*)', usb_lsb).group(1)
384 dut_board = re.search(r'BOARD=(.*)', dut_lsb).group(1)
385 if usb_board != dut_board:
386 raise error.TestError('USB stick in servo contains a %s '
387 'image, but DUT is a %s' % (usb_board, dut_board))
Julius Wernerdc535df2015-02-26 16:42:38 -0800388 finally:
389 for cmd in ('umount %s' % rootfs, 'sync', 'rm -rf %s' % tmpd):
390 self.servo.system(cmd)
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700391
392 self.mark_setup_done('usb_check')
393
Julius Werner18c4e162015-07-07 13:02:08 -0700394 def setup_usbkey(self, usbkey, host=None):
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700395 """Setup the USB disk for the test.
396
397 It checks the setup of USB disk and a valid ChromeOS test image inside.
398 It also muxes the USB disk to either the host or DUT by request.
399
400 @param usbkey: True if the USB disk is required for the test, False if
401 not required.
402 @param host: Optional, True to mux the USB disk to host, False to mux it
403 to DUT, default to do nothing.
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700404 """
405 if usbkey:
Julius Werner18c4e162015-07-07 13:02:08 -0700406 self.assert_test_image_in_usb_disk()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700407 elif host is None:
408 # USB disk is not required for the test. Better to mux it to host.
409 host = True
410
411 if host is True:
412 self.servo.switch_usbkey('host')
413 elif host is False:
414 self.servo.switch_usbkey('dut')
415
416 def get_usbdisk_path_on_dut(self):
417 """Get the path of the USB disk device plugged-in the servo on DUT.
418
419 Returns:
420 A string representing USB disk path, like '/dev/sdb', or None if
421 no USB disk is found.
422 """
423 cmd = 'ls -d /dev/s*[a-z]'
424 original_value = self.servo.get_usbkey_direction()
425
426 # Make the dut unable to see the USB disk.
427 self.servo.switch_usbkey('off')
428 no_usb_set = set(
429 self.faft_client.system.run_shell_command_get_output(cmd))
430
431 # Make the dut able to see the USB disk.
432 self.servo.switch_usbkey('dut')
Tom Wai-Hong Tamb0314a02015-05-20 05:25:22 +0800433 time.sleep(self.faft_config.usb_plug)
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700434 has_usb_set = set(
435 self.faft_client.system.run_shell_command_get_output(cmd))
436
437 # Back to its original value.
438 if original_value != self.servo.get_usbkey_direction():
439 self.servo.switch_usbkey(original_value)
440
441 diff_set = has_usb_set - no_usb_set
442 if len(diff_set) == 1:
443 return diff_set.pop()
444 else:
445 return None
446
Duncan Laurie10eb6182014-10-07 15:39:05 -0700447 def _create_faft_lockfile(self):
448 """Creates the FAFT lockfile."""
449 logging.info('Creating FAFT lockfile...')
450 command = 'touch %s' % (self.lockfile)
451 self.faft_client.system.run_shell_command(command)
452
453 def _remove_faft_lockfile(self):
454 """Removes the FAFT lockfile."""
455 logging.info('Removing FAFT lockfile...')
456 command = 'rm -f %s' % (self.lockfile)
457 self.faft_client.system.run_shell_command(command)
458
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700459 def _stop_service(self, service):
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700460 """Stops a upstart service on the client.
461
462 @param service: The name of the upstart service.
463 """
464 logging.info('Stopping %s...', service)
465 command = 'status %s | grep stop || stop %s' % (service, service)
466 self.faft_client.system.run_shell_command(command)
467
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700468 def _start_service(self, service):
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700469 """Starts a upstart service on the client.
470
471 @param service: The name of the upstart service.
472 """
473 logging.info('Starting %s...', service)
474 command = 'status %s | grep start || start %s' % (service, service)
475 self.faft_client.system.run_shell_command(command)
476
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700477 def clear_set_gbb_flags(self, clear_mask, set_mask):
478 """Clear and set the GBB flags in the current flashrom.
479
480 @param clear_mask: A mask of flags to be cleared.
481 @param set_mask: A mask of flags to be set.
482 """
483 gbb_flags = self.faft_client.bios.get_gbb_flags()
484 new_flags = gbb_flags & ctypes.c_uint32(~clear_mask).value | set_mask
Tom Wai-Hong Tamfc0c7702015-09-12 03:43:53 +0800485 if new_flags != gbb_flags:
486 self._backup_gbb_flags = gbb_flags
487 logging.info('Changing GBB flags from 0x%x to 0x%x.',
488 gbb_flags, new_flags)
489 self.faft_client.bios.set_gbb_flags(new_flags)
490 # If changing FORCE_DEV_SWITCH_ON flag, reboot to get a clear state
491 if ((gbb_flags ^ new_flags) & vboot.GBB_FLAG_FORCE_DEV_SWITCH_ON):
492 self.switcher.mode_aware_reboot()
493 else:
494 logging.info('Current GBB flags look good for test: 0x%x.',
495 gbb_flags)
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700496
497 def check_ec_capability(self, required_cap=None, suppress_warning=False):
498 """Check if current platform has required EC capabilities.
499
500 @param required_cap: A list containing required EC capabilities. Pass in
501 None to only check for presence of Chrome EC.
502 @param suppress_warning: True to suppress any warning messages.
503 @return: True if requirements are met. Otherwise, False.
504 """
505 if not self.faft_config.chrome_ec:
506 if not suppress_warning:
507 logging.warn('Requires Chrome EC to run this test.')
508 return False
509
510 if not required_cap:
511 return True
512
513 for cap in required_cap:
514 if cap not in self.faft_config.ec_capability:
515 if not suppress_warning:
516 logging.warn('Requires EC capability "%s" to run this '
517 'test.', cap)
518 return False
519
520 return True
521
522 def check_root_part_on_non_recovery(self, part):
523 """Check the partition number of root device and on normal/dev boot.
524
525 @param part: A string of partition number, e.g.'3'.
526 @return: True if the root device matched and on normal/dev boot;
527 otherwise, False.
528 """
529 return self.checkers.root_part_checker(part) and \
530 self.checkers.crossystem_checker({
531 'mainfw_type': ('normal', 'developer'),
532 })
533
534 def _join_part(self, dev, part):
535 """Return a concatenated string of device and partition number.
536
537 @param dev: A string of device, e.g.'/dev/sda'.
538 @param part: A string of partition number, e.g.'3'.
539 @return: A concatenated string of device and partition number,
540 e.g.'/dev/sda3'.
541
542 >>> seq = FirmwareTest()
543 >>> seq._join_part('/dev/sda', '3')
544 '/dev/sda3'
545 >>> seq._join_part('/dev/mmcblk0', '2')
546 '/dev/mmcblk0p2'
547 """
548 if 'mmcblk' in dev:
549 return dev + 'p' + part
550 else:
551 return dev + part
552
553 def copy_kernel_and_rootfs(self, from_part, to_part):
554 """Copy kernel and rootfs from from_part to to_part.
555
556 @param from_part: A string of partition number to be copied from.
557 @param to_part: A string of partition number to be copied to.
558 """
559 root_dev = self.faft_client.system.get_root_dev()
560 logging.info('Copying kernel from %s to %s. Please wait...',
561 from_part, to_part)
562 self.faft_client.system.run_shell_command('dd if=%s of=%s bs=4M' %
563 (self._join_part(root_dev, self.KERNEL_MAP[from_part]),
564 self._join_part(root_dev, self.KERNEL_MAP[to_part])))
565 logging.info('Copying rootfs from %s to %s. Please wait...',
566 from_part, to_part)
567 self.faft_client.system.run_shell_command('dd if=%s of=%s bs=4M' %
568 (self._join_part(root_dev, self.ROOTFS_MAP[from_part]),
569 self._join_part(root_dev, self.ROOTFS_MAP[to_part])))
570
571 def ensure_kernel_boot(self, part):
572 """Ensure the request kernel boot.
573
574 If not, it duplicates the current kernel to the requested kernel
575 and sets the requested higher priority to ensure it boot.
576
577 @param part: A string of kernel partition number or 'a'/'b'.
578 """
579 if not self.checkers.root_part_checker(part):
580 if self.faft_client.kernel.diff_a_b():
581 self.copy_kernel_and_rootfs(
582 from_part=self.OTHER_KERNEL_MAP[part],
583 to_part=part)
Yusuf Mohsinallyab1b5fc2014-05-08 12:46:10 -0700584 self.reset_and_prioritize_kernel(part)
Tom Wai-Hong Tamb1a86012015-09-25 14:27:20 +0800585 self.switcher.mode_aware_reboot()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700586
587 def set_hardware_write_protect(self, enable):
588 """Set hardware write protect pin.
589
590 @param enable: True if asserting write protect pin. Otherwise, False.
591 """
592 self.servo.set('fw_wp_vref', self.faft_config.wp_voltage)
593 self.servo.set('fw_wp_en', 'on')
594 self.servo.set('fw_wp', 'on' if enable else 'off')
595
596 def set_ec_write_protect_and_reboot(self, enable):
597 """Set EC write protect status and reboot to take effect.
598
599 The write protect state is only activated if both hardware write
600 protect pin is asserted and software write protect flag is set.
601 This method asserts/deasserts hardware write protect pin first, and
602 set corresponding EC software write protect flag.
603
604 If the device uses non-Chrome EC, set the software write protect via
605 flashrom.
606
607 If the device uses Chrome EC, a reboot is required for write protect
608 to take effect. Since the software write protect flag cannot be unset
609 if hardware write protect pin is asserted, we need to deasserted the
610 pin first if we are deactivating write protect. Similarly, a reboot
611 is required before we can modify the software flag.
612
613 @param enable: True if activating EC write protect. Otherwise, False.
614 """
615 self.set_hardware_write_protect(enable)
616 if self.faft_config.chrome_ec:
617 self.set_chrome_ec_write_protect_and_reboot(enable)
618 else:
619 self.faft_client.ec.set_write_protect(enable)
Tom Wai-Hong Tama704f182015-05-06 06:12:55 +0800620 self.switcher.mode_aware_reboot()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700621
622 def set_chrome_ec_write_protect_and_reboot(self, enable):
623 """Set Chrome EC write protect status and reboot to take effect.
624
625 @param enable: True if activating EC write protect. Otherwise, False.
626 """
627 if enable:
628 # Set write protect flag and reboot to take effect.
629 self.ec.set_flash_write_protect(enable)
630 self.sync_and_ec_reboot()
631 else:
632 # Reboot after deasserting hardware write protect pin to deactivate
633 # write protect. And then remove software write protect flag.
634 self.sync_and_ec_reboot()
635 self.ec.set_flash_write_protect(enable)
636
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700637 def _setup_ec_write_protect(self, ec_wp):
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700638 """Setup for EC write-protection.
639
640 It makes sure the EC in the requested write-protection state. If not, it
641 flips the state. Flipping the write-protection requires DUT reboot.
642
643 @param ec_wp: True to request EC write-protected; False to request EC
644 not write-protected; None to do nothing.
645 """
646 if ec_wp is None:
647 self._old_ec_wp = None
648 return
649 self._old_ec_wp = self.checkers.crossystem_checker({'wpsw_boot': '1'})
650 if ec_wp != self._old_ec_wp:
651 logging.info('The test required EC is %swrite-protected. Reboot '
652 'and flip the state.', '' if ec_wp else 'not ')
Tom Wai-Hong Tam3e92b8e2015-05-07 06:29:57 +0800653 self.switcher.mode_aware_reboot(
654 'custom',
655 lambda:self.set_ec_write_protect_and_reboot(ec_wp))
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700656
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700657 def _restore_ec_write_protect(self):
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700658 """Restore the original EC write-protection."""
659 if (not hasattr(self, '_old_ec_wp')) or (self._old_ec_wp is None):
660 return
661 if not self.checkers.crossystem_checker(
662 {'wpsw_boot': '1' if self._old_ec_wp else '0'}):
Yusuf Mohsinallyab1b5fc2014-05-08 12:46:10 -0700663 logging.info('Restore original EC write protection and reboot.')
Tom Wai-Hong Tam3e92b8e2015-05-07 06:29:57 +0800664 self.switcher.mode_aware_reboot(
665 'custom',
666 lambda:self.set_ec_write_protect_and_reboot(
667 self._old_ec_wp))
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700668
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700669 def _setup_uart_capture(self):
Duncan Laurieaf61c1f2014-10-07 15:35:18 -0700670 """Setup the CPU/EC/PD UART capture."""
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700671 self.cpu_uart_file = os.path.join(self.resultsdir, 'cpu_uart.txt')
672 self.servo.set('cpu_uart_capture', 'on')
673 self.ec_uart_file = None
Duncan Laurieaf61c1f2014-10-07 15:35:18 -0700674 self.usbpd_uart_file = None
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700675 if self.faft_config.chrome_ec:
676 try:
677 self.servo.set('ec_uart_capture', 'on')
678 self.ec_uart_file = os.path.join(self.resultsdir, 'ec_uart.txt')
679 except error.TestFail as e:
680 if 'No control named' in str(e):
681 logging.warn('The servod is too old that ec_uart_capture '
682 'not supported.')
Duncan Laurieaf61c1f2014-10-07 15:35:18 -0700683 # Log separate PD console if supported
684 if self.check_ec_capability(['usbpd_uart'], suppress_warning=True):
685 try:
686 self.servo.set('usbpd_uart_capture', 'on')
687 self.usbpd_uart_file = os.path.join(self.resultsdir,
688 'usbpd_uart.txt')
689 except error.TestFail as e:
690 if 'No control named' in str(e):
691 logging.warn('The servod is too old that '
692 'usbpd_uart_capture is not supported.')
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700693 else:
694 logging.info('Not a Google EC, cannot capture ec console output.')
695
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700696 def _record_uart_capture(self):
Duncan Laurieaf61c1f2014-10-07 15:35:18 -0700697 """Record the CPU/EC/PD UART output stream to files."""
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700698 if self.cpu_uart_file:
699 with open(self.cpu_uart_file, 'a') as f:
700 f.write(ast.literal_eval(self.servo.get('cpu_uart_stream')))
701 if self.ec_uart_file and self.faft_config.chrome_ec:
702 with open(self.ec_uart_file, 'a') as f:
703 f.write(ast.literal_eval(self.servo.get('ec_uart_stream')))
Duncan Laurieaf61c1f2014-10-07 15:35:18 -0700704 if (self.usbpd_uart_file and self.faft_config.chrome_ec and
705 self.check_ec_capability(['usbpd_uart'], suppress_warning=True)):
706 with open(self.usbpd_uart_file, 'a') as f:
707 f.write(ast.literal_eval(self.servo.get('usbpd_uart_stream')))
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700708
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700709 def _cleanup_uart_capture(self):
Duncan Laurieaf61c1f2014-10-07 15:35:18 -0700710 """Cleanup the CPU/EC/PD UART capture."""
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700711 # Flush the remaining UART output.
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700712 self._record_uart_capture()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700713 self.servo.set('cpu_uart_capture', 'off')
714 if self.ec_uart_file and self.faft_config.chrome_ec:
715 self.servo.set('ec_uart_capture', 'off')
Duncan Laurieaf61c1f2014-10-07 15:35:18 -0700716 if (self.usbpd_uart_file and self.faft_config.chrome_ec and
717 self.check_ec_capability(['usbpd_uart'], suppress_warning=True)):
718 self.servo.set('usbpd_uart_capture', 'off')
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700719
Shelley Chenc0781c42015-10-05 10:52:42 -0700720 def _get_power_state(self, power_state):
721 """
722 Return the current power state of the AP
723 """
724 return self.ec.send_command_get_output("powerinfo", [power_state])
725
726 def wait_power_state(self, power_state, retries):
727 """
728 Wait for certain power state.
729
730 @param power_state: power state you are expecting
731 @param retries: retries. This is necessary if AP is powering down
732 and transitioning through different states.
733 """
734 logging.info('Checking power state "%s" maximum %d times.',
735 power_state, retries)
736 while retries > 0:
737 logging.info("try count: %d" % retries)
738 try:
739 retries = retries - 1
740 ret = self._get_power_state(power_state)
741 return True
742 except error.TestFail:
743 pass
744 return False
745
Shelley Chen530490a2015-10-21 10:43:06 -0700746 def delayed(seconds):
Shelley Chen530490a2015-10-21 10:43:06 -0700747 def decorator(f):
748 def wrapper(*args, **kargs):
749 t = Timer(seconds, f, args, kargs)
750 t.start()
751 return wrapper
752 return decorator
753
754 @delayed(WAKE_DELAY)
755 def wake_by_power_button(self):
756 """Delay by WAKE_DELAY seconds and then wake DUT with power button."""
757 self.servo.power_normal_press()
758
759 @delayed(WAKE_DELAY)
760 def wake_by_lid_switch(self):
761 """Delay by WAKE_DELAY seconds and then wake DUT with lid switch."""
762 self.servo.set('lid_open', 'no')
763 time.sleep(self.LID_DELAY)
764 self.servo.set('lid_open', 'yes')
765
766 def suspend_as_reboot(self, wake_func):
767 """
768 Suspend DUT and also kill FAFT client so that this acts like a reboot.
769
770 Args:
771 wake_func: A function that is called to wake DUT. Note that this
772 function must delay itself so that we don't wake DUT before
773 suspend_as_reboot returns.
774 """
775 cmd = '(sleep %d; powerd_dbus_suspend) &' % self.EC_SUSPEND_DELAY
776 self.faft_client.system.run_shell_command(cmd)
777 self.faft_client.disconnect()
778 time.sleep(self.EC_SUSPEND_DELAY)
779 logging.info("wake function disabled")
780 wake_func()
781
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700782 def _fetch_servo_log(self):
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700783 """Fetch the servo log."""
784 cmd = '[ -e %s ] && cat %s || echo NOTFOUND' % ((self._SERVOD_LOG,) * 2)
785 servo_log = self.servo.system_output(cmd)
786 return None if servo_log == 'NOTFOUND' else servo_log
787
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700788 def _setup_servo_log(self):
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700789 """Setup the servo log capturing."""
790 self.servo_log_original_len = -1
791 if self.servo.is_localhost():
792 # No servo log recorded when servod runs locally.
793 return
794
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700795 servo_log = self._fetch_servo_log()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700796 if servo_log:
797 self.servo_log_original_len = len(servo_log)
798 else:
799 logging.warn('Servo log file not found.')
800
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700801 def _record_servo_log(self):
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700802 """Record the servo log to the results directory."""
803 if self.servo_log_original_len != -1:
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700804 servo_log = self._fetch_servo_log()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700805 servo_log_file = os.path.join(self.resultsdir, 'servod.log')
806 with open(servo_log_file, 'a') as f:
807 f.write(servo_log[self.servo_log_original_len:])
808
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700809 def _record_faft_client_log(self):
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700810 """Record the faft client log to the results directory."""
811 client_log = self.faft_client.system.dump_log(True)
812 client_log_file = os.path.join(self.resultsdir, 'faft_client.log')
813 with open(client_log_file, 'w') as f:
814 f.write(client_log)
815
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700816 def _setup_gbb_flags(self):
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700817 """Setup the GBB flags for FAFT test."""
818 if self.faft_config.gbb_version < 1.1:
819 logging.info('Skip modifying GBB on versions older than 1.1.')
820 return
821
822 if self.check_setup_done('gbb_flags'):
823 return
824
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700825 logging.info('Set proper GBB flags for test.')
826 self.clear_set_gbb_flags(vboot.GBB_FLAG_DEV_SCREEN_SHORT_DELAY |
827 vboot.GBB_FLAG_FORCE_DEV_SWITCH_ON |
828 vboot.GBB_FLAG_FORCE_DEV_BOOT_USB |
Tom Wai-Hong Tam46ebbb12015-08-28 07:59:32 +0800829 vboot.GBB_FLAG_DISABLE_FW_ROLLBACK_CHECK |
830 vboot.GBB_FLAG_FORCE_DEV_BOOT_FASTBOOT_FULL_CAP,
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700831 vboot.GBB_FLAG_ENTER_TRIGGERS_TONORM |
832 vboot.GBB_FLAG_FAFT_KEY_OVERIDE)
833 self.mark_setup_done('gbb_flags')
834
835 def drop_backup_gbb_flags(self):
836 """Drops the backup GBB flags.
837
838 This can be used when a test intends to permanently change GBB flags.
839 """
840 self._backup_gbb_flags = None
841
Yusuf Mohsinally8b377eb2014-05-12 18:50:03 -0700842 def _restore_gbb_flags(self):
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700843 """Restore GBB flags to their original state."""
Tom Wai-Hong Tamfc0c7702015-09-12 03:43:53 +0800844 if self._backup_gbb_flags is None:
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700845 return
Tom Wai-Hong Tam2e3db4a2015-08-27 06:26:32 +0800846 # Setting up and restoring the GBB flags take a lot of time. For
847 # speed-up purpose, don't restore it.
848 logging.info('***')
849 logging.info('*** Please manually restore the original GBB flags to: '
850 '0x%x ***', self._backup_gbb_flags)
851 logging.info('***')
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700852 self.unmark_setup_done('gbb_flags')
853
854 def setup_tried_fwb(self, tried_fwb):
855 """Setup for fw B tried state.
856
857 It makes sure the system in the requested fw B tried state. If not, it
858 tries to do so.
859
860 @param tried_fwb: True if requested in tried_fwb=1;
861 False if tried_fwb=0.
862 """
863 if tried_fwb:
864 if not self.checkers.crossystem_checker({'tried_fwb': '1'}):
865 logging.info(
866 'Firmware is not booted with tried_fwb. Reboot into it.')
Yusuf Mohsinallyab1b5fc2014-05-08 12:46:10 -0700867 self.faft_client.system.set_try_fw_b()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700868 else:
869 if not self.checkers.crossystem_checker({'tried_fwb': '0'}):
870 logging.info(
871 'Firmware is booted with tried_fwb. Reboot to clear.')
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700872
873 def power_on(self):
874 """Switch DUT AC power on."""
875 self._client.power_on(self.power_control)
876
877 def power_off(self):
878 """Switch DUT AC power off."""
879 self._client.power_off(self.power_control)
880
881 def power_cycle(self):
882 """Power cycle DUT AC power."""
883 self._client.power_cycle(self.power_control)
884
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700885 def setup_rw_boot(self, section='a'):
886 """Make sure firmware is in RW-boot mode.
887
888 If the given firmware section is in RO-boot mode, turn off the RO-boot
889 flag and reboot DUT into RW-boot mode.
890
891 @param section: A firmware section, either 'a' or 'b'.
892 """
893 flags = self.faft_client.bios.get_preamble_flags(section)
894 if flags & vboot.PREAMBLE_USE_RO_NORMAL:
895 flags = flags ^ vboot.PREAMBLE_USE_RO_NORMAL
Yusuf Mohsinallyab1b5fc2014-05-08 12:46:10 -0700896 self.faft_client.bios.set_preamble_flags(section, flags)
Tom Wai-Hong Tam47776242015-05-07 02:45:32 +0800897 self.switcher.mode_aware_reboot()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700898
899 def setup_kernel(self, part):
900 """Setup for kernel test.
901
902 It makes sure both kernel A and B bootable and the current boot is
903 the requested kernel part.
904
905 @param part: A string of kernel partition number or 'a'/'b'.
906 """
907 self.ensure_kernel_boot(part)
908 logging.info('Checking the integrity of kernel B and rootfs B...')
909 if (self.faft_client.kernel.diff_a_b() or
910 not self.faft_client.rootfs.verify_rootfs('B')):
911 logging.info('Copying kernel and rootfs from A to B...')
912 self.copy_kernel_and_rootfs(from_part=part,
913 to_part=self.OTHER_KERNEL_MAP[part])
914 self.reset_and_prioritize_kernel(part)
915
916 def reset_and_prioritize_kernel(self, part):
917 """Make the requested partition highest priority.
918
919 This function also reset kerenl A and B to bootable.
920
921 @param part: A string of partition number to be prioritized.
922 """
923 root_dev = self.faft_client.system.get_root_dev()
924 # Reset kernel A and B to bootable.
925 self.faft_client.system.run_shell_command(
926 'cgpt add -i%s -P1 -S1 -T0 %s' % (self.KERNEL_MAP['a'], root_dev))
927 self.faft_client.system.run_shell_command(
928 'cgpt add -i%s -P1 -S1 -T0 %s' % (self.KERNEL_MAP['b'], root_dev))
929 # Set kernel part highest priority.
930 self.faft_client.system.run_shell_command('cgpt prioritize -i%s %s' %
931 (self.KERNEL_MAP[part], root_dev))
932
Yusuf Mohsinally1bacc962014-08-14 11:37:32 -0700933 def blocking_sync(self):
934 """Run a blocking sync command."""
935 # The double calls to sync fakes a blocking call
936 # since the first call returns before the flush
937 # is complete, but the second will wait for the
938 # first to finish.
939 self.faft_client.system.run_shell_command('sync')
940 self.faft_client.system.run_shell_command('sync')
941
Ryan Lin5bee6102014-09-16 13:17:02 -0700942 # sync only sends SYNCHRONIZE_CACHE but doesn't
Steve Fungb5752422015-01-09 16:45:32 -0800943 # check the status. For mmc devices, use `mmc
944 # status get` command to send an empty command to
945 # wait for the disk to be available again. For
946 # other devices, hdparm sends TUR to check if
Ryan Lin5bee6102014-09-16 13:17:02 -0700947 # a device is ready for transfer operation.
948 root_dev = self.faft_client.system.get_root_dev()
Steve Fungb5752422015-01-09 16:45:32 -0800949 if 'mmcblk' in root_dev:
950 self.faft_client.system.run_shell_command('mmc status get %s' %
951 root_dev)
952 else:
953 self.faft_client.system.run_shell_command('hdparm -f %s' % root_dev)
Yusuf Mohsinallyab1b5fc2014-05-08 12:46:10 -0700954
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700955 def sync_and_ec_reboot(self, flags=''):
956 """Request the client sync and do a EC triggered reboot.
957
958 @param flags: Optional, a space-separated string of flags passed to EC
959 reboot command, including:
960 default: EC soft reboot;
961 'hard': EC cold/hard reboot.
962 """
Yusuf Mohsinally1bacc962014-08-14 11:37:32 -0700963 self.blocking_sync()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700964 self.ec.reboot(flags)
965 time.sleep(self.faft_config.ec_boot_to_console)
966 self.check_lid_and_power_on()
967
Julius Werner18c4e162015-07-07 13:02:08 -0700968 def reboot_and_reset_tpm(self):
969 """Reboot into recovery mode, reset TPM, then reboot back to disk."""
970 self.switcher.reboot_to_mode(to_mode='rec')
971 self.faft_client.system.run_shell_command('chromeos-tpm-recovery')
Tom Wai-Hong Tama704f182015-05-06 06:12:55 +0800972 self.switcher.mode_aware_reboot()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700973
974 def full_power_off_and_on(self):
975 """Shutdown the device by pressing power button and power on again."""
Danny Chan101b0b22014-11-06 10:08:54 -0800976 boot_id = self.get_bootid()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700977 # Press power button to trigger Chrome OS normal shutdown process.
978 # We use a customized delay since the normal-press 1.2s is not enough.
David Hendricks25d703a2015-08-21 14:37:51 -0700979 self.servo.power_key(self.faft_config.hold_pwr_button_poweroff)
Danny Chan101b0b22014-11-06 10:08:54 -0800980 # device can take 44-51 seconds to restart,
981 # add buffer from the default timeout of 60 seconds.
Tom Wai-Hong Tam19bfb6e2015-08-20 06:05:36 +0800982 self.switcher.wait_for_client_offline(timeout=100, orig_boot_id=boot_id)
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700983 time.sleep(self.faft_config.shutdown)
984 # Short press power button to boot DUT again.
David Hendricks25d703a2015-08-21 14:37:51 -0700985 self.servo.power_key(self.faft_config.hold_pwr_button_poweron)
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -0700986
987 def check_lid_and_power_on(self):
988 """
989 On devices with EC software sync, system powers on after EC reboots if
990 lid is open. Otherwise, the EC shuts down CPU after about 3 seconds.
991 This method checks lid switch state and presses power button if
992 necessary.
993 """
994 if self.servo.get("lid_open") == "no":
995 time.sleep(self.faft_config.software_sync)
996 self.servo.power_short_press()
997
998 def _modify_usb_kernel(self, usb_dev, from_magic, to_magic):
999 """Modify the kernel header magic in USB stick.
1000
1001 The kernel header magic is the first 8-byte of kernel partition.
1002 We modify it to make it fail on kernel verification check.
1003
1004 @param usb_dev: A string of USB stick path on the host, like '/dev/sdc'.
1005 @param from_magic: A string of magic which we change it from.
1006 @param to_magic: A string of magic which we change it to.
1007 @raise TestError: if failed to change magic.
1008 """
1009 assert len(from_magic) == 8
1010 assert len(to_magic) == 8
1011 # USB image only contains one kernel.
1012 kernel_part = self._join_part(usb_dev, self.KERNEL_MAP['a'])
1013 read_cmd = "sudo dd if=%s bs=8 count=1 2>/dev/null" % kernel_part
1014 current_magic = self.servo.system_output(read_cmd)
1015 if current_magic == to_magic:
1016 logging.info("The kernel magic is already %s.", current_magic)
1017 return
1018 if current_magic != from_magic:
1019 raise error.TestError("Invalid kernel image on USB: wrong magic.")
1020
1021 logging.info('Modify the kernel magic in USB, from %s to %s.',
1022 from_magic, to_magic)
1023 write_cmd = ("echo -n '%s' | sudo dd of=%s oflag=sync conv=notrunc "
1024 " 2>/dev/null" % (to_magic, kernel_part))
1025 self.servo.system(write_cmd)
1026
1027 if self.servo.system_output(read_cmd) != to_magic:
1028 raise error.TestError("Failed to write new magic.")
1029
1030 def corrupt_usb_kernel(self, usb_dev):
1031 """Corrupt USB kernel by modifying its magic from CHROMEOS to CORRUPTD.
1032
1033 @param usb_dev: A string of USB stick path on the host, like '/dev/sdc'.
1034 """
1035 self._modify_usb_kernel(usb_dev, self.CHROMEOS_MAGIC,
1036 self.CORRUPTED_MAGIC)
1037
1038 def restore_usb_kernel(self, usb_dev):
1039 """Restore USB kernel by modifying its magic from CORRUPTD to CHROMEOS.
1040
1041 @param usb_dev: A string of USB stick path on the host, like '/dev/sdc'.
1042 """
1043 self._modify_usb_kernel(usb_dev, self.CORRUPTED_MAGIC,
1044 self.CHROMEOS_MAGIC)
1045
1046 def _call_action(self, action_tuple, check_status=False):
1047 """Call the action function with/without arguments.
1048
1049 @param action_tuple: A function, or a tuple (function, args, error_msg),
1050 in which, args and error_msg are optional. args is
1051 either a value or a tuple if multiple arguments.
1052 This can also be a list containing multiple
1053 function or tuple. In this case, these actions are
1054 called in sequence.
1055 @param check_status: Check the return value of action function. If not
1056 succeed, raises a TestFail exception.
1057 @return: The result value of the action function.
1058 @raise TestError: An error when the action function is not callable.
1059 @raise TestFail: When check_status=True, action function not succeed.
1060 """
1061 if isinstance(action_tuple, list):
1062 return all([self._call_action(action, check_status=check_status)
1063 for action in action_tuple])
1064
1065 action = action_tuple
1066 args = ()
1067 error_msg = 'Not succeed'
1068 if isinstance(action_tuple, tuple):
1069 action = action_tuple[0]
1070 if len(action_tuple) >= 2:
1071 args = action_tuple[1]
1072 if not isinstance(args, tuple):
1073 args = (args,)
1074 if len(action_tuple) >= 3:
1075 error_msg = action_tuple[2]
1076
1077 if action is None:
1078 return
1079
1080 if not callable(action):
1081 raise error.TestError('action is not callable!')
1082
1083 info_msg = 'calling %s' % str(action)
1084 if args:
1085 info_msg += ' with args %s' % str(args)
1086 logging.info(info_msg)
1087 ret = action(*args)
1088
1089 if check_status and not ret:
1090 raise error.TestFail('%s: %s returning %s' %
1091 (error_msg, info_msg, str(ret)))
1092 return ret
1093
1094 def run_shutdown_process(self, shutdown_action, pre_power_action=None,
Danny Chana7db9b52015-12-04 16:12:48 -08001095 run_power_action=True, post_power_action=None, shutdown_timeout=None):
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001096 """Run shutdown_action(), which makes DUT shutdown, and power it on.
1097
1098 @param shutdown_action: function which makes DUT shutdown, like
1099 pressing power key.
1100 @param pre_power_action: function which is called before next power on.
Danny Chana7db9b52015-12-04 16:12:48 -08001101 @param power_action: power_key press by default, set to None to skip.
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001102 @param post_power_action: function which is called after next power on.
1103 @param shutdown_timeout: a timeout to confirm DUT shutdown.
1104 @raise TestFail: if the shutdown_action() failed to turn DUT off.
1105 """
1106 self._call_action(shutdown_action)
1107 logging.info('Wait to ensure DUT shut down...')
1108 try:
1109 if shutdown_timeout is None:
1110 shutdown_timeout = self.faft_config.shutdown_timeout
Tom Wai-Hong Tam19bfb6e2015-08-20 06:05:36 +08001111 self.switcher.wait_for_client(timeout=shutdown_timeout)
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001112 raise error.TestFail(
1113 'Should shut the device down after calling %s.' %
1114 str(shutdown_action))
1115 except ConnectionError:
1116 logging.info(
1117 'DUT is surely shutdown. We are going to power it on again...')
1118
1119 if pre_power_action:
1120 self._call_action(pre_power_action)
Danny Chana7db9b52015-12-04 16:12:48 -08001121 if run_power_action:
1122 self.servo.power_key(self.faft_config.hold_pwr_button_poweron)
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001123 if post_power_action:
1124 self._call_action(post_power_action)
1125
Yusuf Mohsinallyab1b5fc2014-05-08 12:46:10 -07001126 def get_bootid(self, retry=3):
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001127 """
Yusuf Mohsinallyab1b5fc2014-05-08 12:46:10 -07001128 Return the bootid.
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001129 """
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001130 boot_id = None
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001131 while retry:
1132 try:
1133 boot_id = self._client.get_boot_id()
1134 break
1135 except error.AutoservRunError:
1136 retry -= 1
1137 if retry:
1138 logging.info('Retry to get boot_id...')
1139 else:
1140 logging.warning('Failed to get boot_id.')
1141 logging.info('boot_id: %s', boot_id)
Yusuf Mohsinallyab1b5fc2014-05-08 12:46:10 -07001142 return boot_id
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001143
Yusuf Mohsinallyab1b5fc2014-05-08 12:46:10 -07001144 def check_state(self, func):
1145 """
1146 Wrapper around _call_action with check_status set to True. This is a
1147 helper function to be used by tests and is currently implemented by
1148 calling _call_action with check_status=True.
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001149
Yusuf Mohsinallyab1b5fc2014-05-08 12:46:10 -07001150 TODO: This function's arguments need to be made more stringent. And
1151 its functionality should be moved over to check functions directly in
1152 the future.
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001153
Yusuf Mohsinallyab1b5fc2014-05-08 12:46:10 -07001154 @param func: A function, or a tuple (function, args, error_msg),
1155 in which, args and error_msg are optional. args is
1156 either a value or a tuple if multiple arguments.
1157 This can also be a list containing multiple
1158 function or tuple. In this case, these actions are
1159 called in sequence.
1160 @return: The result value of the action function.
1161 @raise TestFail: If the function does notsucceed.
1162 """
1163 logging.info("-[FAFT]-[ start stepstate_checker ]----------")
1164 self._call_action(func, check_status=True)
1165 logging.info("-[FAFT]-[ end state_checker ]----------------")
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001166
1167 def get_current_firmware_sha(self):
1168 """Get current firmware sha of body and vblock.
1169
1170 @return: Current firmware sha follows the order (
1171 vblock_a_sha, body_a_sha, vblock_b_sha, body_b_sha)
1172 """
1173 current_firmware_sha = (self.faft_client.bios.get_sig_sha('a'),
1174 self.faft_client.bios.get_body_sha('a'),
1175 self.faft_client.bios.get_sig_sha('b'),
1176 self.faft_client.bios.get_body_sha('b'))
1177 if not all(current_firmware_sha):
1178 raise error.TestError('Failed to get firmware sha.')
1179 return current_firmware_sha
1180
1181 def is_firmware_changed(self):
1182 """Check if the current firmware changed, by comparing its SHA.
1183
1184 @return: True if it is changed, otherwise Flase.
1185 """
1186 # Device may not be rebooted after test.
1187 self.faft_client.bios.reload()
1188
1189 current_sha = self.get_current_firmware_sha()
1190
1191 if current_sha == self._backup_firmware_sha:
1192 return False
1193 else:
1194 corrupt_VBOOTA = (current_sha[0] != self._backup_firmware_sha[0])
1195 corrupt_FVMAIN = (current_sha[1] != self._backup_firmware_sha[1])
1196 corrupt_VBOOTB = (current_sha[2] != self._backup_firmware_sha[2])
1197 corrupt_FVMAINB = (current_sha[3] != self._backup_firmware_sha[3])
1198 logging.info("Firmware changed:")
1199 logging.info('VBOOTA is changed: %s', corrupt_VBOOTA)
1200 logging.info('VBOOTB is changed: %s', corrupt_VBOOTB)
1201 logging.info('FVMAIN is changed: %s', corrupt_FVMAIN)
1202 logging.info('FVMAINB is changed: %s', corrupt_FVMAINB)
1203 return True
1204
1205 def backup_firmware(self, suffix='.original'):
1206 """Backup firmware to file, and then send it to host.
1207
1208 @param suffix: a string appended to backup file name
1209 """
1210 remote_temp_dir = self.faft_client.system.create_temp_dir()
Tom Wai-Hong Tame1d5e662015-08-26 05:29:54 +08001211 remote_bios_path = os.path.join(remote_temp_dir, 'bios')
1212 self.faft_client.bios.dump_whole(remote_bios_path)
1213 self._client.get_file(remote_bios_path,
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001214 os.path.join(self.resultsdir, 'bios' + suffix))
Tom Wai-Hong Tame1d5e662015-08-26 05:29:54 +08001215 self._client.run('rm -rf %s' % remote_temp_dir)
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001216 logging.info('Backup firmware stored in %s with suffix %s',
1217 self.resultsdir, suffix)
1218
Tom Wai-Hong Tame1d5e662015-08-26 05:29:54 +08001219 self._backup_firmware_sha = self.get_current_firmware_sha()
1220
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001221 def is_firmware_saved(self):
1222 """Check if a firmware saved (called backup_firmware before).
1223
1224 @return: True if the firmware is backuped; otherwise False.
1225 """
1226 return self._backup_firmware_sha != ()
1227
1228 def clear_saved_firmware(self):
1229 """Clear the firmware saved by the method backup_firmware."""
1230 self._backup_firmware_sha = ()
1231
1232 def restore_firmware(self, suffix='.original'):
1233 """Restore firmware from host in resultsdir.
1234
1235 @param suffix: a string appended to backup file name
1236 """
1237 if not self.is_firmware_changed():
1238 return
1239
1240 # Backup current corrupted firmware.
1241 self.backup_firmware(suffix='.corrupt')
1242
1243 # Restore firmware.
1244 remote_temp_dir = self.faft_client.system.create_temp_dir()
1245 self._client.send_file(os.path.join(self.resultsdir, 'bios' + suffix),
1246 os.path.join(remote_temp_dir, 'bios'))
1247
1248 self.faft_client.bios.write_whole(
1249 os.path.join(remote_temp_dir, 'bios'))
Tom Wai-Hong Tama704f182015-05-06 06:12:55 +08001250 self.switcher.mode_aware_reboot()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001251 logging.info('Successfully restore firmware.')
1252
1253 def setup_firmwareupdate_shellball(self, shellball=None):
Tom Wai-Hong Tam28d82a82016-03-04 06:44:43 +08001254 """Setup a shellball to use in firmware update test.
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001255
1256 Check if there is a given shellball, and it is a shell script. Then,
Tom Wai-Hong Tam28d82a82016-03-04 06:44:43 +08001257 send it to the remote host. Otherwise, use the
1258 /usr/sbin/chromeos-firmwareupdate in the image and replace its inside
1259 BIOS and EC images with the active firmware images.
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001260
1261 @param shellball: path of a shellball or default to None.
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001262 """
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001263 if shellball:
1264 # Determine the firmware file is a shellball or a raw binary.
1265 is_shellball = (utils.system_output("file %s" % shellball).find(
1266 "shell script") != -1)
1267 if is_shellball:
1268 logging.info('Device will update firmware with shellball %s',
1269 shellball)
Tom Wai-Hong Tam28d82a82016-03-04 06:44:43 +08001270 temp_path = self.faft_client.updater.get_temp_path()
1271 working_shellball = os.path.join(temp_path,
1272 'chromeos-firmwareupdate')
1273 self._client.send_file(shellball, working_shellball)
1274 self.faft_client.updater.extract_shellball()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001275 else:
1276 raise error.TestFail(
1277 'The given shellball is not a shell script.')
Tom Wai-Hong Tam28d82a82016-03-04 06:44:43 +08001278 else:
1279 logging.info('No shellball given, use the original shellball and '
1280 'replace its BIOS and EC images.')
1281 work_path = self.faft_client.updater.get_work_path()
1282 bios_in_work_path = os.path.join(work_path, 'bios.bin')
1283 ec_in_work_path = os.path.join(work_path, 'ec.bin')
1284 self.faft_client.bios.dump_whole(bios_in_work_path)
Tom Wai-Hong Tam133b00c2016-06-22 04:06:19 +08001285 if self.faft_config.chrome_ec:
1286 self.faft_client.ec.dump_firmware(ec_in_work_path)
Tom Wai-Hong Tam28d82a82016-03-04 06:44:43 +08001287 self.faft_client.updater.repack_shellball()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001288
1289 def is_kernel_changed(self):
1290 """Check if the current kernel is changed, by comparing its SHA1 hash.
1291
1292 @return: True if it is changed; otherwise, False.
1293 """
1294 changed = False
1295 for p in ('A', 'B'):
1296 backup_sha = self._backup_kernel_sha.get(p, None)
1297 current_sha = self.faft_client.kernel.get_sha(p)
1298 if backup_sha != current_sha:
1299 changed = True
1300 logging.info('Kernel %s is changed', p)
1301 return changed
1302
1303 def backup_kernel(self, suffix='.original'):
1304 """Backup kernel to files, and the send them to host.
1305
1306 @param suffix: a string appended to backup file name.
1307 """
1308 remote_temp_dir = self.faft_client.system.create_temp_dir()
1309 for p in ('A', 'B'):
1310 remote_path = os.path.join(remote_temp_dir, 'kernel_%s' % p)
1311 self.faft_client.kernel.dump(p, remote_path)
1312 self._client.get_file(
1313 remote_path,
1314 os.path.join(self.resultsdir, 'kernel_%s%s' % (p, suffix)))
1315 self._backup_kernel_sha[p] = self.faft_client.kernel.get_sha(p)
1316 logging.info('Backup kernel stored in %s with suffix %s',
1317 self.resultsdir, suffix)
1318
1319 def is_kernel_saved(self):
1320 """Check if kernel images are saved (backup_kernel called before).
1321
1322 @return: True if the kernel is saved; otherwise, False.
1323 """
1324 return len(self._backup_kernel_sha) != 0
1325
1326 def clear_saved_kernel(self):
1327 """Clear the kernel saved by backup_kernel()."""
1328 self._backup_kernel_sha = dict()
1329
1330 def restore_kernel(self, suffix='.original'):
1331 """Restore kernel from host in resultsdir.
1332
1333 @param suffix: a string appended to backup file name.
1334 """
1335 if not self.is_kernel_changed():
1336 return
1337
1338 # Backup current corrupted kernel.
1339 self.backup_kernel(suffix='.corrupt')
1340
1341 # Restore kernel.
1342 remote_temp_dir = self.faft_client.system.create_temp_dir()
1343 for p in ('A', 'B'):
1344 remote_path = os.path.join(remote_temp_dir, 'kernel_%s' % p)
1345 self._client.send_file(
1346 os.path.join(self.resultsdir, 'kernel_%s%s' % (p, suffix)),
1347 remote_path)
1348 self.faft_client.kernel.write(p, remote_path)
1349
Tom Wai-Hong Tama704f182015-05-06 06:12:55 +08001350 self.switcher.mode_aware_reboot()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001351 logging.info('Successfully restored kernel.')
1352
1353 def backup_cgpt_attributes(self):
1354 """Backup CGPT partition table attributes."""
1355 self._backup_cgpt_attr = self.faft_client.cgpt.get_attributes()
1356
1357 def restore_cgpt_attributes(self):
1358 """Restore CGPT partition table attributes."""
1359 current_table = self.faft_client.cgpt.get_attributes()
1360 if current_table == self._backup_cgpt_attr:
1361 return
1362 logging.info('CGPT table is changed. Original: %r. Current: %r.',
1363 self._backup_cgpt_attr,
1364 current_table)
1365 self.faft_client.cgpt.set_attributes(self._backup_cgpt_attr)
1366
Tom Wai-Hong Tama704f182015-05-06 06:12:55 +08001367 self.switcher.mode_aware_reboot()
Yusuf Mohsinally05c3c552014-05-07 23:56:42 -07001368 logging.info('Successfully restored CGPT table.')
Shelley Chen3edea982014-12-30 14:54:21 -08001369
1370 def try_fwb(self, count=0):
1371 """set to try booting FWB count # times
1372
1373 Wrapper to set fwb_tries for vboot1 and fw_try_count,fw_try_next for
1374 vboot2
1375
1376 @param count: an integer specifying value to program into
1377 fwb_tries(vb1)/fw_try_next(vb2)
1378 """
1379 if self.fw_vboot2:
1380 self.faft_client.system.set_fw_try_next('B', count)
1381 else:
1382 # vboot1: we need to boot into fwb at least once
1383 if not count:
1384 count = count + 1
1385 self.faft_client.system.set_try_fw_b(count)
1386