• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
7import sys
8
9from multiprocessing import Process
10from autotest_lib.client.bin import utils
11from autotest_lib.client.common_lib import error
12from autotest_lib.client.cros.faft.utils import shell_wrapper
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    def __init__(self, faft_framework):
23        self.servo = faft_framework.servo
24        self.faft_config = faft_framework.faft_config
25        self.client_host = faft_framework._client
26
27
28    def bypass_dev_mode(self):
29        """Bypass the dev mode firmware logic to boot internal image."""
30        raise NotImplementedError
31
32
33    def bypass_dev_boot_usb(self):
34        """Bypass the dev mode firmware logic to boot USB."""
35        raise NotImplementedError
36
37
38    def bypass_rec_mode(self):
39        """Bypass the rec mode firmware logic to boot USB."""
40        raise NotImplementedError
41
42
43    def trigger_dev_to_rec(self):
44        """Trigger to the rec mode from the dev screen."""
45        raise NotImplementedError
46
47
48    def trigger_rec_to_dev(self):
49        """Trigger to the dev mode from the rec screen."""
50        raise NotImplementedError
51
52
53    def trigger_dev_to_normal(self):
54        """Trigger to the normal mode from the dev screen."""
55        raise NotImplementedError
56
57
58class _CtrlDBypasser(_BaseFwBypasser):
59    """Controls bypass logic via Ctrl-D combo."""
60
61    def bypass_dev_mode(self):
62        """Bypass the dev mode firmware logic to boot internal image."""
63        time.sleep(self.faft_config.firmware_screen)
64        self.servo.ctrl_d()
65
66
67    def bypass_dev_boot_usb(self):
68        """Bypass the dev mode firmware logic to boot USB."""
69        time.sleep(self.faft_config.firmware_screen)
70        self.servo.ctrl_u()
71
72
73    def bypass_rec_mode(self):
74        """Bypass the rec mode firmware logic to boot USB."""
75        self.servo.switch_usbkey('host')
76        time.sleep(self.faft_config.usb_plug)
77        self.servo.switch_usbkey('dut')
78        logging.info('Enabled dut_sees_usb')
79        if not self.client_host.ping_wait_up(
80                timeout=self.faft_config.delay_reboot_to_ping):
81            logging.info('ping timed out, try REC_ON')
82            psc = self.servo.get_power_state_controller()
83            psc.power_on(psc.REC_ON)
84
85
86    def trigger_dev_to_rec(self):
87        """Trigger to the rec mode from the dev screen."""
88        time.sleep(self.faft_config.firmware_screen)
89
90        # Pressing Enter for too long triggers a second key press.
91        # Let's press it without delay
92        self.servo.enter_key(press_secs=0)
93
94        # For Alex/ZGB, there is a dev warning screen in text mode.
95        # Skip it by pressing Ctrl-D.
96        if self.faft_config.need_dev_transition:
97            time.sleep(self.faft_config.legacy_text_screen)
98            self.servo.ctrl_d()
99
100
101    def trigger_rec_to_dev(self):
102        """Trigger to the dev mode from the rec screen."""
103        time.sleep(self.faft_config.firmware_screen)
104        self.servo.ctrl_d()
105        time.sleep(self.faft_config.confirm_screen)
106        if self.faft_config.rec_button_dev_switch:
107            logging.info('RECOVERY button pressed to switch to dev mode')
108            self.servo.toggle_recovery_switch()
109        else:
110            logging.info('ENTER pressed to switch to dev mode')
111            self.servo.enter_key()
112
113
114    def trigger_dev_to_normal(self):
115        """Trigger to the normal mode from the dev screen."""
116        time.sleep(self.faft_config.firmware_screen)
117        self.servo.enter_key()
118        time.sleep(self.faft_config.confirm_screen)
119        self.servo.enter_key()
120
121
122class _JetstreamBypasser(_BaseFwBypasser):
123    """Controls bypass logic of Jetstream devices."""
124
125    def bypass_dev_mode(self):
126        """Bypass the dev mode firmware logic to boot internal image."""
127        # Jetstream does nothing to bypass.
128        pass
129
130
131    def bypass_dev_boot_usb(self):
132        """Bypass the dev mode firmware logic to boot USB."""
133        self.servo.switch_usbkey('dut')
134        time.sleep(self.faft_config.firmware_screen)
135        self.servo.toggle_development_switch()
136
137
138    def bypass_rec_mode(self):
139        """Bypass the rec mode firmware logic to boot USB."""
140        self.servo.switch_usbkey('host')
141        time.sleep(self.faft_config.usb_plug)
142        self.servo.switch_usbkey('dut')
143        if not self.client_host.ping_wait_up(
144                timeout=self.faft_config.delay_reboot_to_ping):
145            psc = self.servo.get_power_state_controller()
146            psc.power_on(psc.REC_ON)
147
148
149    def trigger_dev_to_rec(self):
150        """Trigger to the rec mode from the dev screen."""
151        # Jetstream does not have this triggering logic.
152        raise NotImplementedError
153
154
155    def trigger_rec_to_dev(self):
156        """Trigger to the dev mode from the rec screen."""
157        self.servo.disable_development_mode()
158        time.sleep(self.faft_config.firmware_screen)
159        self.servo.toggle_development_switch()
160
161
162    def trigger_dev_to_normal(self):
163        """Trigger to the normal mode from the dev screen."""
164        # Jetstream does not have this triggering logic.
165        raise NotImplementedError
166
167
168class _TabletDetachableBypasser(_BaseFwBypasser):
169    """Controls bypass logic of tablet/ detachable chromebook devices."""
170
171    def set_button(self, button, duration, info):
172        """Helper method that sets the button hold time for UI selections"""
173        self.servo.set_nocheck(button, duration)
174        time.sleep(self.faft_config.confirm_screen)
175        logging.info(info)
176
177
178    def bypass_dev_boot_usb(self):
179        """Bypass the dev mode firmware logic to boot USB.
180
181        On tablets/ detachables, recovery entered by pressing pwr, vol up
182        & vol down buttons for 10s.
183           Menu options seen in DEVELOPER WARNING screen:
184                 Developer Options
185                 Show Debug Info
186                 Enable Root Verification
187                 Power Off*
188                 Language
189           Menu options seen in DEV screen:
190                 Boot legacy BIOS
191                 Boot USB image
192                 Boot developer image*
193                 Cancel
194                 Power off
195                 Language
196        Vol up button selects previous item, vol down button selects
197        next item and pwr button selects current activated item.
198        """
199        self.trigger_dev_screen()
200        time.sleep(self.faft_config.firmware_screen)
201        self.set_button('volume_up_hold', 100, ('Selecting power as'
202                        ' enter key to select Boot USB Image'))
203        self.servo.power_short_press()
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        time.sleep(self.faft_config.usb_plug)
210        self.servo.switch_usbkey('dut')
211        logging.info('Enabled dut_sees_usb')
212        if not self.client_host.ping_wait_up(
213                timeout=self.faft_config.delay_reboot_to_ping):
214            logging.info('ping timed out, try REC_ON')
215            psc = self.servo.get_power_state_controller()
216            psc.power_on(psc.REC_ON)
217
218
219    def bypass_dev_mode(self):
220        """Bypass the dev mode firmware logic to boot internal image
221
222        On tablets/ detachables, recovery entered by pressing pwr, vol up
223        & vol down buttons for 10s.
224           Menu options seen in DEVELOPER WARNING screen:
225                 Developer Options
226                 Show Debug Info
227                 Enable Root Verification
228                 Power Off*
229                 Language
230           Menu options seen in DEV screen:
231                 Boot legacy BIOS
232                 Boot USB image
233                 Boot developer image*
234                 Cancel
235                 Power off
236                 Language
237        Vol up button selects previous item, vol down button selects
238        next item and pwr button selects current activated item.
239        """
240        self.trigger_dev_screen()
241        time.sleep(self.faft_config.firmware_screen)
242        logging.info('Selecting power as enter key to select '
243                     'Boot Developer Image')
244        self.servo.power_short_press()
245
246
247    def trigger_dev_screen(self):
248        """Helper method that transitions from DEVELOPER WARNING to DEV screen
249
250           Menu options seen in DEVELOPER WARNING screen:
251                 Developer Options
252                 Show Debug Info
253                 Enable Root Verification
254                 Power Off*
255                 Language
256           Menu options seen in DEV screen:
257                 Boot legacy BIOS
258                 Boot USB image
259                 Boot developer image*
260                 Cancel
261                 Power off
262                 Language
263        Vol up button selects previous item, vol down button selects
264        next item and pwr button selects current activated item.
265        """
266        time.sleep(self.faft_config.firmware_screen)
267        time.sleep(self.faft_config.firmware_screen)
268        self.servo.set_nocheck('volume_up_hold', 100)
269        time.sleep(self.faft_config.confirm_screen)
270        self.servo.set_nocheck('volume_up_hold', 100)
271        time.sleep(self.faft_config.confirm_screen)
272        self.set_button('volume_up_hold', 100, ('Selecting power '
273                        'as enter key to select Developer Options'))
274        self.servo.power_short_press()
275
276
277    def trigger_rec_to_dev(self):
278        """Trigger to the dev mode from the rec screen using vol up button.
279
280        On tablets/ detachables, recovery entered by pressing pwr, vol up
281        & vol down buttons for 10s.
282           Menu options seen in RECOVERY screen:
283                 Enable Developer Mode
284                 Show Debug Info
285                 Power off*
286                 Language
287           Menu options seen in TO_DEV screen:
288                 Confirm enabling developer mode
289                 Cancel
290                 Power off*
291                 Language
292        Vol up button selects previous item, vol down button selects
293        next item and pwr button selects current activated item.
294        """
295        time.sleep(self.faft_config.firmware_screen)
296        self.servo.set_nocheck('volume_up_hold', 100)
297        time.sleep(self.faft_config.confirm_screen)
298        self.set_button('volume_up_hold', 100, ('Selecting power as '
299                        'enter key to select Enable Developer Mode'))
300        self.servo.power_short_press()
301        logging.info('Transitioning from REC to TO_DEV screen.')
302        time.sleep(self.faft_config.confirm_screen)
303        self.servo.set_nocheck('volume_up_hold', 100)
304        time.sleep(self.faft_config.confirm_screen)
305        self.set_button('volume_up_hold', 100, ('Selecting power as '
306                        'enter key to select Confirm enabling '
307                        'developer mode'))
308        self.servo.power_short_press()
309        time.sleep(self.faft_config.firmware_screen)
310
311
312    def trigger_dev_to_normal(self):
313        """Trigger to the normal mode from the dev screen.
314
315           Menu options seen in DEVELOPER WARNING screen:
316                 Developer Options
317                 Show Debug Info
318                 Enable Root Verification
319                 Power Off*
320                 Language
321           Menu options seen in TO_NORM screen:
322                 Confirm Enabling Verified Boot
323                 Cancel
324                 Power off*
325                 Language
326        Vol up button selects previous item, vol down button selects
327        next item and pwr button selects current activated item.
328        """
329        time.sleep(self.faft_config.firmware_screen)
330        self.set_button('volume_up_hold', 100, ('Selecting '
331                        'Enable Root Verification using pwr '
332                        'button to enter TO_NORM screen'))
333        self.servo.power_short_press()
334        time.sleep(self.faft_config.firmware_screen)
335        self.servo.set_nocheck('volume_up_hold', 100)
336        time.sleep(self.faft_config.confirm_screen)
337        self.set_button('volume_up_hold', 100, ('Selecting Confirm '
338                        'Enabling Verified Boot using pwr '
339                        'button in TO_NORM screen'))
340        self.servo.power_short_press()
341
342
343def _create_fw_bypasser(faft_framework):
344    """Creates a proper firmware bypasser.
345
346    @param faft_framework: The main FAFT framework object.
347    """
348    bypasser_type = faft_framework.faft_config.fw_bypasser_type
349    if bypasser_type == 'ctrl_d_bypasser':
350        logging.info('Create a CtrlDBypasser')
351        return _CtrlDBypasser(faft_framework)
352    elif bypasser_type == 'jetstream_bypasser':
353        logging.info('Create a JetstreamBypasser')
354        return _JetstreamBypasser(faft_framework)
355    elif bypasser_type == 'ryu_bypasser':
356        # FIXME Create an RyuBypasser
357        logging.info('Create a CtrlDBypasser')
358        return _CtrlDBypasser(faft_framework)
359    elif bypasser_type == 'tablet_detachable_bypasser':
360        logging.info('Create a TabletDetachableBypasser')
361        return _TabletDetachableBypasser(faft_framework)
362    else:
363        raise NotImplementedError('Not supported fw_bypasser_type: %s',
364                                  bypasser_type)
365
366
367class _BaseModeSwitcher(object):
368    """Base class that controls firmware mode switching."""
369
370    def __init__(self, faft_framework):
371        self.faft_framework = faft_framework
372        self.client_host = faft_framework._client
373        self.faft_client = faft_framework.faft_client
374        self.servo = faft_framework.servo
375        self.faft_config = faft_framework.faft_config
376        self.checkers = faft_framework.checkers
377        self.bypasser = _create_fw_bypasser(faft_framework)
378        self._backup_mode = None
379
380
381    def setup_mode(self, mode):
382        """Setup for the requested mode.
383
384        It makes sure the system in the requested mode. If not, it tries to
385        do so.
386
387        @param mode: A string of mode, one of 'normal', 'dev', or 'rec'.
388        """
389        if not self.checkers.mode_checker(mode):
390            logging.info('System not in expected %s mode. Reboot into it.',
391                         mode)
392            if self._backup_mode is None:
393                # Only resume to normal/dev mode after test, not recovery.
394                self._backup_mode = 'dev' if mode == 'normal' else 'normal'
395            self.reboot_to_mode(mode)
396
397
398    def restore_mode(self):
399        """Restores original dev mode status if it has changed."""
400        if self._backup_mode is not None:
401            self.reboot_to_mode(self._backup_mode)
402
403
404    def reboot_to_mode(self, to_mode, from_mode=None, sync_before_boot=True,
405                       wait_for_dut_up=True):
406        """Reboot and execute the mode switching sequence.
407
408        @param to_mode: The target mode, one of 'normal', 'dev', or 'rec'.
409        @param from_mode: The original mode, optional, one of 'normal, 'dev',
410                          or 'rec'.
411        @param sync_before_boot: True to sync to disk before booting.
412        @param wait_for_dut_up: True to wait DUT online again. False to do the
413                                reboot and mode switching sequence only and may
414                                need more operations to pass the firmware
415                                screen.
416        """
417        logging.info('-[ModeSwitcher]-[ start reboot_to_mode(%r, %r, %r) ]-',
418                     to_mode, from_mode, wait_for_dut_up)
419        if sync_before_boot:
420            self.faft_framework.blocking_sync()
421        if to_mode == 'rec':
422            self._enable_rec_mode_and_reboot(usb_state='dut')
423            if wait_for_dut_up:
424                self.wait_for_client()
425
426        elif to_mode == 'dev':
427            self._enable_dev_mode_and_reboot()
428            if wait_for_dut_up:
429                self.bypass_dev_mode()
430                self.wait_for_client()
431
432        elif to_mode == 'normal':
433            self._enable_normal_mode_and_reboot()
434            if wait_for_dut_up:
435                self.wait_for_client()
436
437        else:
438            raise NotImplementedError(
439                    'Not supported mode switching from %s to %s' %
440                     (str(from_mode), to_mode))
441        logging.info('-[ModeSwitcher]-[ end reboot_to_mode(%r, %r, %r) ]-',
442                     to_mode, from_mode, wait_for_dut_up)
443
444    def simple_reboot(self, reboot_type='warm', sync_before_boot=True):
445        """Simple reboot method
446
447        Just reboot the DUT using either cold or warm reset.  Does not wait for
448        DUT to come back online.  Will wait for test to handle this.
449
450        @param reboot_type: A string of reboot type, 'warm' or 'cold'.
451                            If reboot_type != warm/cold, raise exception.
452        @param sync_before_boot: True to sync to disk before booting.
453                                 If sync_before_boot=False, DUT offline before
454                                 calling mode_aware_reboot.
455        """
456        if reboot_type == 'warm':
457            reboot_method = self.servo.get_power_state_controller().warm_reset
458        elif reboot_type == 'cold':
459            reboot_method = self.servo.get_power_state_controller().reset
460        else:
461            raise NotImplementedError('Not supported reboot_type: %s',
462                                      reboot_type)
463        if sync_before_boot:
464            boot_id = self.faft_framework.get_bootid()
465            self.faft_framework.blocking_sync()
466        logging.info("-[ModeSwitcher]-[ start simple_reboot(%r) ]-",
467                     reboot_type)
468        reboot_method()
469        if sync_before_boot:
470            self.wait_for_client_offline(orig_boot_id=boot_id)
471        logging.info("-[ModeSwitcher]-[ end simple_reboot(%r) ]-",
472                     reboot_type)
473
474    def mode_aware_reboot(self, reboot_type=None, reboot_method=None,
475                          sync_before_boot=True, wait_for_dut_up=True):
476        """Uses a mode-aware way to reboot DUT.
477
478        For example, if DUT is in dev mode, it requires pressing Ctrl-D to
479        bypass the developer screen.
480
481        @param reboot_type: A string of reboot type, one of 'warm', 'cold', or
482                            'custom'. Default is a warm reboot.
483        @param reboot_method: A custom method to do the reboot. Only use it if
484                              reboot_type='custom'.
485        @param sync_before_boot: True to sync to disk before booting.
486                                 If sync_before_boot=False, DUT offline before
487                                 calling mode_aware_reboot.
488        @param wait_for_dut_up: True to wait DUT online again. False to do the
489                                reboot only.
490        """
491        if reboot_type is None or reboot_type == 'warm':
492            reboot_method = self.servo.get_power_state_controller().warm_reset
493        elif reboot_type == 'cold':
494            reboot_method = self.servo.get_power_state_controller().reset
495        elif reboot_type != 'custom':
496            raise NotImplementedError('Not supported reboot_type: %s',
497                                      reboot_type)
498
499        logging.info("-[ModeSwitcher]-[ start mode_aware_reboot(%r, %s, ..) ]-",
500                     reboot_type, reboot_method.__name__)
501        is_dev = False
502        if sync_before_boot:
503            is_dev = self.checkers.mode_checker('dev')
504            boot_id = self.faft_framework.get_bootid()
505            self.faft_framework.blocking_sync()
506        logging.info("-[mode_aware_reboot]-[ is_dev=%s ]-", is_dev);
507        reboot_method()
508        if sync_before_boot:
509            self.wait_for_client_offline(orig_boot_id=boot_id)
510        # Encapsulating the behavior of skipping dev firmware screen,
511        # hitting ctrl-D
512        # Note that if booting from recovery mode, will not
513        # call bypass_dev_mode because can't determine prior to
514        # reboot if we're going to boot up in dev or normal mode.
515        if is_dev:
516            self.bypass_dev_mode()
517        if wait_for_dut_up:
518            self.wait_for_client()
519        logging.info("-[ModeSwitcher]-[ end mode_aware_reboot(%r, %s, ..) ]-",
520                     reboot_type, reboot_method.__name__)
521
522
523    def _enable_rec_mode_and_reboot(self, usb_state=None):
524        """Switch to rec mode and reboot.
525
526        This method emulates the behavior of the old physical recovery switch,
527        i.e. switch ON + reboot + switch OFF, and the new keyboard controlled
528        recovery mode, i.e. just press Power + Esc + Refresh.
529
530        @param usb_state: A string, one of 'dut', 'host', or 'off'.
531        """
532        psc = self.servo.get_power_state_controller()
533        psc.power_off()
534        if usb_state:
535            self.servo.switch_usbkey(usb_state)
536        psc.power_on(psc.REC_ON)
537
538
539    def _disable_rec_mode_and_reboot(self, usb_state=None):
540        """Disable the rec mode and reboot.
541
542        It is achieved by calling power state controller to do a normal
543        power on.
544        """
545        psc = self.servo.get_power_state_controller()
546        psc.power_off()
547        time.sleep(self.faft_config.ec_boot_to_pwr_button)
548        psc.power_on(psc.REC_OFF)
549
550
551    def _enable_dev_mode_and_reboot(self):
552        """Switch to developer mode and reboot."""
553        raise NotImplementedError
554
555
556    def _enable_normal_mode_and_reboot(self):
557        """Switch to normal mode and reboot."""
558        raise NotImplementedError
559
560
561    # Redirects the following methods to FwBypasser
562    def bypass_dev_mode(self):
563        """Bypass the dev mode firmware logic to boot internal image."""
564        logging.info("-[bypass_dev_mode]-")
565        self.bypasser.bypass_dev_mode()
566
567
568    def bypass_dev_boot_usb(self):
569        """Bypass the dev mode firmware logic to boot USB."""
570        logging.info("-[bypass_dev_boot_usb]-")
571        self.bypasser.bypass_dev_boot_usb()
572
573
574    def bypass_rec_mode(self):
575        """Bypass the rec mode firmware logic to boot USB."""
576        logging.info("-[bypass_rec_mode]-")
577        self.bypasser.bypass_rec_mode()
578
579
580    def trigger_dev_to_rec(self):
581        """Trigger to the rec mode from the dev screen."""
582        self.bypasser.trigger_dev_to_rec()
583
584
585    def trigger_rec_to_dev(self):
586        """Trigger to the dev mode from the rec screen."""
587        self.bypasser.trigger_rec_to_dev()
588
589
590    def trigger_dev_to_normal(self):
591        """Trigger to the normal mode from the dev screen."""
592        self.bypasser.trigger_dev_to_normal()
593
594
595    def wait_for_client(self, timeout=180):
596        """Wait for the client to come back online.
597
598        New remote processes will be launched if their used flags are enabled.
599
600        @param timeout: Time in seconds to wait for the client SSH daemon to
601                        come up.
602        @raise ConnectionError: Failed to connect DUT.
603        """
604        logging.info("-[FAFT]-[ start wait_for_client ]---")
605        # Wait for the system to respond to ping before attempting ssh
606        if not self.client_host.ping_wait_up(timeout):
607            logging.warning("-[FAFT]-[ system did not respond to ping ]")
608        if self.client_host.wait_up(timeout):
609            # Check the FAFT client is avaiable.
610            self.faft_client.system.is_available()
611            # Stop update-engine as it may change firmware/kernel.
612            self.faft_framework._stop_service('update-engine')
613        else:
614            logging.error('wait_for_client() timed out.')
615            raise ConnectionError()
616        logging.info("-[FAFT]-[ end wait_for_client ]-----")
617
618
619    def wait_for_client_offline(self, timeout=60, orig_boot_id=None):
620        """Wait for the client to come offline.
621
622        @param timeout: Time in seconds to wait the client to come offline.
623        @param orig_boot_id: A string containing the original boot id.
624        @raise ConnectionError: Failed to wait DUT offline.
625        """
626        # When running against panther, we see that sometimes
627        # ping_wait_down() does not work correctly. There needs to
628        # be some investigation to the root cause.
629        # If we sleep for 120s before running get_boot_id(), it
630        # does succeed. But if we change this to ping_wait_down()
631        # there are implications on the wait time when running
632        # commands at the fw screens.
633        if not self.client_host.ping_wait_down(timeout):
634            if orig_boot_id and self.client_host.get_boot_id() != orig_boot_id:
635                logging.warn('Reboot done very quickly.')
636                return
637            raise ConnectionError()
638
639
640class _PhysicalButtonSwitcher(_BaseModeSwitcher):
641    """Class that switches firmware mode via physical button."""
642
643    def _enable_dev_mode_and_reboot(self):
644        """Switch to developer mode and reboot."""
645        self.servo.enable_development_mode()
646        self.faft_client.system.run_shell_command(
647                'chromeos-firmwareupdate --mode todev && reboot')
648
649
650    def _enable_normal_mode_and_reboot(self):
651        """Switch to normal mode and reboot."""
652        self.servo.disable_development_mode()
653        self.faft_client.system.run_shell_command(
654                'chromeos-firmwareupdate --mode tonormal && reboot')
655
656
657class _KeyboardDevSwitcher(_BaseModeSwitcher):
658    """Class that switches firmware mode via keyboard combo."""
659
660    def _enable_dev_mode_and_reboot(self):
661        """Switch to developer mode and reboot."""
662        logging.info("Enabling keyboard controlled developer mode")
663        # Rebooting EC with rec mode on. Should power on AP.
664        # Plug out USB disk for preventing recovery boot without warning
665        self._enable_rec_mode_and_reboot(usb_state='host')
666        self.wait_for_client_offline()
667        self.bypasser.trigger_rec_to_dev()
668
669
670    def _enable_normal_mode_and_reboot(self):
671        """Switch to normal mode and reboot."""
672        logging.info("Disabling keyboard controlled developer mode")
673        self._disable_rec_mode_and_reboot()
674        self.wait_for_client_offline()
675        self.bypasser.trigger_dev_to_normal()
676
677
678class _JetstreamSwitcher(_BaseModeSwitcher):
679    """Class that switches firmware mode in Jetstream devices."""
680
681    def _enable_dev_mode_and_reboot(self):
682        """Switch to developer mode and reboot."""
683        logging.info("Enabling Jetstream developer mode")
684        self._enable_rec_mode_and_reboot(usb_state='host')
685        self.wait_for_client_offline()
686        self.bypasser.trigger_rec_to_dev()
687
688
689    def _enable_normal_mode_and_reboot(self):
690        """Switch to normal mode and reboot."""
691        logging.info("Disabling Jetstream developer mode")
692        self.servo.disable_development_mode()
693        self._enable_rec_mode_and_reboot(usb_state='host')
694        time.sleep(self.faft_config.firmware_screen)
695        self._disable_rec_mode_and_reboot(usb_state='host')
696
697
698class _TabletDetachableSwitcher(_BaseModeSwitcher):
699    """Class that switches fw mode in tablets/detachables with fw menu UI."""
700
701    def _enable_dev_mode_and_reboot(self):
702        """Switch to developer mode and reboot.
703
704        On tablets/ detachables, recovery entered by pressing pwr, vol up
705        & vol down buttons for 10s.
706           Menu options seen in RECOVERY screen:
707                 Enable Developer Mode
708                 Show Debug Info
709                 Power off*
710                 Language
711        """
712        logging.info('Enabling tablets/detachable recovery mode')
713        self._enable_rec_mode_and_reboot(usb_state='host')
714        self.wait_for_client_offline()
715        self.bypasser.trigger_rec_to_dev()
716
717
718    def _enable_normal_mode_and_reboot(self):
719        """Switch to normal mode and reboot.
720
721           Menu options seen in DEVELOPER WARNING screen:
722                 Developer Options
723                 Show Debug Info
724                 Enable Root Verification
725                 Power Off*
726                 Language
727           Menu options seen in TO_NORM screen:
728                 Confirm Enabling Verified Boot
729                 Cancel
730                 Power off*
731                 Language
732        Vol up button selects previous item, vol down button selects
733        next item and pwr button selects current activated item.
734        """
735        self._disable_rec_mode_and_reboot()
736        self.wait_for_client_offline()
737        self.bypasser.trigger_dev_to_normal()
738
739
740class _RyuSwitcher(_BaseModeSwitcher):
741    """Class that switches firmware mode via physical button."""
742
743    FASTBOOT_OEM_DELAY = 10
744    RECOVERY_TIMEOUT = 2400
745    RECOVERY_SETUP = 60
746    ANDROID_BOOTUP = 600
747    FWTOOL_STARTUP_DELAY = 30
748
749    def wait_for_client(self, timeout=180):
750        """Wait for the client to come back online.
751
752        New remote processes will be launched if their used flags are enabled.
753
754        @param timeout: Time in seconds to wait for the client SSH daemon to
755                        come up.
756        @raise ConnectionError: Failed to connect DUT.
757        """
758        if not self.faft_client.system.wait_for_client(timeout):
759            raise ConnectionError()
760
761        # there's a conflict between fwtool and crossystem trying to access
762        # the nvram after the OS boots up.  Temporarily put a hard wait of
763        # 30 seconds to try to wait for fwtool to finish up.
764        time.sleep(self.FWTOOL_STARTUP_DELAY)
765
766
767    def wait_for_client_offline(self, timeout=60, orig_boot_id=None):
768        """Wait for the client to come offline.
769
770        @param timeout: Time in seconds to wait the client to come offline.
771        @param orig_boot_id: A string containing the original boot id.
772        @raise ConnectionError: Failed to wait DUT offline.
773        """
774        # TODO: Add a way to check orig_boot_id
775        if not self.faft_client.system.wait_for_client_offline(timeout):
776            raise ConnectionError()
777
778    def print_recovery_warning(self):
779        """Print recovery warning"""
780        logging.info("***")
781        logging.info("*** Entering recovery mode.  This may take awhile ***")
782        logging.info("***")
783        # wait a minute for DUT to get settled into wipe stage
784        time.sleep(self.RECOVERY_SETUP)
785
786    def is_fastboot_mode(self):
787        """Return True if DUT in fastboot mode, False otherwise"""
788        result = self.faft_client.host.run_shell_command_get_output(
789            'fastboot devices')
790        if not result:
791            return False
792        else:
793            return True
794
795    def wait_for_client_fastboot(self, timeout=30):
796        """Wait for the client to come online in fastboot mode
797
798        @param timeout: Time in seconds to wait the client
799        @raise ConnectionError: Failed to wait DUT offline.
800        """
801        utils.wait_for_value(self.is_fastboot_mode, True, timeout_sec=timeout)
802
803    def _run_cmd(self, args):
804        """Wrapper for run_shell_command
805
806        For Process creation
807        """
808        return self.faft_client.host.run_shell_command(args)
809
810    def _enable_dev_mode_and_reboot(self):
811        """Switch to developer mode and reboot."""
812        logging.info("Entering RyuSwitcher: _enable_dev_mode_and_reboot")
813        try:
814            self.faft_client.system.run_shell_command('reboot bootloader')
815            self.wait_for_client_fastboot()
816
817            process = Process(
818                target=self._run_cmd,
819                args=('fastboot oem unlock',))
820            process.start()
821
822            # need a slight delay to give the ap time to get into valid state
823            time.sleep(self.FASTBOOT_OEM_DELAY)
824            self.servo.power_key(self.faft_config.hold_pwr_button_poweron)
825            process.join()
826
827            self.print_recovery_warning()
828            self.wait_for_client_fastboot(self.RECOVERY_TIMEOUT)
829            self.faft_client.host.run_shell_command('fastboot continue')
830            self.wait_for_client(self.ANDROID_BOOTUP)
831
832        # need to reset DUT into clean state
833        except shell_wrapper.ShellError:
834            raise error.TestError('Error executing shell command')
835        except ConnectionError:
836            raise error.TestError('Timed out waiting for DUT to exit recovery')
837        except:
838            raise error.TestError('Unexpected Exception: %s' % sys.exc_info()[0])
839        logging.info("Exiting RyuSwitcher: _enable_dev_mode_and_reboot")
840
841    def _enable_normal_mode_and_reboot(self):
842        """Switch to normal mode and reboot."""
843        try:
844            self.faft_client.system.run_shell_command('reboot bootloader')
845            self.wait_for_client_fastboot()
846
847            process = Process(
848                target=self._run_cmd,
849                args=('fastboot oem lock',))
850            process.start()
851
852            # need a slight delay to give the ap time to get into valid state
853            time.sleep(self.FASTBOOT_OEM_DELAY)
854            self.servo.power_key(self.faft_config.hold_pwr_button_poweron)
855            process.join()
856
857            self.print_recovery_warning()
858            self.wait_for_client_fastboot(self.RECOVERY_TIMEOUT)
859            self.faft_client.host.run_shell_command('fastboot continue')
860            self.wait_for_client(self.ANDROID_BOOTUP)
861
862        # need to reset DUT into clean state
863        except shell_wrapper.ShellError:
864            raise error.TestError('Error executing shell command')
865        except ConnectionError:
866            raise error.TestError('Timed out waiting for DUT to exit recovery')
867        except:
868            raise error.TestError('Unexpected Exception: %s' % sys.exc_info()[0])
869        logging.info("Exiting RyuSwitcher: _enable_normal_mode_and_reboot")
870
871def create_mode_switcher(faft_framework):
872    """Creates a proper mode switcher.
873
874    @param faft_framework: The main FAFT framework object.
875    """
876    switcher_type = faft_framework.faft_config.mode_switcher_type
877    if switcher_type == 'physical_button_switcher':
878        logging.info('Create a PhysicalButtonSwitcher')
879        return _PhysicalButtonSwitcher(faft_framework)
880    elif switcher_type == 'keyboard_dev_switcher':
881        logging.info('Create a KeyboardDevSwitcher')
882        return _KeyboardDevSwitcher(faft_framework)
883    elif switcher_type == 'jetstream_switcher':
884        logging.info('Create a JetstreamSwitcher')
885        return _JetstreamSwitcher(faft_framework)
886    elif switcher_type == 'ryu_switcher':
887        logging.info('Create a RyuSwitcher')
888        return _RyuSwitcher(faft_framework)
889    elif switcher_type == 'tablet_detachable_switcher':
890        logging.info('Create a TabletDetachableSwitcher')
891        return _TabletDetachableSwitcher(faft_framework)
892    else:
893        raise NotImplementedError('Not supported mode_switcher_type: %s',
894                                  switcher_type)
895