mode_switcher.py revision ed4d67be4d0faa1a267d33e86905df1153d78333
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 105def _create_fw_bypasser(servo, faft_config): 106 """Creates a proper firmware bypasser. 107 108 @param servo: A servo object controlling the servo device. 109 @param faft_config: A FAFT config object, which describes the type of 110 firmware bypasser. 111 """ 112 bypasser_type = faft_config.fw_bypasser_type 113 if bypasser_type == 'ctrl_d_bypasser': 114 logging.info('Create a CtrlDBypasser') 115 return _CtrlDBypasser(servo, faft_config) 116 else: 117 raise NotImplementedError('Not supported fw_bypasser_type: %s', 118 bypasser_type) 119 120 121class _BaseModeSwitcher(object): 122 """Base class that controls firmware mode switching.""" 123 124 def __init__(self, faft_framework): 125 self.faft_framework = faft_framework 126 self.faft_client = faft_framework.faft_client 127 self.servo = faft_framework.servo 128 self.faft_config = faft_framework.faft_config 129 self.checkers = faft_framework.checkers 130 self.bypasser = _create_fw_bypasser(self.servo, self.faft_config) 131 self._backup_mode = None 132 133 134 def setup_mode(self, mode): 135 """Setup for the requested mode. 136 137 It makes sure the system in the requested mode. If not, it tries to 138 do so. 139 140 @param mode: A string of mode, one of 'normal', 'dev', or 'rec'. 141 """ 142 if not self.checkers.mode_checker(mode): 143 logging.info('System not in expected %s mode. Reboot into it.', 144 mode) 145 if self._backup_mode is None: 146 # Only resume to normal/dev mode after test, not recovery. 147 self._backup_mode = 'dev' if mode == 'normal' else 'normal' 148 self.reboot_to_mode(mode) 149 150 151 def restore_mode(self): 152 """Restores original dev mode status if it has changed.""" 153 if self._backup_mode is not None: 154 self.reboot_to_mode(self._backup_mode) 155 156 157 def reboot_to_mode(self, to_mode, from_mode=None, sync_before_boot=True, 158 wait_for_dut_up=True): 159 """Reboot and execute the mode switching sequence. 160 161 @param to_mode: The target mode, one of 'normal', 'dev', or 'rec'. 162 @param from_mode: The original mode, optional, one of 'normal, 'dev', 163 or 'rec'. 164 @param sync_before_boot: True to sync to disk before booting. 165 @param wait_for_dut_up: True to wait DUT online again. False to do the 166 reboot and mode switching sequence only and may 167 need more operations to pass the firmware 168 screen. 169 """ 170 logging.info('-[ModeSwitcher]-[ start reboot_to_mode(%r, %r, %r) ]-', 171 to_mode, from_mode, wait_for_dut_up) 172 if sync_before_boot: 173 self.faft_framework.blocking_sync() 174 if to_mode == 'rec': 175 self._enable_rec_mode_and_reboot(usb_state='dut') 176 if wait_for_dut_up: 177 self.bypasser.bypass_rec_mode() 178 self.faft_framework.wait_for_client() 179 180 elif to_mode == 'dev': 181 self._enable_dev_mode_and_reboot() 182 if wait_for_dut_up: 183 self.bypasser.bypass_dev_mode() 184 self.faft_framework.wait_for_client() 185 186 elif to_mode == 'normal': 187 self._enable_normal_mode_and_reboot() 188 if wait_for_dut_up: 189 self.faft_framework.wait_for_client() 190 191 else: 192 raise NotImplementedError( 193 'Not supported mode switching from %s to %s' % 194 (str(from_mode), to_mode)) 195 logging.info('-[ModeSwitcher]-[ end reboot_to_mode(%r, %r, %r) ]-', 196 to_mode, from_mode, wait_for_dut_up) 197 198 199 def mode_aware_reboot(self, reboot_type=None, reboot_method=None, 200 sync_before_boot=True, wait_for_dut_up=True): 201 """Uses a mode-aware way to reboot DUT. 202 203 For example, if DUT is in dev mode, it requires pressing Ctrl-D to 204 bypass the developer screen. 205 206 @param reboot_type: A string of reboot type, one of 'warm', 'cold', or 207 'custom'. Default is a warm reboot. 208 @param reboot_method: A custom method to do the reboot. Only use it if 209 reboot_type='custom'. 210 @param sync_before_boot: True to sync to disk before booting. 211 @param wait_for_dut_up: True to wait DUT online again. False to do the 212 reboot only. 213 """ 214 if reboot_type is None or reboot_type == 'warm': 215 reboot_method = self.servo.get_power_state_controller().warm_reset 216 elif reboot_type == 'cold': 217 reboot_method = self.servo.get_power_state_controller().reset 218 elif reboot_type != 'custom': 219 raise NotImplementedError('Not supported reboot_type: %s', 220 reboot_type) 221 222 logging.info("-[ModeSwitcher]-[ start mode_aware_reboot(%r, %s, ..) ]-", 223 reboot_type, reboot_method.__name__) 224 is_normal = is_dev = False 225 if sync_before_boot: 226 if wait_for_dut_up: 227 is_normal = self.checkers.mode_checker('normal') 228 is_dev = self.checkers.mode_checker('dev') 229 boot_id = self.faft_framework.get_bootid() 230 self.faft_framework.blocking_sync() 231 reboot_method() 232 if sync_before_boot: 233 self.faft_framework.wait_for_client_offline(orig_boot_id=boot_id) 234 if wait_for_dut_up: 235 # For encapsulating the behavior of skipping firmware screen, 236 # e.g. requiring unplug and plug USB, the variants are not 237 # hard coded in tests. We keep this logic in this 238 # mode_aware_reboot method. 239 if not is_dev: 240 # In the normal/recovery boot flow, replugging USB does not 241 # affect the boot flow. But when something goes wrong, like 242 # firmware corrupted, it automatically leads to a recovery USB 243 # boot. 244 self.servo.switch_usbkey('host') 245 if not is_normal: 246 self.bypasser.bypass_dev_mode() 247 if not is_dev: 248 self.bypasser.bypass_rec_mode() 249 self.faft_framework.wait_for_kernel_up() 250 logging.info("-[ModeSwitcher]-[ end mode_aware_reboot(%r, %s, ..) ]-", 251 reboot_type, reboot_method.__name__) 252 253 254 def _enable_rec_mode_and_reboot(self, usb_state=None): 255 """Switch to rec mode and reboot. 256 257 This method emulates the behavior of the old physical recovery switch, 258 i.e. switch ON + reboot + switch OFF, and the new keyboard controlled 259 recovery mode, i.e. just press Power + Esc + Refresh. 260 261 @param usb_state: A string, one of 'dut', 'host', or 'off'. 262 """ 263 psc = self.servo.get_power_state_controller() 264 psc.power_off() 265 if usb_state: 266 self.servo.switch_usbkey(usb_state) 267 psc.power_on(psc.REC_ON) 268 269 270 def _disable_rec_mode_and_reboot(self, usb_state=None): 271 """Disable the rec mode and reboot. 272 273 It is achieved by calling power state controller to do a normal 274 power on. 275 """ 276 psc = self.servo.get_power_state_controller() 277 psc.power_off() 278 psc.power_on(psc.REC_OFF) 279 280 281 def _enable_dev_mode_and_reboot(self): 282 """Switch to developer mode and reboot.""" 283 raise NotImplementedError 284 285 286 def _enable_normal_mode_and_reboot(self): 287 """Switch to normal mode and reboot.""" 288 raise NotImplementedError 289 290 291 # Redirects the following methods to FwBypasser 292 def bypass_dev_mode(self): 293 """Bypass the dev mode firmware logic to boot internal image.""" 294 self.bypasser.bypass_dev_mode() 295 296 297 def bypass_dev_boot_usb(self): 298 """Bypass the dev mode firmware logic to boot USB.""" 299 self.bypasser.bypass_dev_boot_usb() 300 301 302 def bypass_rec_mode(self): 303 """Bypass the rec mode firmware logic to boot USB.""" 304 self.bypasser.bypass_rec_mode() 305 306 307 def trigger_dev_to_rec(self): 308 """Trigger to the rec mode from the dev screen.""" 309 self.bypasser.trigger_dev_to_rec() 310 311 312 def trigger_rec_to_dev(self): 313 """Trigger to the dev mode from the rec screen.""" 314 self.bypasser.trigger_rec_to_dev() 315 316 317 def trigger_dev_to_normal(self): 318 """Trigger to the normal mode from the dev screen.""" 319 self.bypasser.trigger_dev_to_normal() 320 321 322class _PhysicalButtonSwitcher(_BaseModeSwitcher): 323 """Class that switches firmware mode via physical button.""" 324 325 def _enable_dev_mode_and_reboot(self): 326 """Switch to developer mode and reboot.""" 327 self.servo.enable_development_mode() 328 self.faft_client.system.run_shell_command( 329 'chromeos-firmwareupdate --mode todev && reboot') 330 331 332 def _enable_normal_mode_and_reboot(self): 333 """Switch to normal mode and reboot.""" 334 self.servo.disable_development_mode() 335 self.faft_client.system.run_shell_command( 336 'chromeos-firmwareupdate --mode tonormal && reboot') 337 338 339class _KeyboardDevSwitcher(_BaseModeSwitcher): 340 """Class that switches firmware mode via keyboard combo.""" 341 342 def _enable_dev_mode_and_reboot(self): 343 """Switch to developer mode and reboot.""" 344 logging.info("Enabling keyboard controlled developer mode") 345 # Rebooting EC with rec mode on. Should power on AP. 346 # Plug out USB disk for preventing recovery boot without warning 347 self._enable_rec_mode_and_reboot(usb_state='host') 348 self.faft_framework.wait_for_client_offline() 349 self.bypasser.trigger_rec_to_dev() 350 351 352 def _enable_normal_mode_and_reboot(self): 353 """Switch to normal mode and reboot.""" 354 logging.info("Disabling keyboard controlled developer mode") 355 self._disable_rec_mode_and_reboot() 356 self.faft_framework.wait_for_client_offline() 357 self.bypasser.trigger_dev_to_normal() 358 359 360def create_mode_switcher(faft_framework): 361 """Creates a proper mode switcher. 362 363 @param faft_framework: The main FAFT framework object. 364 """ 365 switcher_type = faft_framework.faft_config.mode_switcher_type 366 if switcher_type == 'physical_button_switcher': 367 logging.info('Create a PhysicalButtonSwitcher') 368 return _PhysicalButtonSwitcher(faft_framework) 369 elif switcher_type == 'keyboard_dev_switcher': 370 logging.info('Create a KeyboardDevSwitcher') 371 return _KeyboardDevSwitcher(faft_framework) 372 else: 373 raise NotImplementedError('Not supported mode_switcher_type: %s', 374 switcher_type) 375