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