mode_switcher.py revision f9ded098bc87317cc0058823894f4bc4914cb9b9
1# Copyright 2015 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 logging 6import time 7 8 9class _BaseFwBypasser(object): 10 """Base class that controls bypass logic for firmware screens.""" 11 12 def __init__(self, servo, faft_config): 13 self.servo = servo 14 self.faft_config = faft_config 15 16 17 def bypass_dev_mode(self): 18 """Bypass the dev mode firmware logic to boot internal image.""" 19 raise NotImplementedError 20 21 22 def bypass_dev_boot_usb(self): 23 """Bypass the dev mode firmware logic to boot USB.""" 24 raise NotImplementedError 25 26 27 def bypass_rec_mode(self): 28 """Bypass the rec mode firmware logic to boot USB.""" 29 raise NotImplementedError 30 31 32 def trigger_dev_to_rec(self): 33 """Trigger to the rec mode from the dev screen.""" 34 raise NotImplementedError 35 36 37 def trigger_rec_to_dev(self): 38 """Trigger to the dev mode from the rec screen.""" 39 raise NotImplementedError 40 41 42 def trigger_dev_to_normal(self): 43 """Trigger to the normal mode from the dev screen.""" 44 raise NotImplementedError 45 46 47class _CtrlDBypasser(_BaseFwBypasser): 48 """Controls bypass logic via Ctrl-D combo.""" 49 50 def bypass_dev_mode(self): 51 """Bypass the dev mode firmware logic to boot internal image.""" 52 time.sleep(self.faft_config.firmware_screen) 53 self.servo.ctrl_d() 54 55 56 def bypass_dev_boot_usb(self): 57 """Bypass the dev mode firmware logic to boot USB.""" 58 time.sleep(self.faft_config.firmware_screen) 59 self.servo.ctrl_u() 60 61 62 def bypass_rec_mode(self): 63 """Bypass the rec mode firmware logic to boot USB.""" 64 self.servo.switch_usbkey('host') 65 time.sleep(self.faft_config.usb_plug) 66 self.servo.switch_usbkey('dut') 67 68 69 def trigger_dev_to_rec(self): 70 """Trigger to the rec mode from the dev screen.""" 71 time.sleep(self.faft_config.firmware_screen) 72 73 # Pressing Enter for too long triggers a second key press. 74 # Let's press it without delay 75 self.servo.enter_key(press_secs=0) 76 77 # For Alex/ZGB, there is a dev warning screen in text mode. 78 # Skip it by pressing Ctrl-D. 79 if self.faft_config.need_dev_transition: 80 time.sleep(self.faft_config.legacy_text_screen) 81 self.servo.ctrl_d() 82 83 84 def trigger_rec_to_dev(self): 85 """Trigger to the dev mode from the rec screen.""" 86 time.sleep(self.faft_config.firmware_screen) 87 self.servo.ctrl_d() 88 time.sleep(self.faft_config.confirm_screen) 89 if self.faft_config.rec_button_dev_switch: 90 logging.info('RECOVERY button pressed to switch to dev mode') 91 self.servo.toggle_recovery_switch() 92 else: 93 logging.info('ENTER pressed to switch to dev mode') 94 self.servo.enter_key() 95 96 97 def trigger_dev_to_normal(self): 98 """Trigger to the normal mode from the dev screen.""" 99 time.sleep(self.faft_config.firmware_screen) 100 self.servo.enter_key() 101 time.sleep(self.faft_config.confirm_screen) 102 self.servo.enter_key() 103 104 105class _JetstreamBypasser(_BaseFwBypasser): 106 """Controls bypass logic of Jetstream devices.""" 107 108 def bypass_dev_mode(self): 109 """Bypass the dev mode firmware logic to boot internal image.""" 110 # Jetstream does nothing to bypass. 111 pass 112 113 114 def bypass_dev_boot_usb(self): 115 """Bypass the dev mode firmware logic to boot USB.""" 116 # TODO: Confirm if it is a proper way to trigger dev boot USB. 117 # We can't verify it this time due to a bug that always boots into 118 # USB on dev mode. 119 self.servo.enable_development_mode() 120 self.servo.switch_usbkey('dut') 121 time.sleep(self.faft_config.firmware_screen) 122 self.servo.toggle_development_switch() 123 124 125 def bypass_rec_mode(self): 126 """Bypass the rec mode firmware logic to boot USB.""" 127 self.servo.switch_usbkey('host') 128 time.sleep(self.faft_config.usb_plug) 129 self.servo.switch_usbkey('dut') 130 131 132 def trigger_dev_to_rec(self): 133 """Trigger to the rec mode from the dev screen.""" 134 # Jetstream does not have this triggering logic. 135 raise NotImplementedError 136 137 138 def trigger_rec_to_dev(self): 139 """Trigger to the dev mode from the rec screen.""" 140 self.servo.disable_development_mode() 141 time.sleep(self.faft_config.firmware_screen) 142 self.servo.toggle_development_switch() 143 144 145 def trigger_dev_to_normal(self): 146 """Trigger to the normal mode from the dev screen.""" 147 # Jetstream does not have this triggering logic. 148 raise NotImplementedError 149 150 151def _create_fw_bypasser(servo, faft_config): 152 """Creates a proper firmware bypasser. 153 154 @param servo: A servo object controlling the servo device. 155 @param faft_config: A FAFT config object, which describes the type of 156 firmware bypasser. 157 """ 158 bypasser_type = faft_config.fw_bypasser_type 159 if bypasser_type == 'ctrl_d_bypasser': 160 logging.info('Create a CtrlDBypasser') 161 return _CtrlDBypasser(servo, faft_config) 162 if bypasser_type == 'jetstream_bypasser': 163 logging.info('Create a JetstreamBypasser') 164 return _JetstreamBypasser(servo, faft_config) 165 else: 166 raise NotImplementedError('Not supported fw_bypasser_type: %s', 167 bypasser_type) 168 169 170class _BaseModeSwitcher(object): 171 """Base class that controls firmware mode switching.""" 172 173 def __init__(self, faft_framework): 174 self.faft_framework = faft_framework 175 self.faft_client = faft_framework.faft_client 176 self.servo = faft_framework.servo 177 self.faft_config = faft_framework.faft_config 178 self.checkers = faft_framework.checkers 179 self.bypasser = _create_fw_bypasser(self.servo, self.faft_config) 180 self._backup_mode = None 181 182 183 def setup_mode(self, mode): 184 """Setup for the requested mode. 185 186 It makes sure the system in the requested mode. If not, it tries to 187 do so. 188 189 @param mode: A string of mode, one of 'normal', 'dev', or 'rec'. 190 """ 191 if not self.checkers.mode_checker(mode): 192 logging.info('System not in expected %s mode. Reboot into it.', 193 mode) 194 if self._backup_mode is None: 195 # Only resume to normal/dev mode after test, not recovery. 196 self._backup_mode = 'dev' if mode == 'normal' else 'normal' 197 self.reboot_to_mode(mode) 198 199 200 def restore_mode(self): 201 """Restores original dev mode status if it has changed.""" 202 if self._backup_mode is not None: 203 self.reboot_to_mode(self._backup_mode) 204 205 206 def reboot_to_mode(self, to_mode, from_mode=None, sync_before_boot=True, 207 wait_for_dut_up=True): 208 """Reboot and execute the mode switching sequence. 209 210 @param to_mode: The target mode, one of 'normal', 'dev', or 'rec'. 211 @param from_mode: The original mode, optional, one of 'normal, 'dev', 212 or 'rec'. 213 @param sync_before_boot: True to sync to disk before booting. 214 @param wait_for_dut_up: True to wait DUT online again. False to do the 215 reboot and mode switching sequence only and may 216 need more operations to pass the firmware 217 screen. 218 """ 219 logging.info('-[ModeSwitcher]-[ start reboot_to_mode(%r, %r, %r) ]-', 220 to_mode, from_mode, wait_for_dut_up) 221 if sync_before_boot: 222 self.faft_framework.blocking_sync() 223 if to_mode == 'rec': 224 self._enable_rec_mode_and_reboot(usb_state='dut') 225 if wait_for_dut_up: 226 self.bypasser.bypass_rec_mode() 227 self.faft_framework.wait_for_client() 228 229 elif to_mode == 'dev': 230 self._enable_dev_mode_and_reboot() 231 if wait_for_dut_up: 232 self.bypasser.bypass_dev_mode() 233 self.faft_framework.wait_for_client() 234 235 elif to_mode == 'normal': 236 self._enable_normal_mode_and_reboot() 237 if wait_for_dut_up: 238 self.faft_framework.wait_for_client() 239 240 else: 241 raise NotImplementedError( 242 'Not supported mode switching from %s to %s' % 243 (str(from_mode), to_mode)) 244 logging.info('-[ModeSwitcher]-[ end reboot_to_mode(%r, %r, %r) ]-', 245 to_mode, from_mode, wait_for_dut_up) 246 247 248 def mode_aware_reboot(self, reboot_type=None, reboot_method=None, 249 sync_before_boot=True, wait_for_dut_up=True): 250 """Uses a mode-aware way to reboot DUT. 251 252 For example, if DUT is in dev mode, it requires pressing Ctrl-D to 253 bypass the developer screen. 254 255 @param reboot_type: A string of reboot type, one of 'warm', 'cold', or 256 'custom'. Default is a warm reboot. 257 @param reboot_method: A custom method to do the reboot. Only use it if 258 reboot_type='custom'. 259 @param sync_before_boot: True to sync to disk before booting. 260 @param wait_for_dut_up: True to wait DUT online again. False to do the 261 reboot only. 262 """ 263 if reboot_type is None or reboot_type == 'warm': 264 reboot_method = self.servo.get_power_state_controller().warm_reset 265 elif reboot_type == 'cold': 266 reboot_method = self.servo.get_power_state_controller().reset 267 elif reboot_type != 'custom': 268 raise NotImplementedError('Not supported reboot_type: %s', 269 reboot_type) 270 271 logging.info("-[ModeSwitcher]-[ start mode_aware_reboot(%r, %s, ..) ]-", 272 reboot_type, reboot_method.__name__) 273 is_normal = is_dev = False 274 if sync_before_boot: 275 if wait_for_dut_up: 276 is_normal = self.checkers.mode_checker('normal') 277 is_dev = self.checkers.mode_checker('dev') 278 boot_id = self.faft_framework.get_bootid() 279 self.faft_framework.blocking_sync() 280 reboot_method() 281 if sync_before_boot: 282 self.faft_framework.wait_for_client_offline(orig_boot_id=boot_id) 283 if wait_for_dut_up: 284 # For encapsulating the behavior of skipping firmware screen, 285 # e.g. requiring unplug and plug USB, the variants are not 286 # hard coded in tests. We keep this logic in this 287 # mode_aware_reboot method. 288 if not is_dev: 289 # In the normal/recovery boot flow, replugging USB does not 290 # affect the boot flow. But when something goes wrong, like 291 # firmware corrupted, it automatically leads to a recovery USB 292 # boot. 293 self.servo.switch_usbkey('host') 294 if not is_normal: 295 self.bypasser.bypass_dev_mode() 296 if not is_dev: 297 self.bypasser.bypass_rec_mode() 298 self.faft_framework.wait_for_kernel_up() 299 logging.info("-[ModeSwitcher]-[ end mode_aware_reboot(%r, %s, ..) ]-", 300 reboot_type, reboot_method.__name__) 301 302 303 def _enable_rec_mode_and_reboot(self, usb_state=None): 304 """Switch to rec mode and reboot. 305 306 This method emulates the behavior of the old physical recovery switch, 307 i.e. switch ON + reboot + switch OFF, and the new keyboard controlled 308 recovery mode, i.e. just press Power + Esc + Refresh. 309 310 @param usb_state: A string, one of 'dut', 'host', or 'off'. 311 """ 312 psc = self.servo.get_power_state_controller() 313 psc.power_off() 314 if usb_state: 315 self.servo.switch_usbkey(usb_state) 316 psc.power_on(psc.REC_ON) 317 318 319 def _disable_rec_mode_and_reboot(self, usb_state=None): 320 """Disable the rec mode and reboot. 321 322 It is achieved by calling power state controller to do a normal 323 power on. 324 """ 325 psc = self.servo.get_power_state_controller() 326 psc.power_off() 327 psc.power_on(psc.REC_OFF) 328 329 330 def _enable_dev_mode_and_reboot(self): 331 """Switch to developer mode and reboot.""" 332 raise NotImplementedError 333 334 335 def _enable_normal_mode_and_reboot(self): 336 """Switch to normal mode and reboot.""" 337 raise NotImplementedError 338 339 340 # Redirects the following methods to FwBypasser 341 def bypass_dev_mode(self): 342 """Bypass the dev mode firmware logic to boot internal image.""" 343 self.bypasser.bypass_dev_mode() 344 345 346 def bypass_dev_boot_usb(self): 347 """Bypass the dev mode firmware logic to boot USB.""" 348 self.bypasser.bypass_dev_boot_usb() 349 350 351 def bypass_rec_mode(self): 352 """Bypass the rec mode firmware logic to boot USB.""" 353 self.bypasser.bypass_rec_mode() 354 355 356 def trigger_dev_to_rec(self): 357 """Trigger to the rec mode from the dev screen.""" 358 self.bypasser.trigger_dev_to_rec() 359 360 361 def trigger_rec_to_dev(self): 362 """Trigger to the dev mode from the rec screen.""" 363 self.bypasser.trigger_rec_to_dev() 364 365 366 def trigger_dev_to_normal(self): 367 """Trigger to the normal mode from the dev screen.""" 368 self.bypasser.trigger_dev_to_normal() 369 370 371class _PhysicalButtonSwitcher(_BaseModeSwitcher): 372 """Class that switches firmware mode via physical button.""" 373 374 def _enable_dev_mode_and_reboot(self): 375 """Switch to developer mode and reboot.""" 376 self.servo.enable_development_mode() 377 self.faft_client.system.run_shell_command( 378 'chromeos-firmwareupdate --mode todev && reboot') 379 380 381 def _enable_normal_mode_and_reboot(self): 382 """Switch to normal mode and reboot.""" 383 self.servo.disable_development_mode() 384 self.faft_client.system.run_shell_command( 385 'chromeos-firmwareupdate --mode tonormal && reboot') 386 387 388class _KeyboardDevSwitcher(_BaseModeSwitcher): 389 """Class that switches firmware mode via keyboard combo.""" 390 391 def _enable_dev_mode_and_reboot(self): 392 """Switch to developer mode and reboot.""" 393 logging.info("Enabling keyboard controlled developer mode") 394 # Rebooting EC with rec mode on. Should power on AP. 395 # Plug out USB disk for preventing recovery boot without warning 396 self._enable_rec_mode_and_reboot(usb_state='host') 397 self.faft_framework.wait_for_client_offline() 398 self.bypasser.trigger_rec_to_dev() 399 400 401 def _enable_normal_mode_and_reboot(self): 402 """Switch to normal mode and reboot.""" 403 logging.info("Disabling keyboard controlled developer mode") 404 self._disable_rec_mode_and_reboot() 405 self.faft_framework.wait_for_client_offline() 406 self.bypasser.trigger_dev_to_normal() 407 408 409class _JetstreamSwitcher(_BaseModeSwitcher): 410 """Class that switches firmware mode in Jetstream devices.""" 411 412 def _enable_dev_mode_and_reboot(self): 413 """Switch to developer mode and reboot.""" 414 logging.info("Enabling Jetstream developer mode") 415 self._enable_rec_mode_and_reboot(usb_state='host') 416 self.faft_framework.wait_for_client_offline() 417 self.bypasser.trigger_rec_to_dev() 418 419 420 def _enable_normal_mode_and_reboot(self): 421 """Switch to normal mode and reboot.""" 422 logging.info("Disabling Jetstream developer mode") 423 self.servo.disable_development_mode() 424 self._enable_rec_mode_and_reboot(usb_state='host') 425 time.sleep(self.faft_config.firmware_screen) 426 self._disable_rec_mode_and_reboot(usb_state='host') 427 428 429def create_mode_switcher(faft_framework): 430 """Creates a proper mode switcher. 431 432 @param faft_framework: The main FAFT framework object. 433 """ 434 switcher_type = faft_framework.faft_config.mode_switcher_type 435 if switcher_type == 'physical_button_switcher': 436 logging.info('Create a PhysicalButtonSwitcher') 437 return _PhysicalButtonSwitcher(faft_framework) 438 elif switcher_type == 'keyboard_dev_switcher': 439 logging.info('Create a KeyboardDevSwitcher') 440 return _KeyboardDevSwitcher(faft_framework) 441 elif switcher_type == 'jetstream_switcher': 442 logging.info('Create a JetstreamSwitcher') 443 return _JetstreamSwitcher(faft_framework) 444 else: 445 raise NotImplementedError('Not supported mode_switcher_type: %s', 446 switcher_type) 447