blob: adabd08d42d4b9a8ef2c2e240bf527872f599375 [file] [log] [blame]
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +08001# Copyright (c) 2011 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
Vic Yangb4e3e742012-06-02 13:17:38 +08005import fdpexpect
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +08006import logging
Tom Wai-Hong Tam76c75072011-10-25 18:00:12 +08007import os
Vic Yangb4e3e742012-06-02 13:17:38 +08008import pexpect
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +08009import re
Tom Wai-Hong Tam40fd9472012-01-09 17:11:02 +080010import sys
Tom Wai-Hong Tam76c75072011-10-25 18:00:12 +080011import tempfile
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +080012import time
13import xmlrpclib
14
Tom Wai-Hong Tam76c75072011-10-25 18:00:12 +080015from autotest_lib.client.bin import utils
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +080016from autotest_lib.client.common_lib import error
Vic Yangebd6de62012-06-26 14:25:57 +080017from autotest_lib.server.cros.faft_client_attribute import FAFTClientAttribute
Tom Wai-Hong Tam22b77302011-11-03 13:03:48 +080018from autotest_lib.server.cros.servo_test import ServoTest
Tom Wai-Hong Tam40fd9472012-01-09 17:11:02 +080019from autotest_lib.site_utils import lab_test
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +080020
Tom Wai-Hong Tam40fd9472012-01-09 17:11:02 +080021dirname = os.path.dirname(sys.modules[__name__].__file__)
22autotest_dir = os.path.abspath(os.path.join(dirname, "..", ".."))
23cros_dir = os.path.join(autotest_dir, "..", "..", "..", "..")
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +080024
25class FAFTSequence(ServoTest):
26 """
27 The base class of Fully Automated Firmware Test Sequence.
28
29 Many firmware tests require several reboot cycles and verify the resulted
30 system states. To do that, an Autotest test case should detailly handle
31 every action on each step. It makes the test case hard to read and many
32 duplicated code. The base class FAFTSequence is to solve this problem.
33
34 The actions of one reboot cycle is defined in a dict, namely FAFT_STEP.
35 There are four functions in the FAFT_STEP dict:
36 state_checker: a function to check the current is valid or not,
37 returning True if valid, otherwise, False to break the whole
38 test sequence.
39 userspace_action: a function to describe the action ran in userspace.
Tom Wai-Hong Tamf1e34972011-11-02 17:07:04 +080040 reboot_action: a function to do reboot, default: sync_and_hw_reboot.
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +080041 firmware_action: a function to describe the action ran after reboot.
42
Tom Wai-Hong Tam7c17ff22011-10-26 09:44:09 +080043 And configurations:
44 install_deps_after_boot: if True, install the Autotest dependency after
45 boot; otherwise, do nothing. It is for the cases of recovery mode
46 test. The test boots a USB/SD image instead of an internal image.
47 The previous installed Autotest dependency on the internal image
48 is lost. So need to install it again.
49
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +080050 The default FAFT_STEP checks nothing in state_checker and does nothing in
Tom Wai-Hong Tamf1e34972011-11-02 17:07:04 +080051 userspace_action and firmware_action. Its reboot_action is a hardware
52 reboot. You can change the default FAFT_STEP by calling
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +080053 self.register_faft_template(FAFT_STEP).
54
55 A FAFT test case consists of several FAFT_STEP's, namely FAFT_SEQUENCE.
56 FAFT_SEQUENCE is an array of FAFT_STEP's. Any missing fields on FAFT_STEP
57 fall back to default.
58
59 In the run_once(), it should register and run FAFT_SEQUENCE like:
60 def run_once(self):
61 self.register_faft_sequence(FAFT_SEQUENCE)
62 self.run_faft_sequnce()
63
64 Note that in the last step, we only run state_checker. The
65 userspace_action, reboot_action, and firmware_action are not executed.
66
67 Attributes:
68 _faft_template: The default FAFT_STEP of each step. The actions would
69 be over-written if the registered FAFT_SEQUENCE is valid.
70 _faft_sequence: The registered FAFT_SEQUENCE.
Tom Wai-Hong Tam40fd9472012-01-09 17:11:02 +080071 _customized_ctrl_d_key_command: The customized Ctrl-D key command
72 instead of sending key via servo board.
73 _customized_enter_key_command: The customized Enter key command instead
74 of sending key via servo board.
75 _install_image_path: The path of Chrome OS test image to be installed.
Tom Wai-Hong Tam1a3ff742012-01-11 16:36:46 +080076 _firmware_update: Boolean. True if firmware update needed after
77 installing the image.
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +080078 """
79 version = 1
80
Tom Wai-Hong Tamcfda61f2011-11-02 17:41:01 +080081
82 # Mapping of partition number of kernel and rootfs.
83 KERNEL_MAP = {'a':'2', 'b':'4', '2':'2', '4':'4', '3':'2', '5':'4'}
84 ROOTFS_MAP = {'a':'3', 'b':'5', '2':'3', '4':'5', '3':'3', '5':'5'}
85 OTHER_KERNEL_MAP = {'a':'4', 'b':'2', '2':'4', '4':'2', '3':'4', '5':'2'}
86 OTHER_ROOTFS_MAP = {'a':'5', 'b':'3', '2':'5', '4':'3', '3':'5', '5':'3'}
87
Tom Wai-Hong Tama79574c2012-02-07 09:29:03 +080088 # Delay between power-on and firmware screen.
Tom Wai-Hong Tam211ccba2012-01-13 15:35:53 +080089 FIRMWARE_SCREEN_DELAY = 2
Tom Wai-Hong Tama79574c2012-02-07 09:29:03 +080090 # Delay between passing firmware screen and text mode warning screen.
Tom Wai-Hong Tama9c1a502011-11-10 06:39:26 +080091 TEXT_SCREEN_DELAY = 20
Tom Wai-Hong Tama79574c2012-02-07 09:29:03 +080092 # Delay of loading the USB kernel.
Tom Wai-Hong Tam07278c22012-02-08 16:53:00 +080093 USB_LOAD_DELAY = 10
Tom Wai-Hong Tama79574c2012-02-07 09:29:03 +080094 # Delay between USB plug-out and plug-in.
Tom Wai-Hong Tam9ca742a2011-12-05 15:48:57 +080095 USB_PLUG_DELAY = 10
Tom Wai-Hong Tama79574c2012-02-07 09:29:03 +080096 # Delay after running the 'sync' command.
Tom Wai-Hong Tam6a863ba2011-12-08 10:13:28 +080097 SYNC_DELAY = 5
Vic Yang59cac9c2012-05-21 15:28:42 +080098 # Delay for waiting client to return before EC reboot
99 EC_REBOOT_DELAY = 1
100 # Delay between EC reboot and pressing power button
101 POWER_BTN_DELAY = 0.5
Tom Wai-Hong Tamcfda61f2011-11-02 17:41:01 +0800102
Tom Wai-Hong Tam78709592011-12-19 11:16:50 +0800103 CHROMEOS_MAGIC = "CHROMEOS"
104 CORRUPTED_MAGIC = "CORRUPTD"
105
Tom Wai-Hong Tamf954d172011-12-08 17:14:15 +0800106 # Recovery reason codes, copied from:
107 # vboot_reference/firmware/lib/vboot_nvstorage.h
108 # vboot_reference/firmware/lib/vboot_struct.h
109 RECOVERY_REASON = {
110 # Recovery not requested
111 'NOT_REQUESTED': '0', # 0x00
112 # Recovery requested from legacy utility
113 'LEGACY': '1', # 0x01
114 # User manually requested recovery via recovery button
115 'RO_MANUAL': '2', # 0x02
116 # RW firmware failed signature check
117 'RO_INVALID_RW': '3', # 0x03
118 # S3 resume failed
119 'RO_S3_RESUME': '4', # 0x04
120 # TPM error in read-only firmware
121 'RO_TPM_ERROR': '5', # 0x05
122 # Shared data error in read-only firmware
123 'RO_SHARED_DATA': '6', # 0x06
124 # Test error from S3Resume()
125 'RO_TEST_S3': '7', # 0x07
126 # Test error from LoadFirmwareSetup()
127 'RO_TEST_LFS': '8', # 0x08
128 # Test error from LoadFirmware()
129 'RO_TEST_LF': '9', # 0x09
130 # RW firmware failed signature check
131 'RW_NOT_DONE': '16', # 0x10
132 'RW_DEV_MISMATCH': '17', # 0x11
133 'RW_REC_MISMATCH': '18', # 0x12
134 'RW_VERIFY_KEYBLOCK': '19', # 0x13
135 'RW_KEY_ROLLBACK': '20', # 0x14
136 'RW_DATA_KEY_PARSE': '21', # 0x15
137 'RW_VERIFY_PREAMBLE': '22', # 0x16
138 'RW_FW_ROLLBACK': '23', # 0x17
139 'RW_HEADER_VALID': '24', # 0x18
140 'RW_GET_FW_BODY': '25', # 0x19
141 'RW_HASH_WRONG_SIZE': '26', # 0x1A
142 'RW_VERIFY_BODY': '27', # 0x1B
143 'RW_VALID': '28', # 0x1C
144 # Read-only normal path requested by firmware preamble, but
145 # unsupported by firmware.
146 'RW_NO_RO_NORMAL': '29', # 0x1D
147 # Firmware boot failure outside of verified boot
148 'RO_FIRMWARE': '32', # 0x20
149 # Recovery mode TPM initialization requires a system reboot.
150 # The system was already in recovery mode for some other reason
151 # when this happened.
152 'RO_TPM_REBOOT': '33', # 0x21
153 # Unspecified/unknown error in read-only firmware
154 'RO_UNSPECIFIED': '63', # 0x3F
155 # User manually requested recovery by pressing a key at developer
156 # warning screen.
157 'RW_DEV_SCREEN': '65', # 0x41
158 # No OS kernel detected
159 'RW_NO_OS': '66', # 0x42
160 # OS kernel failed signature check
161 'RW_INVALID_OS': '67', # 0x43
162 # TPM error in rewritable firmware
163 'RW_TPM_ERROR': '68', # 0x44
164 # RW firmware in dev mode, but dev switch is off.
165 'RW_DEV_MISMATCH': '69', # 0x45
166 # Shared data error in rewritable firmware
167 'RW_SHARED_DATA': '70', # 0x46
168 # Test error from LoadKernel()
169 'RW_TEST_LK': '71', # 0x47
170 # No bootable disk found
171 'RW_NO_DISK': '72', # 0x48
172 # Unspecified/unknown error in rewritable firmware
173 'RW_UNSPECIFIED': '127', # 0x7F
174 # DM-verity error
175 'KE_DM_VERITY': '129', # 0x81
176 # Unspecified/unknown error in kernel
177 'KE_UNSPECIFIED': '191', # 0xBF
178 # Recovery mode test from user-mode
179 'US_TEST': '193', # 0xC1
180 # Unspecified/unknown error in user-mode
181 'US_UNSPECIFIED': '255', # 0xFF
182 }
183
Tom Wai-Hong Tam109f63c2011-12-08 14:58:27 +0800184 _faft_template = {}
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800185 _faft_sequence = ()
186
Tom Wai-Hong Tam1db43832011-12-09 10:50:56 +0800187 _customized_ctrl_d_key_command = None
188 _customized_enter_key_command = None
Tom Wai-Hong Tam40fd9472012-01-09 17:11:02 +0800189 _install_image_path = None
Tom Wai-Hong Tam1a3ff742012-01-11 16:36:46 +0800190 _firmware_update = False
Tom Wai-Hong Tam1db43832011-12-09 10:50:56 +0800191
192
193 def initialize(self, host, cmdline_args, use_pyauto=False, use_faft=False):
194 # Parse arguments from command line
195 args = {}
196 for arg in cmdline_args:
197 match = re.search("^(\w+)=(.+)", arg)
198 if match:
199 args[match.group(1)] = match.group(2)
200
Tom Wai-Hong Tam40fd9472012-01-09 17:11:02 +0800201 # Keep the arguments which will be used later.
Tom Wai-Hong Tam1db43832011-12-09 10:50:56 +0800202 if 'ctrl_d_cmd' in args:
203 self._customized_ctrl_d_key_command = args['ctrl_d_cmd']
204 logging.info('Customized Ctrl-D key command: %s' %
205 self._customized_ctrl_d_key_command)
206 if 'enter_cmd' in args:
207 self._customized_enter_key_command = args['enter_cmd']
208 logging.info('Customized Enter key command: %s' %
209 self._customized_enter_key_command)
Tom Wai-Hong Tam40fd9472012-01-09 17:11:02 +0800210 if 'image' in args:
211 self._install_image_path = args['image']
212 logging.info('Install Chrome OS test image path: %s' %
213 self._install_image_path)
Tom Wai-Hong Tam1a3ff742012-01-11 16:36:46 +0800214 if 'firmware_update' in args and args['firmware_update'].lower() \
215 not in ('0', 'false', 'no'):
216 if self._install_image_path:
217 self._firmware_update = True
218 logging.info('Also update firmware after installing.')
219 else:
220 logging.warning('Firmware update will not not performed '
221 'since no image is specified.')
Tom Wai-Hong Tam1db43832011-12-09 10:50:56 +0800222
223 super(FAFTSequence, self).initialize(host, cmdline_args, use_pyauto,
224 use_faft)
Vic Yangebd6de62012-06-26 14:25:57 +0800225 if use_faft:
226 self.client_attr = FAFTClientAttribute(
227 self.faft_client.get_platform_name())
Tom Wai-Hong Tam1db43832011-12-09 10:50:56 +0800228
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800229
230 def setup(self):
231 """Autotest setup function."""
232 super(FAFTSequence, self).setup()
233 if not self._remote_infos['faft']['used']:
234 raise error.TestError('The use_faft flag should be enabled.')
Tom Wai-Hong Tam40fd9472012-01-09 17:11:02 +0800235
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800236 self.register_faft_template({
237 'state_checker': (None),
238 'userspace_action': (None),
Tom Wai-Hong Tamf1e34972011-11-02 17:07:04 +0800239 'reboot_action': (self.sync_and_hw_reboot),
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800240 'firmware_action': (None)
241 })
Tom Wai-Hong Tam40fd9472012-01-09 17:11:02 +0800242 if self._install_image_path:
Tom Wai-Hong Tam1a3ff742012-01-11 16:36:46 +0800243 self.install_test_image(self._install_image_path,
244 self._firmware_update)
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800245
246
247 def cleanup(self):
248 """Autotest cleanup function."""
249 self._faft_sequence = ()
Tom Wai-Hong Tam109f63c2011-12-08 14:58:27 +0800250 self._faft_template = {}
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800251 super(FAFTSequence, self).cleanup()
252
253
Tom Wai-Hong Tam91f49822011-12-28 15:44:15 +0800254 def assert_test_image_in_usb_disk(self, usb_dev=None):
Tom Wai-Hong Tam76c75072011-10-25 18:00:12 +0800255 """Assert an USB disk plugged-in on servo and a test image inside.
256
Tom Wai-Hong Tam91f49822011-12-28 15:44:15 +0800257 Args:
258 usb_dev: A string of USB stick path on the host, like '/dev/sdc'.
259 If None, it is detected automatically.
260
Tom Wai-Hong Tam76c75072011-10-25 18:00:12 +0800261 Raises:
Tom Wai-Hong Tama9c1a502011-11-10 06:39:26 +0800262 error.TestError: if USB disk not detected or not a test image.
Tom Wai-Hong Tam76c75072011-10-25 18:00:12 +0800263 """
Tom Wai-Hong Tam91f49822011-12-28 15:44:15 +0800264 if usb_dev:
265 assert self.servo.get('usb_mux_sel1') == 'servo_sees_usbkey'
266 else:
267 self.servo.set('usb_mux_sel1', 'servo_sees_usbkey')
268 usb_dev = self.servo.probe_host_usb_dev()
269 if not usb_dev:
270 raise error.TestError(
271 'An USB disk should be plugged in the servo board.')
Tom Wai-Hong Tam76c75072011-10-25 18:00:12 +0800272
273 tmp_dir = tempfile.mkdtemp()
Tom Wai-Hong Tamb0e80852011-12-07 16:15:06 +0800274 utils.system('sudo mount -r -t ext2 %s3 %s' % (usb_dev, tmp_dir))
Tom Wai-Hong Tame77459e2011-11-03 17:19:46 +0800275 code = utils.system(
276 'grep -qE "(Test Build|testimage-channel)" %s/etc/lsb-release' %
277 tmp_dir, ignore_status=True)
Tom Wai-Hong Tam76c75072011-10-25 18:00:12 +0800278 utils.system('sudo umount %s' % tmp_dir)
279 os.removedirs(tmp_dir)
280 if code != 0:
281 raise error.TestError(
282 'The image in the USB disk should be a test image.')
283
284
Simran Basi741b5d42012-05-18 11:27:15 -0700285 def install_test_image(self, image_path=None, firmware_update=False):
Tom Wai-Hong Tam40fd9472012-01-09 17:11:02 +0800286 """Install the test image specied by the path onto the USB and DUT disk.
287
288 The method first copies the image to USB disk and reboots into it via
289 recovery mode. Then runs 'chromeos-install' to install it to DUT disk.
290
291 Args:
292 image_path: Path on the host to the test image.
Tom Wai-Hong Tam1a3ff742012-01-11 16:36:46 +0800293 firmware_update: Also update the firmware after installing.
Tom Wai-Hong Tam40fd9472012-01-09 17:11:02 +0800294 """
Tom Wai-Hong Tam1a3ff742012-01-11 16:36:46 +0800295 install_cmd = 'chromeos-install --yes'
296 if firmware_update:
297 install_cmd += ' && chromeos-firmwareupdate --mode recovery'
Tom Wai-Hong Tam40fd9472012-01-09 17:11:02 +0800298 build_ver, build_hash = lab_test.VerifyImageAndGetId(cros_dir,
299 image_path)
300 logging.info('Processing build: %s %s' % (build_ver, build_hash))
Tom Wai-Hong Tam40fd9472012-01-09 17:11:02 +0800301
302 # Reuse the install_recovery_image method by using a test image.
303 # Don't wait for completion but run chromeos-install to install it.
Simran Basi741b5d42012-05-18 11:27:15 -0700304 self.servo.install_recovery_image(image_path)
Tom Wai-Hong Tam40fd9472012-01-09 17:11:02 +0800305 self.wait_for_client(install_deps=True)
306 self.run_faft_step({
307 'userspace_action': (self.faft_client.run_shell_command,
Tom Wai-Hong Tam1a3ff742012-01-11 16:36:46 +0800308 install_cmd)
Tom Wai-Hong Tam40fd9472012-01-09 17:11:02 +0800309 })
310
311
Vic Yangb4e3e742012-06-02 13:17:38 +0800312 def _open_uart_pty(self):
313 """Open UART pty and spawn pexpect object.
314
315 Returns:
316 Tuple (fd, child): fd is the file descriptor of opened UART pty, and
317 child is a fdpexpect object tied to it.
318 """
319 fd = os.open(self.servo.get("uart1_pty"), os.O_RDWR | os.O_NONBLOCK)
320 child = fdpexpect.fdspawn(fd)
321 return (fd, child)
322
323
324 def _flush_uart_pty(self, child):
325 """Flush UART output to prevent previous pending message interferring.
326
327 Args:
328 child: The fdpexpect object tied to UART pty.
329 """
330 child.sendline("")
331 while True:
332 try:
333 child.expect(".", timeout=0.01)
334 except pexpect.TIMEOUT:
335 break
336
337
338 def _uart_send(self, child, line):
339 """Flush and send command through UART.
340
341 Args:
342 child: The pexpect object tied to UART pty.
343 line: String to send through UART.
344
345 Raises:
346 error.TestFail: Raised when writing to UART fails.
347 """
348 logging.info("Sending UART command: %s" % line)
349 self._flush_uart_pty(child)
350 if child.sendline(line) != len(line) + 1:
351 raise error.TestFail("Failed to send UART command.")
352
353
354 def send_uart_command(self, command):
355 """Send command through UART.
356
357 This function open UART pty when called, and then command is sent
358 through UART.
359
360 Args:
361 command: The command string to send.
362
363 Raises:
364 error.TestFail: Raised when writing to UART fails.
365 """
366 (fd, child) = self._open_uart_pty()
367 try:
368 self._uart_send(child, command)
369 finally:
370 os.close(fd)
371
372
373 def send_uart_command_get_output(self, command, regex_list, timeout=1):
374 """Send command through UART and wait for response.
375
376 This function waits for response message matching regular expressions.
377
378 Args:
379 command: The command sent.
380 regex_list: List of regular expressions used to match response message.
381 Note, list must be ordered.
382
383 Returns:
384 List of match objects of response message.
385
386 Raises:
387 error.TestFail: If timed out waiting for EC response.
388 """
389 if not isinstance(regex_list, list):
390 regex_list = [regex_list]
391 result_list = []
392 (fd, child) = self._open_uart_pty()
393 try:
394 self._uart_send(child, command)
395 for regex in regex_list:
396 child.expect(regex, timeout=timeout)
397 result_list.append(child.match)
398 except pexpect.TIMEOUT:
399 raise error.TestFail("Timeout waiting for UART response.")
400 finally:
401 os.close(fd)
402 return result_list
403
404
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800405 def _parse_crossystem_output(self, lines):
406 """Parse the crossystem output into a dict.
407
408 Args:
409 lines: The list of crossystem output strings.
410
411 Returns:
412 A dict which contains the crossystem keys/values.
413
414 Raises:
415 error.TestError: If wrong format in crossystem output.
416
417 >>> seq = FAFTSequence()
418 >>> seq._parse_crossystem_output([ \
419 "arch = x86 # Platform architecture", \
420 "cros_debug = 1 # OS should allow debug", \
421 ])
422 {'cros_debug': '1', 'arch': 'x86'}
423 >>> seq._parse_crossystem_output([ \
424 "arch=x86", \
425 ])
426 Traceback (most recent call last):
427 ...
428 TestError: Failed to parse crossystem output: arch=x86
429 >>> seq._parse_crossystem_output([ \
430 "arch = x86 # Platform architecture", \
431 "arch = arm # Platform architecture", \
432 ])
433 Traceback (most recent call last):
434 ...
435 TestError: Duplicated crossystem key: arch
436 """
437 pattern = "^([^ =]*) *= *(.*[^ ]) *# [^#]*$"
438 parsed_list = {}
439 for line in lines:
440 matched = re.match(pattern, line.strip())
441 if not matched:
442 raise error.TestError("Failed to parse crossystem output: %s"
443 % line)
444 (name, value) = (matched.group(1), matched.group(2))
445 if name in parsed_list:
446 raise error.TestError("Duplicated crossystem key: %s" % name)
447 parsed_list[name] = value
448 return parsed_list
449
450
451 def crossystem_checker(self, expected_dict):
452 """Check the crossystem values matched.
453
454 Given an expect_dict which describes the expected crossystem values,
455 this function check the current crossystem values are matched or not.
456
457 Args:
458 expected_dict: A dict which contains the expected values.
459
460 Returns:
461 True if the crossystem value matched; otherwise, False.
462 """
463 lines = self.faft_client.run_shell_command_get_output('crossystem')
464 got_dict = self._parse_crossystem_output(lines)
465 for key in expected_dict:
466 if key not in got_dict:
467 logging.info('Expected key "%s" not in crossystem result' % key)
468 return False
469 if isinstance(expected_dict[key], str):
470 if got_dict[key] != expected_dict[key]:
471 logging.info("Expected '%s' value '%s' but got '%s'" %
472 (key, expected_dict[key], got_dict[key]))
473 return False
474 elif isinstance(expected_dict[key], tuple):
475 # Expected value is a tuple of possible actual values.
476 if got_dict[key] not in expected_dict[key]:
477 logging.info("Expected '%s' values %s but got '%s'" %
478 (key, str(expected_dict[key]), got_dict[key]))
479 return False
480 else:
481 logging.info("The expected_dict is neither a str nor a dict.")
482 return False
483 return True
484
485
Tom Wai-Hong Tamcfda61f2011-11-02 17:41:01 +0800486 def root_part_checker(self, expected_part):
487 """Check the partition number of the root device matched.
488
489 Args:
490 expected_part: A string containing the number of the expected root
491 partition.
492
493 Returns:
494 True if the currect root partition number matched; otherwise, False.
495 """
Tom Wai-Hong Tam6a863ba2011-12-08 10:13:28 +0800496 part = self.faft_client.get_root_part()[-1]
497 if self.ROOTFS_MAP[expected_part] != part:
498 logging.info("Expected root part %s but got %s" %
499 (self.ROOTFS_MAP[expected_part], part))
500 return False
501 return True
Tom Wai-Hong Tamcfda61f2011-11-02 17:41:01 +0800502
503
Vic Yang59cac9c2012-05-21 15:28:42 +0800504 def ec_act_copy_checker(self, expected_copy):
505 """Check the EC running firmware copy matches.
506
507 Args:
508 expected_copy: A string containing 'RO', 'A', or 'B' indicating
509 the expected copy of EC running firmware.
510
511 Returns:
512 True if the current EC running copy matches; otherwise, False.
513 """
514 lines = self.faft_client.run_shell_command_get_output('ectool version')
515 pattern = re.compile("Firmware copy: (.*)")
516 for line in lines:
517 matched = pattern.match(line)
518 if matched and matched.group(1) == expected_copy:
519 return True
520 return False
521
522
Tom Wai-Hong Tam07278c22012-02-08 16:53:00 +0800523 def check_root_part_on_non_recovery(self, part):
524 """Check the partition number of root device and on normal/dev boot.
525
526 Returns:
527 True if the root device matched and on normal/dev boot;
528 otherwise, False.
529 """
530 return self.root_part_checker(part) and \
531 self.crossystem_checker({
532 'mainfw_type': ('normal', 'developer'),
533 'recoverysw_boot': '0',
534 })
535
536
Tom Wai-Hong Tamf2103be2011-11-10 07:26:56 +0800537 def _join_part(self, dev, part):
538 """Return a concatenated string of device and partition number.
539
540 Args:
541 dev: A string of device, e.g.'/dev/sda'.
542 part: A string of partition number, e.g.'3'.
543
544 Returns:
545 A concatenated string of device and partition number, e.g.'/dev/sda3'.
546
547 >>> seq = FAFTSequence()
548 >>> seq._join_part('/dev/sda', '3')
549 '/dev/sda3'
550 >>> seq._join_part('/dev/mmcblk0', '2')
551 '/dev/mmcblk0p2'
552 """
553 if 'mmcblk' in dev:
554 return dev + 'p' + part
555 else:
556 return dev + part
557
558
Tom Wai-Hong Tamcfda61f2011-11-02 17:41:01 +0800559 def copy_kernel_and_rootfs(self, from_part, to_part):
560 """Copy kernel and rootfs from from_part to to_part.
561
562 Args:
563 from_part: A string of partition number to be copied from.
Tom Wai-Hong Tamf2103be2011-11-10 07:26:56 +0800564 to_part: A string of partition number to be copied to.
Tom Wai-Hong Tamcfda61f2011-11-02 17:41:01 +0800565 """
566 root_dev = self.faft_client.get_root_dev()
Tom Wai-Hong Tamf2103be2011-11-10 07:26:56 +0800567 logging.info('Copying kernel from %s to %s. Please wait...' %
568 (from_part, to_part))
Tom Wai-Hong Tamcfda61f2011-11-02 17:41:01 +0800569 self.faft_client.run_shell_command('dd if=%s of=%s bs=4M' %
Tom Wai-Hong Tamf2103be2011-11-10 07:26:56 +0800570 (self._join_part(root_dev, self.KERNEL_MAP[from_part]),
571 self._join_part(root_dev, self.KERNEL_MAP[to_part])))
572 logging.info('Copying rootfs from %s to %s. Please wait...' %
573 (from_part, to_part))
Tom Wai-Hong Tamcfda61f2011-11-02 17:41:01 +0800574 self.faft_client.run_shell_command('dd if=%s of=%s bs=4M' %
Tom Wai-Hong Tamf2103be2011-11-10 07:26:56 +0800575 (self._join_part(root_dev, self.ROOTFS_MAP[from_part]),
576 self._join_part(root_dev, self.ROOTFS_MAP[to_part])))
Tom Wai-Hong Tamcfda61f2011-11-02 17:41:01 +0800577
578
579 def ensure_kernel_boot(self, part):
580 """Ensure the request kernel boot.
581
582 If not, it duplicates the current kernel to the requested kernel
583 and sets the requested higher priority to ensure it boot.
584
585 Args:
Tom Wai-Hong Tama9c1a502011-11-10 06:39:26 +0800586 part: A string of kernel partition number or 'a'/'b'.
Tom Wai-Hong Tamcfda61f2011-11-02 17:41:01 +0800587 """
588 if not self.root_part_checker(part):
589 self.copy_kernel_and_rootfs(from_part=self.OTHER_KERNEL_MAP[part],
590 to_part=part)
Tom Wai-Hong Tamc7ecfca2011-12-06 11:12:31 +0800591 self.run_faft_step({
592 'userspace_action': (self.reset_and_prioritize_kernel, part),
593 })
Tom Wai-Hong Tamcfda61f2011-11-02 17:41:01 +0800594
595
Tom Wai-Hong Tam1db43832011-12-09 10:50:56 +0800596 def send_ctrl_d_to_dut(self):
597 """Send Ctrl-D key to DUT."""
598 if self._customized_ctrl_d_key_command:
599 logging.info('running the customized Ctrl-D key command')
600 os.system(self._customized_ctrl_d_key_command)
601 else:
602 self.servo.ctrl_d()
603
604
605 def send_enter_to_dut(self):
606 """Send Enter key to DUT."""
607 if self._customized_enter_key_command:
608 logging.info('running the customized Enter key command')
609 os.system(self._customized_enter_key_command)
610 else:
611 self.servo.enter_key()
612
613
Tom Wai-Hong Tama9c1a502011-11-10 06:39:26 +0800614 def wait_fw_screen_and_ctrl_d(self):
615 """Wait for firmware warning screen and press Ctrl-D."""
616 time.sleep(self.FIRMWARE_SCREEN_DELAY)
Tom Wai-Hong Tam1db43832011-12-09 10:50:56 +0800617 self.send_ctrl_d_to_dut()
Tom Wai-Hong Tama9c1a502011-11-10 06:39:26 +0800618
619
Tom Wai-Hong Tam78709592011-12-19 11:16:50 +0800620 def wait_fw_screen_and_trigger_recovery(self, need_dev_transition=False):
621 """Wait for firmware warning screen and trigger recovery boot."""
622 time.sleep(self.FIRMWARE_SCREEN_DELAY)
623 self.send_enter_to_dut()
624
625 # For Alex/ZGB, there is a dev warning screen in text mode.
626 # Skip it by pressing Ctrl-D.
627 if need_dev_transition:
628 time.sleep(self.TEXT_SCREEN_DELAY)
629 self.send_ctrl_d_to_dut()
630
631
Tom Wai-Hong Tam5d2f4702011-12-06 10:42:31 +0800632 def wait_fw_screen_and_plug_usb(self):
633 """Wait for firmware warning screen and then unplug and plug the USB."""
Tom Wai-Hong Tama79574c2012-02-07 09:29:03 +0800634 time.sleep(self.USB_LOAD_DELAY)
Tom Wai-Hong Tam5d2f4702011-12-06 10:42:31 +0800635 self.servo.set('usb_mux_sel1', 'servo_sees_usbkey')
636 time.sleep(self.USB_PLUG_DELAY)
637 self.servo.set('usb_mux_sel1', 'dut_sees_usbkey')
638
639
Tom Wai-Hong Tam78709592011-12-19 11:16:50 +0800640 def wait_fw_screen_and_press_power(self):
641 """Wait for firmware warning screen and press power button."""
642 time.sleep(self.FIRMWARE_SCREEN_DELAY)
Tom Wai-Hong Tam610262a2012-01-12 14:16:53 +0800643 self.servo.power_short_press()
Tom Wai-Hong Tam78709592011-12-19 11:16:50 +0800644
645
646 def wait_fw_screen_and_close_lid(self):
647 """Wait for firmware warning screen and close lid."""
648 time.sleep(self.FIRMWARE_SCREEN_DELAY)
649 self.servo.lid_close()
650
651
Tom Wai-Hong Tamfd590c92011-11-25 11:50:57 +0800652 def setup_tried_fwb(self, tried_fwb):
653 """Setup for fw B tried state.
654
655 It makes sure the system in the requested fw B tried state. If not, it
656 tries to do so.
657
658 Args:
659 tried_fwb: True if requested in tried_fwb=1; False if tried_fwb=0.
660 """
661 if tried_fwb:
662 if not self.crossystem_checker({'tried_fwb': '1'}):
663 logging.info(
664 'Firmware is not booted with tried_fwb. Reboot into it.')
665 self.run_faft_step({
666 'userspace_action': self.faft_client.set_try_fw_b,
667 })
668 else:
669 if not self.crossystem_checker({'tried_fwb': '0'}):
670 logging.info(
671 'Firmware is booted with tried_fwb. Reboot to clear.')
672 self.run_faft_step({})
673
674
Tom Wai-Hong Tam78709592011-12-19 11:16:50 +0800675 def enable_dev_mode_and_fw(self):
676 """Enable developer mode and use developer firmware."""
677 self.servo.enable_development_mode()
678 self.faft_client.run_shell_command(
679 'chromeos-firmwareupdate --mode todev && reboot')
680
681
682 def enable_normal_mode_and_fw(self):
683 """Enable normal mode and use normal firmware."""
684 self.servo.disable_development_mode()
685 self.faft_client.run_shell_command(
686 'chromeos-firmwareupdate --mode tonormal && reboot')
687
688
Tom Wai-Hong Tama9c1a502011-11-10 06:39:26 +0800689 def setup_dev_mode(self, dev_mode):
690 """Setup for development mode.
691
Tom Wai-Hong Tamfd590c92011-11-25 11:50:57 +0800692 It makes sure the system in the requested normal/dev mode. If not, it
Tom Wai-Hong Tama9c1a502011-11-10 06:39:26 +0800693 tries to do so.
694
695 Args:
696 dev_mode: True if requested in dev mode; False if normal mode.
697 """
Tom Wai-Hong Tamfd590c92011-11-25 11:50:57 +0800698 # Change the default firmware_action for dev mode passing the fw screen.
699 self.register_faft_template({
Tom Wai-Hong Tamfd590c92011-11-25 11:50:57 +0800700 'firmware_action': (self.wait_fw_screen_and_ctrl_d if dev_mode
701 else None),
702 })
Tom Wai-Hong Tama9c1a502011-11-10 06:39:26 +0800703 if dev_mode:
704 if not self.crossystem_checker({'devsw_cur': '1'}):
705 logging.info('Dev switch is not on. Now switch it on.')
706 self.servo.enable_development_mode()
707 if not self.crossystem_checker({'devsw_boot': '1',
708 'mainfw_type': 'developer'}):
709 logging.info('System is not in dev mode. Reboot into it.')
Tom Wai-Hong Tamfd590c92011-11-25 11:50:57 +0800710 self.run_faft_step({
711 'userspace_action': (self.faft_client.run_shell_command,
Tom Wai-Hong Tamc7ecfca2011-12-06 11:12:31 +0800712 'chromeos-firmwareupdate --mode todev && reboot'),
713 'reboot_action': None,
Tom Wai-Hong Tamfd590c92011-11-25 11:50:57 +0800714 })
Tom Wai-Hong Tama9c1a502011-11-10 06:39:26 +0800715 else:
716 if not self.crossystem_checker({'devsw_cur': '0'}):
717 logging.info('Dev switch is not off. Now switch it off.')
718 self.servo.disable_development_mode()
719 if not self.crossystem_checker({'devsw_boot': '0',
720 'mainfw_type': 'normal'}):
721 logging.info('System is not in normal mode. Reboot into it.')
Tom Wai-Hong Tamfd590c92011-11-25 11:50:57 +0800722 self.run_faft_step({
723 'userspace_action': (self.faft_client.run_shell_command,
Tom Wai-Hong Tamc7ecfca2011-12-06 11:12:31 +0800724 'chromeos-firmwareupdate --mode tonormal && reboot'),
725 'reboot_action': None,
Tom Wai-Hong Tamfd590c92011-11-25 11:50:57 +0800726 })
Tom Wai-Hong Tama9c1a502011-11-10 06:39:26 +0800727
728
Tom Wai-Hong Tamcfda61f2011-11-02 17:41:01 +0800729 def setup_kernel(self, part):
730 """Setup for kernel test.
731
732 It makes sure both kernel A and B bootable and the current boot is
733 the requested kernel part.
734
735 Args:
Tom Wai-Hong Tama9c1a502011-11-10 06:39:26 +0800736 part: A string of kernel partition number or 'a'/'b'.
Tom Wai-Hong Tamcfda61f2011-11-02 17:41:01 +0800737 """
738 self.ensure_kernel_boot(part)
739 self.copy_kernel_and_rootfs(from_part=part,
740 to_part=self.OTHER_KERNEL_MAP[part])
741 self.reset_and_prioritize_kernel(part)
742
743
744 def reset_and_prioritize_kernel(self, part):
745 """Make the requested partition highest priority.
746
747 This function also reset kerenl A and B to bootable.
748
749 Args:
Tom Wai-Hong Tama9c1a502011-11-10 06:39:26 +0800750 part: A string of partition number to be prioritized.
Tom Wai-Hong Tamcfda61f2011-11-02 17:41:01 +0800751 """
752 root_dev = self.faft_client.get_root_dev()
753 # Reset kernel A and B to bootable.
754 self.faft_client.run_shell_command('cgpt add -i%s -P1 -S1 -T0 %s' %
755 (self.KERNEL_MAP['a'], root_dev))
756 self.faft_client.run_shell_command('cgpt add -i%s -P1 -S1 -T0 %s' %
757 (self.KERNEL_MAP['b'], root_dev))
758 # Set kernel part highest priority.
759 self.faft_client.run_shell_command('cgpt prioritize -i%s %s' %
760 (self.KERNEL_MAP[part], root_dev))
Tom Wai-Hong Tam6a863ba2011-12-08 10:13:28 +0800761 # Safer to sync and wait until the cgpt status written to the disk.
762 self.faft_client.run_shell_command('sync')
763 time.sleep(self.SYNC_DELAY)
Tom Wai-Hong Tamcfda61f2011-11-02 17:41:01 +0800764
765
Tom Wai-Hong Tamf1e34972011-11-02 17:07:04 +0800766 def sync_and_hw_reboot(self):
767 """Request the client sync and do a warm reboot.
768
769 This is the default reboot action on FAFT.
770 """
771 self.faft_client.run_shell_command('sync')
Tom Wai-Hong Tam6a863ba2011-12-08 10:13:28 +0800772 time.sleep(self.SYNC_DELAY)
Tom Wai-Hong Tamf1e34972011-11-02 17:07:04 +0800773 self.servo.warm_reset()
774
775
Vic Yang59cac9c2012-05-21 15:28:42 +0800776 def sync_and_ec_reboot(self):
777 """Request the client sync and do a EC triggered reboot."""
778 self.faft_client.run_shell_command('sync')
779 time.sleep(self.SYNC_DELAY)
780 self.faft_client.run_shell_command('(sleep %d; ectool reboot_ec)&' %
781 self.EC_REBOOT_DELAY)
782 time.sleep(self.EC_REBOOT_DELAY + self.POWER_BTN_DELAY)
783 self.servo.power_normal_press()
784
785
Tom Wai-Hong Tam78709592011-12-19 11:16:50 +0800786 def _modify_usb_kernel(self, usb_dev, from_magic, to_magic):
787 """Modify the kernel header magic in USB stick.
788
789 The kernel header magic is the first 8-byte of kernel partition.
790 We modify it to make it fail on kernel verification check.
791
792 Args:
793 usb_dev: A string of USB stick path on the host, like '/dev/sdc'.
794 from_magic: A string of magic which we change it from.
795 to_magic: A string of magic which we change it to.
796
797 Raises:
798 error.TestError: if failed to change magic.
799 """
800 assert len(from_magic) == 8
801 assert len(to_magic) == 8
Tom Wai-Hong Tama1d9a0f2011-12-23 09:13:33 +0800802 # USB image only contains one kernel.
803 kernel_part = self._join_part(usb_dev, self.KERNEL_MAP['a'])
Tom Wai-Hong Tam78709592011-12-19 11:16:50 +0800804 read_cmd = "sudo dd if=%s bs=8 count=1 2>/dev/null" % kernel_part
805 current_magic = utils.system_output(read_cmd)
806 if current_magic == to_magic:
807 logging.info("The kernel magic is already %s." % current_magic)
808 return
809 if current_magic != from_magic:
810 raise error.TestError("Invalid kernel image on USB: wrong magic.")
811
812 logging.info('Modify the kernel magic in USB, from %s to %s.' %
813 (from_magic, to_magic))
814 write_cmd = ("echo -n '%s' | sudo dd of=%s oflag=sync conv=notrunc "
815 " 2>/dev/null" % (to_magic, kernel_part))
816 utils.system(write_cmd)
817
818 if utils.system_output(read_cmd) != to_magic:
819 raise error.TestError("Failed to write new magic.")
820
821
822 def corrupt_usb_kernel(self, usb_dev):
823 """Corrupt USB kernel by modifying its magic from CHROMEOS to CORRUPTD.
824
825 Args:
826 usb_dev: A string of USB stick path on the host, like '/dev/sdc'.
827 """
828 self._modify_usb_kernel(usb_dev, self.CHROMEOS_MAGIC,
829 self.CORRUPTED_MAGIC)
830
831
832 def restore_usb_kernel(self, usb_dev):
833 """Restore USB kernel by modifying its magic from CORRUPTD to CHROMEOS.
834
835 Args:
836 usb_dev: A string of USB stick path on the host, like '/dev/sdc'.
837 """
838 self._modify_usb_kernel(usb_dev, self.CORRUPTED_MAGIC,
839 self.CHROMEOS_MAGIC)
840
841
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800842 def _call_action(self, action_tuple):
843 """Call the action function with/without arguments.
844
845 Args:
846 action_tuple: A function, or a tuple which consisted of a function
847 and its arguments (if any).
848
849 Returns:
850 The result value of the action function.
851 """
852 if isinstance(action_tuple, tuple):
853 action = action_tuple[0]
854 args = action_tuple[1:]
855 if callable(action):
856 logging.info('calling %s with parameter %s' % (
Tom Wai-Hong Tame8f291a2011-12-08 22:03:53 +0800857 str(action), str(action_tuple[1])))
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800858 return action(*args)
859 else:
860 logging.info('action is not callable!')
861 else:
862 action = action_tuple
863 if action is not None:
864 if callable(action):
Tom Wai-Hong Tame8f291a2011-12-08 22:03:53 +0800865 logging.info('calling %s' % str(action))
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800866 return action()
867 else:
868 logging.info('action is not callable!')
869
870 return None
871
872
Tom Wai-Hong Tam78709592011-12-19 11:16:50 +0800873 def run_shutdown_process(self, shutdown_action, pre_power_action=None,
874 post_power_action=None):
875 """Run shutdown_action(), which makes DUT shutdown, and power it on.
876
877 Args:
878 shutdown_action: a function which makes DUT shutdown, like pressing
879 power key.
880 pre_power_action: a function which is called before next power on.
881 post_power_action: a function which is called after next power on.
882
883 Raises:
884 error.TestFail: if the shutdown_action() failed to turn DUT off.
885 """
886 self._call_action(shutdown_action)
887 logging.info('Wait to ensure DUT shut down...')
888 try:
889 self.wait_for_client()
890 raise error.TestFail(
891 'Should shut the device down after calling %s.' %
892 str(shutdown_action))
893 except AssertionError:
894 logging.info(
895 'DUT is surely shutdown. We are going to power it on again...')
896
897 if pre_power_action:
898 self._call_action(pre_power_action)
Tom Wai-Hong Tam610262a2012-01-12 14:16:53 +0800899 self.servo.power_short_press()
Tom Wai-Hong Tam78709592011-12-19 11:16:50 +0800900 if post_power_action:
901 self._call_action(post_power_action)
902
903
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800904 def register_faft_template(self, template):
905 """Register FAFT template, the default FAFT_STEP of each step.
906
Tom Wai-Hong Tam109f63c2011-12-08 14:58:27 +0800907 Any missing field falls back to the original faft_template.
908
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800909 Args:
910 template: A FAFT_STEP dict.
911 """
Tom Wai-Hong Tam109f63c2011-12-08 14:58:27 +0800912 self._faft_template.update(template)
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800913
914
915 def register_faft_sequence(self, sequence):
916 """Register FAFT sequence.
917
918 Args:
919 sequence: A FAFT_SEQUENCE array which consisted of FAFT_STEP dicts.
920 """
921 self._faft_sequence = sequence
922
923
Tom Wai-Hong Tam2c50dff2011-11-11 07:01:01 +0800924 def run_faft_step(self, step, no_reboot=False):
925 """Run a single FAFT step.
926
927 Any missing field falls back to faft_template. An empty step means
928 running the default faft_template.
929
930 Args:
931 step: A FAFT_STEP dict.
932 no_reboot: True to prevent running reboot_action and firmware_action.
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800933
934 Raises:
Tom Wai-Hong Tama9c1a502011-11-10 06:39:26 +0800935 error.TestFail: An error when the test failed.
Tom Wai-Hong Tamd8445dc2011-12-15 09:00:04 +0800936 error.TestError: An error when the given step is not valid.
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800937 """
Tom Wai-Hong Tamd8445dc2011-12-15 09:00:04 +0800938 FAFT_STEP_KEYS = ('state_checker', 'userspace_action', 'reboot_action',
939 'firmware_action', 'install_deps_after_boot')
940
Tom Wai-Hong Tam2c50dff2011-11-11 07:01:01 +0800941 test = {}
942 test.update(self._faft_template)
943 test.update(step)
944
Tom Wai-Hong Tamd8445dc2011-12-15 09:00:04 +0800945 for key in test:
946 if key not in FAFT_STEP_KEYS:
Tom Wai-Hong Tam78709592011-12-19 11:16:50 +0800947 raise error.TestError('Invalid key in FAFT step: %s', key)
Tom Wai-Hong Tamd8445dc2011-12-15 09:00:04 +0800948
Tom Wai-Hong Tam2c50dff2011-11-11 07:01:01 +0800949 if test['state_checker']:
950 if not self._call_action(test['state_checker']):
951 raise error.TestFail('State checker failed!')
952
953 self._call_action(test['userspace_action'])
954
955 # Don't run reboot_action and firmware_action if no_reboot is True.
956 if not no_reboot:
957 self._call_action(test['reboot_action'])
958 self.wait_for_client_offline()
959 self._call_action(test['firmware_action'])
960
961 if 'install_deps_after_boot' in test:
962 self.wait_for_client(
963 install_deps=test['install_deps_after_boot'])
964 else:
965 self.wait_for_client()
966
967
968 def run_faft_sequence(self):
969 """Run FAFT sequence which was previously registered."""
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800970 sequence = self._faft_sequence
Tom Wai-Hong Tame8f291a2011-12-08 22:03:53 +0800971 index = 1
Tom Wai-Hong Tam2c50dff2011-11-11 07:01:01 +0800972 for step in sequence:
Tom Wai-Hong Tame8f291a2011-12-08 22:03:53 +0800973 logging.info('======== Running FAFT sequence step %d ========' %
974 index)
Tom Wai-Hong Tam2c50dff2011-11-11 07:01:01 +0800975 # Don't reboot in the last step.
976 self.run_faft_step(step, no_reboot=(step is sequence[-1]))
Tom Wai-Hong Tame8f291a2011-12-08 22:03:53 +0800977 index += 1