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
8from autotest_lib.client.common_lib import error
9
10DEBOUNCE_STATE = 'debouncing'
11
12class ConnectionError(Exception):
13    """Raised on an error of connecting DUT."""
14    pass
15
16
17class _BaseFwBypasser(object):
18    """Base class that controls bypass logic for firmware screens."""
19
20    # Duration of holding Volume down button to quickly bypass the developer
21    # warning screen in tablets/detachables.
22    HOLD_VOL_DOWN_BUTTON_BYPASS = 3
23
24    def __init__(self, faft_framework):
25        self.faft_framework = faft_framework
26        self.servo = faft_framework.servo
27        self.faft_config = faft_framework.faft_config
28        self.client_host = faft_framework._client
29        self.ec = getattr(faft_framework, 'ec', None)
30
31
32    def bypass_dev_mode(self):
33        """Bypass the dev mode firmware logic to boot internal image."""
34        raise NotImplementedError
35
36
37    def bypass_dev_boot_usb(self):
38        """Bypass the dev mode firmware logic to boot USB."""
39        raise NotImplementedError
40
41
42    def bypass_rec_mode(self):
43        """Bypass the rec mode firmware logic to boot USB."""
44        raise NotImplementedError
45
46
47    def trigger_dev_to_rec(self):
48        """Trigger to the rec mode from the dev screen."""
49        raise NotImplementedError
50
51
52    def trigger_rec_to_dev(self):
53        """Trigger to the dev mode from the rec screen."""
54        raise NotImplementedError
55
56
57    def trigger_dev_to_normal(self):
58        """Trigger to the normal mode from the dev screen."""
59        raise NotImplementedError
60
61
62    # This is used as a workaround of a bug in RO - DUT does not supply
63    # Vbus when in SRC_ACCESSORY state (we set servo to snk before booting
64    # to recovery due to the assumption of no PD in RO). It causes that DUT can
65    # not see USB Stick in recovery mode(RO) despite being DFP(b/159938441).
66    # The bug in RO has been fixed in 251212fb.
67    # Some boards already have it in RO, so the issue does not appear
68    def check_vbus_and_pd_state(self):
69        """Perform PD power and data swap, if DUT is SRC and doesn't supply
70        Vbus"""
71        if self.ec and self.faft_config.ec_ro_vbus_bug:
72            time.sleep(self.faft_framework.PD_RESYNC_DELAY)
73            servo_pr_role = self.servo.get_servo_v4_role()
74            if servo_pr_role == 'snk':
75                mv = self.servo.get_vbus_voltage()
76                # Despite the faft_config, make sure the issue occurs -
77                # servo is snk and vbus is not supplied.
78                if mv is not None and mv < self.servo.VBUS_THRESHOLD:
79                    # Make servo SRC to supply Vbus correctly
80                    self.servo.set_servo_v4_role('src')
81                    time.sleep(self.faft_framework.PD_RESYNC_DELAY)
82            # After reboot, EC can be UFP so check that
83            if not self.ec.is_dfp():
84                # EC is UFP, perform PD Data Swap
85                self.ec.send_command("pd 0 swap data")
86                time.sleep(self.faft_framework.PD_RESYNC_DELAY)
87                # Make sure EC is DFP now
88                if not self.ec.is_dfp():
89                    # EC is still UFP
90                    raise error.TestError('DUT is not DFP in recovery mode.')
91
92
93class _KeyboardBypasser(_BaseFwBypasser):
94    """Controls bypass logic via keyboard shortcuts for menu UI."""
95
96    def bypass_dev_mode(self):
97        """Bypass the dev mode firmware logic to boot internal image.
98
99        Press Ctrl-D repeatedly. To obtain a low firmware boot time, pressing
100        Ctrl+D for every half second until firmware_screen delay has been
101        reached.
102        """
103        logging.info("Pressing Ctrl-D.")
104        # At maximum, device waits for twice of firmware_screen delay to
105        # bypass the Dev screen.
106        timeout = time.time() + (self.faft_config.firmware_screen * 2)
107        while time.time() < timeout:
108            self.servo.ctrl_d()
109            time.sleep(0.5)
110            if self.client_host.ping_wait_up(timeout=0.1):
111                break
112
113
114    def bypass_dev_boot_usb(self):
115        """Bypass the dev mode firmware logic to boot USB."""
116        self.faft_framework.wait_for('firmware_screen', 'Pressing ctrl+u')
117        self.servo.ctrl_u()
118
119
120    def bypass_dev_default_boot(self):
121        """Bypass the dev mode firmware logic to boot from default target."""
122        self.faft_framework.wait_for('firmware_screen', 'Pressing enter')
123        self.servo.enter_key()
124
125
126    def bypass_rec_mode(self):
127        """Bypass the rec mode firmware logic to boot USB."""
128        self.servo.switch_usbkey('host')
129        self.faft_framework.wait_for('usb_plug', 'Switching usb key to DUT')
130        self.check_vbus_and_pd_state()
131        self.servo.switch_usbkey('dut')
132        logging.info('Enabled dut_sees_usb')
133        if not self.client_host.ping_wait_up(
134                timeout=self.faft_config.delay_reboot_to_ping):
135            logging.info('ping timed out, try REC_ON')
136            psc = self.servo.get_power_state_controller()
137            psc.power_on(psc.REC_ON)
138            # Check Vbus after reboot again
139            self.check_vbus_and_pd_state()
140
141
142    def trigger_dev_to_rec(self):
143        """Trigger to the to-norm screen from the dev screen."""
144        self.faft_framework.wait_for('firmware_screen', 'Pressing ctrl+s')
145        self.servo.ctrl_s()
146
147
148    def trigger_rec_to_dev(self):
149        """Trigger to the dev mode from the rec screen."""
150        self.faft_framework.wait_for('firmware_screen', 'Pressing ctrl+d')
151        self.servo.ctrl_d()
152        self.faft_framework.wait_for('confirm_screen', 'Pressing button to switch to dev mode')
153        if self.faft_config.rec_button_dev_switch:
154            logging.info('RECOVERY button pressed to switch to dev mode')
155            self.servo.toggle_recovery_switch()
156        elif self.faft_config.power_button_dev_switch:
157            logging.info('POWER button pressed to switch to dev mode')
158            self.servo.power_normal_press()
159        else:
160            logging.info('ENTER pressed to switch to dev mode')
161            self.servo.enter_key()
162
163
164    def trigger_dev_to_normal(self):
165        """Trigger to the normal mode from the dev screen."""
166        # Navigate to to-norm screen
167        self.faft_framework.wait_for('firmware_screen', 'Pressing ctrl+s')
168        self.servo.ctrl_s()
169        # Select "Confirm"
170        self.faft_framework.wait_for('confirm_screen', 'Pressing enter')
171        self.servo.enter_key()
172
173
174class _LegacyKeyboardBypasser(_KeyboardBypasser):
175    """Controls bypass logic via keyboard shortcuts for legacy clamshell UI."""
176
177    def trigger_dev_to_rec(self):
178        """Trigger to the to-norm screen from the dev screen."""
179        self.faft_framework.wait_for('firmware_screen', 'Pressing enter')
180        self.servo.enter_key()
181
182    def trigger_dev_to_normal(self):
183        """Trigger to the normal mode from the dev screen."""
184        self.faft_framework.wait_for('firmware_screen', 'Pressing enter')
185        self.servo.enter_key()
186        self.faft_framework.wait_for('confirm_screen', 'Pressing enter')
187        self.servo.enter_key()
188
189
190class _JetstreamBypasser(_BaseFwBypasser):
191    """Controls bypass logic of Jetstream devices."""
192
193    def bypass_dev_mode(self):
194        """Bypass the dev mode firmware logic to boot internal image."""
195        # Jetstream does nothing to bypass.
196        pass
197
198
199    def bypass_dev_boot_usb(self):
200        """Bypass the dev mode firmware logic to boot USB."""
201        self.servo.switch_usbkey('dut')
202        self.faft_framework.wait_for('firmware_screen', 'Toggling development switch')
203        self.servo.toggle_development_switch()
204
205
206    def bypass_rec_mode(self):
207        """Bypass the rec mode firmware logic to boot USB."""
208        self.servo.switch_usbkey('host')
209        self.faft_framework.wait_for('usb_plug', 'Switching usb key to DUT')
210        self.check_vbus_and_pd_state()
211        self.servo.switch_usbkey('dut')
212        if not self.client_host.ping_wait_up(
213                timeout=self.faft_config.delay_reboot_to_ping):
214            psc = self.servo.get_power_state_controller()
215            psc.power_on(psc.REC_ON)
216            # Check Vbus after reboot again
217            self.check_vbus_and_pd_state()
218
219
220    def trigger_dev_to_rec(self):
221        """Trigger to the rec mode from the dev screen."""
222        # Jetstream does not have this triggering logic.
223        raise NotImplementedError
224
225
226    def trigger_rec_to_dev(self):
227        """Trigger to the dev mode from the rec screen."""
228        self.servo.disable_development_mode()
229        self.faft_framework.wait_for('firmware_screen', 'Toggling development switch')
230        self.servo.toggle_development_switch()
231
232
233    def trigger_dev_to_normal(self):
234        """Trigger to the normal mode from the dev screen."""
235        # Jetstream does not have this triggering logic.
236        raise NotImplementedError
237
238
239class _TabletDetachableBypasser(_BaseFwBypasser):
240    """Controls bypass logic of tablet/ detachable chromebook devices."""
241
242    def set_button(self, button, duration, info):
243        """Helper method that sets the button hold time for UI selections"""
244        self.servo.set_nocheck(button, duration)
245        self.faft_framework.wait_for('confirm_screen')
246        logging.info(info)
247
248
249    def bypass_dev_boot_usb(self):
250        """Bypass the dev mode firmware logic to boot USB.
251
252        On tablets/ detachables, recovery entered by pressing pwr, vol up
253        & vol down buttons for 10s.
254           Menu options seen in DEVELOPER WARNING screen:
255                 Developer Options
256                 Show Debug Info
257                 Enable Root Verification
258                 Power Off*
259                 Language
260           Menu options seen in DEV screen:
261                 Boot legacy BIOS
262                 Boot USB image
263                 Boot developer image*
264                 Cancel
265                 Power off
266                 Language
267
268        Vol up button selects previous item, vol down button selects
269        next item and pwr button selects current activated item.
270
271        Note: if dev_default_boot=usb, the default selection will start on USB,
272        and this will move up one to legacy boot instead.
273        """
274        self.trigger_dev_screen()
275        self.faft_framework.wait_for('firmware_screen', 'Pressing volume up')
276        self.set_button('volume_up_hold', 100, ('Selecting power as'
277                        ' enter key to select Boot USB Image'))
278        self.servo.power_short_press()
279
280    def bypass_dev_default_boot(self):
281        """Open the Developer Options menu, and accept the default boot device
282
283           Menu options seen in DEVELOPER WARNING screen:
284                 Developer Options
285                 Show Debug Info
286                 Enable Root Verification
287                 Power Off*
288                 Language
289           Menu options seen in DEV screen:
290                 Boot legacy BIOS*      (default if dev_default_boot=legacy)
291                 Boot USB image*        (default if dev_default_boot=usb)
292                 Boot developer image*  (default if dev_default_boot=disk)
293                 Cancel
294                 Power off
295                 Language
296
297        Vol up button selects previous item, vol down button selects
298        next item and pwr button selects current activated item.
299        """
300        self.trigger_dev_screen()
301        self.faft_framework.wait_for('firmware_screen', 'Pressing power button')
302        logging.info('Selecting power as enter key to accept the default'
303                     ' boot option.')
304        self.servo.power_short_press()
305
306    def bypass_rec_mode(self):
307        """Bypass the rec mode firmware logic to boot USB."""
308        self.servo.switch_usbkey('host')
309        self.faft_framework.wait_for('usb_plug', 'Switching usb key to DUT')
310        self.check_vbus_and_pd_state()
311        self.servo.switch_usbkey('dut')
312        logging.info('Enabled dut_sees_usb')
313        if not self.client_host.ping_wait_up(
314                timeout=self.faft_config.delay_reboot_to_ping):
315            logging.info('ping timed out, try REC_ON')
316            psc = self.servo.get_power_state_controller()
317            psc.power_on(psc.REC_ON)
318            # Check Vbus after reboot again
319            self.check_vbus_and_pd_state()
320
321
322    def bypass_dev_mode(self):
323        """Bypass the developer warning screen immediately to boot into
324        internal disk.
325
326        On tablets/detachables, press & holding the Volume down button for
327        3-seconds will quickly bypass the developer warning screen.
328        """
329        # Unit for the "volume_down_hold" console command is msec.
330        duration = (self.HOLD_VOL_DOWN_BUTTON_BYPASS + 0.1) * 1000
331        logging.info("Press and hold volume down button for %.1f seconds to "
332                     "immediately bypass the Developer warning screen.",
333                     self.HOLD_VOL_DOWN_BUTTON_BYPASS + 0.1)
334        # At maximum, device waits for twice of firmware_screen delay to
335        # bypass the Dev screen.
336        timeout = time.time() + (self.faft_config.firmware_screen * 2)
337        # To obtain a low firmware boot time, volume_down button pressed for
338        # every 3.1 seconds until firmware_screen delay has been reached.
339        while time.time() < timeout:
340            self.servo.set_nocheck('volume_down_hold', duration)
341            # After pressing 'volume_down_hold' button, wait for 0.1 seconds
342            # before start pressing the button for next iteration.
343            time.sleep(0.1)
344            if self.client_host.ping_wait_up(timeout=0.1):
345                break
346
347
348    def trigger_dev_screen(self):
349        """Helper method that transitions from DEVELOPER WARNING to DEV screen
350
351           Menu options seen in DEVELOPER WARNING screen:
352                 Developer Options
353                 Show Debug Info
354                 Enable Root Verification
355                 Power Off*
356                 Language
357           Menu options seen in DEV screen:
358                 Boot legacy BIOS
359                 Boot USB image
360                 Boot developer image*
361                 Cancel
362                 Power off
363                 Language
364        Vol up button selects previous item, vol down button selects
365        next item and pwr button selects current activated item.
366        """
367        self.faft_framework.wait_for('firmware_screen', 'Pressing volume up')
368        self.servo.set_nocheck('volume_up_hold', 100)
369        self.faft_framework.wait_for('confirm_screen', 'Pressing volume up')
370        self.servo.set_nocheck('volume_up_hold', 100)
371        self.faft_framework.wait_for('confirm_screen', 'Pressing volume up')
372        self.set_button('volume_up_hold', 100, ('Selecting power '
373                        'as enter key to select Developer Options'))
374        self.servo.power_short_press()
375
376
377    def trigger_rec_to_dev(self):
378        """Trigger to the dev mode from the rec screen using vol up button.
379
380        On tablets/ detachables, recovery entered by pressing pwr, vol up
381        & vol down buttons for 10s. TO_DEV screen is entered by pressing
382        vol up & vol down buttons together on the INSERT screen.
383           Menu options seen in TO_DEV screen:
384                 Confirm enabling developer mode
385                 Cancel*
386                 Power off
387                 Language
388        Vol up button selects previous item, vol down button selects
389        next item and pwr button selects current activated item.
390        """
391        self.faft_framework.wait_for('firmware_screen', 'Pressing volume up + volume down')
392        self.set_button('volume_up_down_hold', 100, ('Enter Recovery Menu.'))
393        self.faft_framework.wait_for('confirm_screen', 'Pressing volume up')
394        self.set_button('volume_up_hold', 100, ('Selecting power as '
395                        'enter key to select Confirm Enabling Developer Mode'))
396        self.servo.power_short_press()
397        self.faft_framework.wait_for('firmware_screen')
398
399
400    def trigger_dev_to_normal(self):
401        """Trigger to the normal mode from the dev screen.
402
403           Menu options seen in DEVELOPER WARNING screen:
404                 Developer Options
405                 Show Debug Info
406                 Enable Root Verification
407                 Power Off*
408                 Language
409           Menu options seen in TO_NORM screen:
410                 Confirm Enabling Verified Boot*
411                 Cancel
412                 Power off
413                 Language
414        Vol up button selects previous item, vol down button selects
415        next item and pwr button selects current activated item.
416        """
417        self.faft_framework.wait_for('firmware_screen', 'Pressing volume up')
418        self.set_button('volume_up_hold', 100, ('Selecting '
419                        'Enable Root Verification using pwr '
420                        'button to enter TO_NORM screen'))
421        self.servo.power_short_press()
422        logging.info('Transitioning from DEV to TO_NORM screen.')
423        self.faft_framework.wait_for('firmware_screen', 'Pressing power button')
424        logging.info('Selecting Confirm Enabling Verified '
425                        'Boot using pwr button in '
426                        'TO_NORM screen')
427        self.servo.power_short_press()
428
429    def trigger_dev_to_rec(self):
430        """Trigger to the TO_NORM screen from the dev screen.
431           Menu options seen in DEVELOPER WARNING screen:
432                 Developer Options
433                 Show Debug Info
434                 Enable Root Verification
435                 Power Off*
436                 Language
437           Menu options seen in TO_NORM screen:
438                 Confirm Enabling Verified Boot*
439                 Cancel
440                 Power off
441                 Language
442        Vol up button selects previous item, vol down button selects
443        next item and pwr button selects current activated item.
444        """
445        self.faft_framework.wait_for('firmware_screen', 'Pressing volume up')
446        self.set_button('volume_up_hold', 100, ('Selecting '
447                        'Enable Root Verification using pwr '
448                        'button to enter TO_NORM screen'))
449        self.servo.power_short_press()
450        logging.info('Transitioning from DEV to TO_NORM screen.')
451        self.faft_framework.wait_for('firmware_screen', 'Pressing volume down')
452
453        # In firmware_FwScreenPressPower, test will power off the DUT using
454        # Power button in second screen (TO_NORM screen) so scrolling to
455        # Power-off is necessary in this case. Hence scroll to Power-off as
456        # a generic action and wait for next action of either Lid close or
457        # power button press.
458        self.servo.set_nocheck('volume_down_hold', 100)
459        self.faft_framework.wait_for('confirm_screen', 'Pressing volume down')
460        self.servo.set_nocheck('volume_down_hold', 100)
461        self.faft_framework.wait_for('confirm_screen')
462
463
464class _BaseModeSwitcher(object):
465    """Base class that controls firmware mode switching."""
466
467    HOLD_VOL_DOWN_BUTTON_BYPASS = _BaseFwBypasser.HOLD_VOL_DOWN_BUTTON_BYPASS
468
469    FW_BYPASSER_CLASS = _BaseFwBypasser
470
471    def __init__(self, faft_framework):
472        self.faft_framework = faft_framework
473        self.client_host = faft_framework._client
474        self.faft_client = faft_framework.faft_client
475        self.servo = faft_framework.servo
476        self.faft_config = faft_framework.faft_config
477        self.checkers = faft_framework.checkers
478        self.bypasser = self._create_fw_bypasser()
479        self._backup_mode = None
480
481    def _create_fw_bypasser(self):
482        """Creates a proper firmware bypasser.
483
484        @rtype: _BaseFwBypasser
485        """
486        return self.FW_BYPASSER_CLASS(self.faft_framework)
487
488    def setup_mode(self, mode):
489        """Setup for the requested mode.
490
491        It makes sure the system in the requested mode. If not, it tries to
492        do so.
493
494        @param mode: A string of mode, one of 'normal', 'dev', or 'rec'.
495        @raise TestFail: If the system not switched to expected mode after
496                         reboot_to_mode.
497
498        """
499        if not self.checkers.mode_checker(mode):
500            logging.info('System not in expected %s mode. Reboot into it.',
501                         mode)
502            if self._backup_mode is None:
503                # Only resume to normal/dev mode after test, not recovery.
504                self._backup_mode = 'dev' if mode == 'normal' else 'normal'
505            self.reboot_to_mode(mode)
506            if not self.checkers.mode_checker(mode):
507                raise error.TestFail('System not switched to expected %s'
508                        ' mode after setup_mode.' % mode)
509
510    def restore_mode(self):
511        """Restores original dev mode status if it has changed.
512
513        @raise TestFail: If the system not restored to expected mode.
514        """
515        if (self._backup_mode is not None and
516            not self.checkers.mode_checker(self._backup_mode)):
517            self.reboot_to_mode(self._backup_mode)
518            if not self.checkers.mode_checker(self._backup_mode):
519                raise error.TestFail('System not restored to expected %s'
520                        ' mode in cleanup.' % self._backup_mode)
521
522
523
524    def reboot_to_mode(self, to_mode, from_mode=None, sync_before_boot=True,
525                       wait_for_dut_up=True):
526        """Reboot and execute the mode switching sequence.
527
528        This method simulates what a user would do to switch between different
529        modes of ChromeOS.  Note that the modes are end-states where the OS is
530        booted up to the Welcome screen, so it takes care of navigating through
531        intermediate steps such as various boot confirmation screens.
532
533        From the user perspective, these are the states (note that there's also
534        a rec_force_mrc mode which is like rec mode but forces MRC retraining):
535
536        normal <-----> dev <------ rec
537          ^                         ^
538          |                         |
539          +-------------------------+
540
541        This is the implementation, note that "from_mode" is only used for
542        logging purposes.
543
544        Normal <-----> Dev:
545          _enable_dev_mode_and_reboot()
546
547        Rec,normal -----> Dev:
548          disable_rec_mode_and_reboot()
549
550        Any -----> normal:
551          _enable_normal_mode_and_reboot()
552
553        Normal <-----> rec:
554          enable_rec_mode_and_reboot(usb_state='dut')
555
556        Normal <-----> rec_force_mrc:
557          _enable_rec_mode_force_mrc_and_reboot(usb_state='dut')
558
559        Note that one shouldn't transition to dev again without going through the
560        normal mode.  This is because trying to disable os_verification when it's
561        already off is not supported by reboot_to_mode.
562
563        @param to_mode: The target mode, one of 'normal', 'dev', or 'rec'.
564        @param from_mode: The original mode, optional, one of 'normal, 'dev',
565                          or 'rec'.
566        @param sync_before_boot: True to sync to disk before booting.
567        @param wait_for_dut_up: True to wait DUT online again. False to do the
568                                reboot and mode switching sequence only and may
569                                need more operations to pass the firmware
570                                screen.
571        """
572        logging.info('-[ModeSwitcher]-[ start reboot_to_mode(%r, %r, %r) ]-',
573                     to_mode, from_mode, wait_for_dut_up)
574
575        if from_mode:
576            note = 'reboot_to_mode: to=%s, from=%s' % (from_mode, to_mode)
577        else:
578            note = 'reboot_to_mode: to=%s' % to_mode
579        if sync_before_boot:
580            lines = self.faft_client.system.run_shell_command_get_output(
581                'crossystem')
582            logging.debug('-[ModeSwitcher]- crossystem output:\n%s',
583                          '\n'.join(lines))
584            devsw_cur = self.faft_client.system.get_crossystem_value(
585                'devsw_cur')
586            note += ', devsw_cur=%s' % devsw_cur
587            self.faft_framework.blocking_sync(freeze_for_reset=True)
588        note += '.'
589
590        if to_mode == 'rec':
591            self.enable_rec_mode_and_reboot(usb_state='dut')
592
593        elif to_mode == 'rec_force_mrc':
594            self._enable_rec_mode_force_mrc_and_reboot(usb_state='dut')
595
596        elif to_mode == 'dev':
597            self._enable_dev_mode_and_reboot()
598            if wait_for_dut_up:
599                self.bypass_dev_mode()
600
601        elif to_mode == 'normal':
602            self._enable_normal_mode_and_reboot()
603
604        else:
605            raise NotImplementedError(
606                    'Not supported mode switching from %s to %s' %
607                     (str(from_mode), to_mode))
608
609        if wait_for_dut_up:
610            self.wait_for_client(retry_power_on=True, note=note)
611
612        logging.info('-[ModeSwitcher]-[ end reboot_to_mode(%r, %r, %r) ]-',
613                     to_mode, from_mode, wait_for_dut_up)
614
615    def simple_reboot(self, reboot_type='warm', sync_before_boot=True):
616        """Simple reboot method
617
618        Just reboot the DUT using either cold or warm reset.  Does not wait for
619        DUT to come back online.  Will wait for test to handle this.
620
621        @param reboot_type: A string of reboot type, 'warm' or 'cold'.
622                            If reboot_type != warm/cold, raise exception.
623        @param sync_before_boot: True to sync to disk before booting.
624                                 If sync_before_boot=False, DUT offline before
625                                 calling mode_aware_reboot.
626        """
627        if reboot_type == 'warm':
628            reboot_method = self.servo.get_power_state_controller().warm_reset
629        elif reboot_type == 'cold':
630            reboot_method = self.servo.get_power_state_controller().reset
631        else:
632            raise NotImplementedError('Not supported reboot_type: %s',
633                                      reboot_type)
634        if sync_before_boot:
635            boot_id = self.faft_framework.get_bootid()
636            self.faft_framework.blocking_sync(freeze_for_reset=True)
637        logging.info("-[ModeSwitcher]-[ start simple_reboot(%r) ]-",
638                     reboot_type)
639        reboot_method()
640        if sync_before_boot:
641            self.wait_for_client_offline(orig_boot_id=boot_id)
642        logging.info("-[ModeSwitcher]-[ end simple_reboot(%r) ]-",
643                     reboot_type)
644
645    def mode_aware_reboot(self, reboot_type=None, reboot_method=None,
646                          sync_before_boot=True, wait_for_dut_up=True):
647        """Uses a mode-aware way to reboot DUT.
648
649        For example, if DUT is in dev mode, it requires pressing Ctrl-D to
650        bypass the developer screen.
651
652        @param reboot_type: A string of reboot type, one of 'warm', 'cold', or
653                            'custom'. Default is a warm reboot.
654        @param reboot_method: A custom method to do the reboot. Only use it if
655                              reboot_type='custom'.
656        @param sync_before_boot: True to sync to disk before booting.
657                                 If sync_before_boot=False, DUT offline before
658                                 calling mode_aware_reboot.
659        @param wait_for_dut_up: True to wait DUT online again. False to do the
660                                reboot only.
661        """
662        if reboot_type is None or reboot_type == 'warm':
663            reboot_method = self.servo.get_power_state_controller().warm_reset
664        elif reboot_type == 'cold':
665            reboot_method = self.servo.get_power_state_controller().reset
666        elif reboot_type != 'custom':
667            raise NotImplementedError('Not supported reboot_type: %s',
668                                      reboot_type)
669
670        logging.info("-[ModeSwitcher]-[ start mode_aware_reboot(%r, %s, ..) ]-",
671                     reboot_type, reboot_method.__name__)
672        is_dev = is_rec = is_devsw_boot = False
673        if sync_before_boot:
674            is_dev = self.checkers.mode_checker('dev')
675            is_rec = self.checkers.mode_checker('rec')
676            is_devsw_boot = self.checkers.crossystem_checker(
677                                               {'devsw_boot': '1'}, True)
678            boot_id = self.faft_framework.get_bootid()
679
680            self.faft_framework.blocking_sync(reboot_type != 'custom')
681        if is_rec:
682            logging.info("-[mode_aware_reboot]-[ is_rec=%s is_dev_switch=%s ]-",
683                         is_rec, is_devsw_boot)
684        else:
685            logging.info("-[mode_aware_reboot]-[ is_dev=%s ]-", is_dev)
686        reboot_method()
687        if sync_before_boot:
688            self.wait_for_client_offline(orig_boot_id=boot_id)
689        # Encapsulating the behavior of skipping dev firmware screen,
690        # hitting ctrl-D
691        # Note that if booting from recovery mode, we can predict the next
692        # boot based on the developer switch position at boot (devsw_boot).
693        # If devsw_boot is True, we will call bypass_dev_mode after reboot.
694        if is_dev or is_devsw_boot:
695            self.bypass_dev_mode()
696        if wait_for_dut_up:
697            self.wait_for_client()
698        logging.info("-[ModeSwitcher]-[ end mode_aware_reboot(%r, %s, ..) ]-",
699                     reboot_type, reboot_method.__name__)
700
701
702    def enable_rec_mode_and_reboot(self, usb_state=None):
703        """Switch to rec mode and reboot.
704
705        This method emulates the behavior of the old physical recovery switch,
706        i.e. switch ON + reboot + switch OFF, and the new keyboard controlled
707        recovery mode, i.e. just press Power + Esc + Refresh.
708
709        @param usb_state: A string, one of 'dut', 'host', or 'off'.
710        """
711        psc = self.servo.get_power_state_controller()
712        # Switch the USB key when AP is on, because there is a
713        # bug (b/172909077) - using "image_usbkey_direction:usb_state", when
714        # AP if off may cause not recognizing the file system,
715        # so system won't boot in recovery from USB.
716        # When the issue is fixed, it can be done when AP is off.
717        if usb_state:
718            self.servo.switch_usbkey(usb_state)
719        psc.power_off()
720        psc.power_on(psc.REC_ON)
721        # Check VBUS and pd state only if we are going to boot
722        # to ChromeOS in the recovery mode
723        if usb_state == 'dut':
724            self.bypasser.check_vbus_and_pd_state()
725
726
727    def _enable_rec_mode_force_mrc_and_reboot(self, usb_state=None):
728        """Switch to rec mode, enable force mrc cache retraining, and reboot.
729
730        This method emulates the behavior of the old physical recovery switch,
731        i.e. switch ON + reboot + switch OFF, and the new keyboard controlled
732        recovery mode, i.e. just press Power + Esc + Refresh.
733
734        @param usb_state: A string, one of 'dut', 'host', or 'off'.
735        """
736        psc = self.servo.get_power_state_controller()
737        # Switch the USB key when AP is on, because there is a
738        # bug (b/172909077) - using "image_usbkey_direction:usb_state", when
739        # AP if off may cause not recognizing the file system,
740        # so system won't boot in recovery from USB.
741        # When the issue is fixed, it can be done when AP is off.
742        if usb_state:
743            self.servo.switch_usbkey(usb_state)
744        psc.power_off()
745        psc.power_on(psc.REC_ON_FORCE_MRC)
746
747    def disable_rec_mode_and_reboot(self, usb_state=None):
748        """Disable the rec mode and reboot.
749
750        It is achieved by calling power state controller to do a normal
751        power on.
752        """
753        psc = self.servo.get_power_state_controller()
754        psc.power_off()
755        self.faft_framework.wait_for('ec_boot_to_pwr_button', 'Powering on')
756        psc.power_on(psc.REC_OFF)
757
758
759    def _enable_dev_mode_and_reboot(self):
760        """Switch to developer mode and reboot."""
761        raise NotImplementedError
762
763
764    def _enable_normal_mode_and_reboot(self):
765        """Switch to normal mode and reboot."""
766        raise NotImplementedError
767
768
769    # Redirects the following methods to FwBypasser
770    def bypass_dev_mode(self):
771        """Bypass the dev mode firmware logic to boot internal image."""
772        logging.info("-[bypass_dev_mode]-")
773        self.bypasser.bypass_dev_mode()
774
775
776    def bypass_dev_boot_usb(self):
777        """Bypass the dev mode firmware logic to boot USB."""
778        logging.info("-[bypass_dev_boot_usb]-")
779        self.bypasser.bypass_dev_boot_usb()
780
781
782    def bypass_dev_default_boot(self):
783        """Bypass the dev mode firmware logic to boot from default target."""
784        logging.info("-[bypass_dev_default_boot]-")
785        self.bypasser.bypass_dev_default_boot()
786
787
788    def bypass_rec_mode(self):
789        """Bypass the rec mode firmware logic to boot USB."""
790        logging.info("-[bypass_rec_mode]-")
791        self.bypasser.bypass_rec_mode()
792
793
794    def trigger_dev_to_rec(self):
795        """Trigger to the rec mode from the dev screen."""
796        self.bypasser.trigger_dev_to_rec()
797
798
799    def trigger_rec_to_dev(self):
800        """Trigger to the dev mode from the rec screen."""
801        self.bypasser.trigger_rec_to_dev()
802
803
804    def trigger_dev_to_normal(self):
805        """Trigger to the normal mode from the dev screen."""
806        self.bypasser.trigger_dev_to_normal()
807
808
809    def wait_for_client(self, timeout=180, retry_power_on=False,
810                        debounce_power_state=True, note=''):
811        """Wait for the client to come back online.
812
813        New remote processes will be launched if their used flags are enabled.
814
815        @param timeout: Time in seconds to wait for the client SSH daemon to
816                        come up.
817        @param retry_power_on: Try to power on the DUT if it isn't in S0.
818        @param debounce_power_state: Wait until power_state is the same two
819                                     times in a row to determine the actual
820                                     power_state.
821        @param note: Extra note to add to the end of the error text
822        @raise ConnectionError: Failed to connect DUT.
823        """
824        logging.info("-[FAFT]-[ start wait_for_client(%ds) ]---",
825                     timeout if retry_power_on else 0)
826        # Wait for the system to be powered on before trying the network
827        # Skip "None" result because that indicates lack of EC or problem
828        # querying the power state.
829        current_timer = 0
830        self.faft_framework.wait_for('delay_powerinfo_stable',
831                                     'checking power state')
832        power_state = self.faft_framework.get_power_state()
833
834        # The device may transition between states. Wait until the power state
835        # is stable for two seconds before determining the state.
836        if debounce_power_state:
837            last_state = power_state
838            power_state = DEBOUNCE_STATE
839
840        while (timeout > current_timer and
841               power_state not in (self.faft_framework.POWER_STATE_S0, None)):
842            time.sleep(2)
843            current_timer += 2
844            power_state = self.faft_framework.get_power_state()
845
846            # If the state changed, debounce it.
847            if debounce_power_state and power_state != last_state:
848                last_state = power_state
849                power_state = DEBOUNCE_STATE
850
851            logging.info('power state: %s', power_state)
852
853            # Only power-on the device if it has been consistently out of
854            # S0.
855            if (retry_power_on and
856                power_state not in (self.faft_framework.POWER_STATE_S0,
857                                    None, DEBOUNCE_STATE)):
858                logging.info("-[FAFT]-[ retry powering on the DUT ]---")
859                psc = self.servo.get_power_state_controller()
860                psc.retry_power_on()
861
862        # Use the last state if the device didn't reach a stable state in
863        # timeout seconds.
864        if power_state == DEBOUNCE_STATE:
865            power_state = last_state
866        if power_state not in (self.faft_framework.POWER_STATE_S0, None):
867            msg = 'DUT unexpectedly down, power state is %s.' % power_state
868            if note:
869                msg += ' %s' % note
870            raise ConnectionError(msg)
871
872        # Wait for the system to respond to ping before attempting ssh
873        if not self.client_host.ping_wait_up(timeout):
874            logging.warning("-[FAFT]-[ system did not respond to ping ]")
875        if self.client_host.wait_up(timeout):
876            # Check the FAFT client is avaiable.
877            self.faft_client.system.is_available()
878            # Stop update-engine as it may change firmware/kernel.
879            self.faft_framework.faft_client.updater.stop_daemon()
880        else:
881            logging.error('wait_for_client() timed out.')
882            power_state = self.faft_framework.get_power_state()
883            msg = 'DUT is still down unexpectedly.'
884            if power_state:
885                msg += ' Power state: %s.' % power_state
886            if note:
887                msg += ' %s' % note
888            raise ConnectionError(msg)
889        logging.info("-[FAFT]-[ end wait_for_client ]-----")
890
891
892    def wait_for_client_offline(self, timeout=60, orig_boot_id=None):
893        """Wait for the client to come offline.
894
895        @param timeout: Time in seconds to wait the client to come offline.
896        @param orig_boot_id: A string containing the original boot id.
897        @raise ConnectionError: Failed to wait DUT offline.
898        """
899        # When running against panther, we see that sometimes
900        # ping_wait_down() does not work correctly. There needs to
901        # be some investigation to the root cause.
902        # If we sleep for 120s before running get_boot_id(), it
903        # does succeed. But if we change this to ping_wait_down()
904        # there are implications on the wait time when running
905        # commands at the fw screens.
906        if not self.client_host.ping_wait_down(timeout):
907            if orig_boot_id and self.client_host.get_boot_id() != orig_boot_id:
908                logging.warn('Reboot done very quickly.')
909                return
910            raise ConnectionError('DUT is still up unexpectedly')
911
912
913class _MenuSwitcher(_BaseModeSwitcher):
914    """Mode switcher via keyboard shortcuts for menu UI."""
915
916    FW_BYPASSER_CLASS = _KeyboardBypasser
917
918    def _enable_dev_mode_and_reboot(self):
919        """Switch to developer mode and reboot."""
920        logging.info("Enabling keyboard controlled developer mode")
921        # Rebooting EC with rec mode on. Should power on AP.
922        # Plug out USB disk for preventing recovery boot without warning
923        self.enable_rec_mode_and_reboot(usb_state='host')
924        self.wait_for_client_offline()
925        self.bypasser.trigger_rec_to_dev()
926
927    def _enable_normal_mode_and_reboot(self):
928        """Switch to normal mode and reboot."""
929        logging.info("Disabling keyboard controlled developer mode")
930        self.disable_rec_mode_and_reboot()
931        self.wait_for_client_offline()
932        self.bypasser.trigger_dev_to_normal()
933
934
935class _KeyboardDevSwitcher(_MenuSwitcher):
936    """Mode switcher via keyboard shortcuts for legacy clamshell UI."""
937
938    FW_BYPASSER_CLASS = _LegacyKeyboardBypasser
939
940
941class _JetstreamSwitcher(_BaseModeSwitcher):
942    """Mode switcher for Jetstream devices."""
943
944    FW_BYPASSER_CLASS = _JetstreamBypasser
945
946    def _enable_dev_mode_and_reboot(self):
947        """Switch to developer mode and reboot."""
948        logging.info("Enabling Jetstream developer mode")
949        self.enable_rec_mode_and_reboot(usb_state='host')
950        self.wait_for_client_offline()
951        self.bypasser.trigger_rec_to_dev()
952
953    def _enable_normal_mode_and_reboot(self):
954        """Switch to normal mode and reboot."""
955        logging.info("Disabling Jetstream developer mode")
956        self.servo.disable_development_mode()
957        self.enable_rec_mode_and_reboot(usb_state='host')
958        self.faft_framework.wait_for('firmware_screen', 'Disabling rec and rebooting')
959        self.disable_rec_mode_and_reboot(usb_state='host')
960
961
962class _TabletDetachableSwitcher(_BaseModeSwitcher):
963    """Mode switcher for legacy menu UI."""
964
965    FW_BYPASSER_CLASS = _TabletDetachableBypasser
966
967    def _enable_dev_mode_and_reboot(self):
968        """Switch to developer mode and reboot.
969
970        On tablets/ detachables, recovery entered by pressing pwr, vol up
971        & vol down buttons for 10s.
972           Menu options seen in RECOVERY screen:
973                 Enable Developer Mode
974                 Show Debug Info
975                 Power off*
976                 Language
977        """
978        logging.info('Enabling tablets/detachable recovery mode')
979        self.enable_rec_mode_and_reboot(usb_state='host')
980        self.wait_for_client_offline()
981        self.bypasser.trigger_rec_to_dev()
982
983    def _enable_normal_mode_and_reboot(self):
984        """Switch to normal mode and reboot.
985
986           Menu options seen in DEVELOPER WARNING screen:
987                 Developer Options
988                 Show Debug Info
989                 Enable Root Verification
990                 Power Off*
991                 Language
992           Menu options seen in TO_NORM screen:
993                 Confirm Enabling Verified Boot
994                 Cancel
995                 Power off*
996                 Language
997        Vol up button selects previous item, vol down button selects
998        next item and pwr button selects current activated item.
999        """
1000        self.disable_rec_mode_and_reboot()
1001        self.wait_for_client_offline()
1002        self.bypasser.trigger_dev_to_normal()
1003
1004
1005_SWITCHER_CLASSES = {
1006    'menu_switcher': _MenuSwitcher,
1007    'keyboard_dev_switcher': _KeyboardDevSwitcher,
1008    'jetstream_switcher': _JetstreamSwitcher,
1009    'tablet_detachable_switcher': _TabletDetachableSwitcher,
1010}
1011
1012
1013def create_mode_switcher(faft_framework):
1014    """Creates a proper mode switcher.
1015
1016    @param faft_framework: The main FAFT framework object.
1017    """
1018    switcher_type = faft_framework.faft_config.mode_switcher_type
1019    switcher_class = _SWITCHER_CLASSES.get(switcher_type, None)
1020    if switcher_class is None:
1021        raise NotImplementedError('Not supported mode_switcher_type: %s',
1022                                  switcher_type)
1023    else:
1024        return switcher_class(faft_framework)
1025