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