• 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
7
8from autotest_lib.client.common_lib import error
9
10class ConnectionError(Exception):
11    """Raised on an error of connecting DUT."""
12    pass
13
14
15class _BaseFwBypasser(object):
16    """Base class that controls bypass logic for firmware screens."""
17
18    # Duration of holding Volume down button to quickly bypass the developer
19    # warning screen in tablets/detachables.
20    HOLD_VOL_DOWN_BUTTON_BYPASS = 3
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
64        Press Ctrl-D repeatedly. To obtain a low firmware boot time, pressing
65        Ctrl+D for every half second until firmware_screen delay has been
66        reached.
67        """
68        logging.info("Pressing Ctrl-D.")
69        # At maximum, device waits for twice of firmware_screen delay to
70        # bypass the Dev screen.
71        timeout = time.time() + (self.faft_config.firmware_screen * 2)
72        while time.time() < timeout:
73            self.servo.ctrl_d()
74            time.sleep(0.5)
75            if self.client_host.ping_wait_up(timeout=0.1):
76                break
77
78
79    def bypass_dev_boot_usb(self):
80        """Bypass the dev mode firmware logic to boot USB."""
81        time.sleep(self.faft_config.firmware_screen)
82        self.servo.ctrl_u()
83
84
85    def bypass_rec_mode(self):
86        """Bypass the rec mode firmware logic to boot USB."""
87        self.servo.switch_usbkey('host')
88        time.sleep(self.faft_config.usb_plug)
89        self.servo.switch_usbkey('dut')
90        logging.info('Enabled dut_sees_usb')
91        if not self.client_host.ping_wait_up(
92                timeout=self.faft_config.delay_reboot_to_ping):
93            logging.info('ping timed out, try REC_ON')
94            psc = self.servo.get_power_state_controller()
95            psc.power_on(psc.REC_ON)
96
97
98    def trigger_dev_to_rec(self):
99        """Trigger to the rec mode from the dev screen."""
100        time.sleep(self.faft_config.firmware_screen)
101        self.servo.enter_key()
102
103
104    def trigger_rec_to_dev(self):
105        """Trigger to the dev mode from the rec screen."""
106        time.sleep(self.faft_config.firmware_screen)
107        self.servo.ctrl_d()
108        time.sleep(self.faft_config.confirm_screen)
109        if self.faft_config.rec_button_dev_switch:
110            logging.info('RECOVERY button pressed to switch to dev mode')
111            self.servo.toggle_recovery_switch()
112        elif self.faft_config.power_button_dev_switch:
113            logging.info('POWER button pressed to switch to dev mode')
114            self.servo.power_normal_press()
115        else:
116            logging.info('ENTER pressed to switch to dev mode')
117            self.servo.enter_key()
118
119
120    def trigger_dev_to_normal(self):
121        """Trigger to the normal mode from the dev screen."""
122        time.sleep(self.faft_config.firmware_screen)
123        self.servo.enter_key()
124        time.sleep(self.faft_config.confirm_screen)
125        self.servo.enter_key()
126
127
128class _JetstreamBypasser(_BaseFwBypasser):
129    """Controls bypass logic of Jetstream devices."""
130
131    def bypass_dev_mode(self):
132        """Bypass the dev mode firmware logic to boot internal image."""
133        # Jetstream does nothing to bypass.
134        pass
135
136
137    def bypass_dev_boot_usb(self):
138        """Bypass the dev mode firmware logic to boot USB."""
139        self.servo.switch_usbkey('dut')
140        time.sleep(self.faft_config.firmware_screen)
141        self.servo.toggle_development_switch()
142
143
144    def bypass_rec_mode(self):
145        """Bypass the rec mode firmware logic to boot USB."""
146        self.servo.switch_usbkey('host')
147        time.sleep(self.faft_config.usb_plug)
148        self.servo.switch_usbkey('dut')
149        if not self.client_host.ping_wait_up(
150                timeout=self.faft_config.delay_reboot_to_ping):
151            psc = self.servo.get_power_state_controller()
152            psc.power_on(psc.REC_ON)
153
154
155    def trigger_dev_to_rec(self):
156        """Trigger to the rec mode from the dev screen."""
157        # Jetstream does not have this triggering logic.
158        raise NotImplementedError
159
160
161    def trigger_rec_to_dev(self):
162        """Trigger to the dev mode from the rec screen."""
163        self.servo.disable_development_mode()
164        time.sleep(self.faft_config.firmware_screen)
165        self.servo.toggle_development_switch()
166
167
168    def trigger_dev_to_normal(self):
169        """Trigger to the normal mode from the dev screen."""
170        # Jetstream does not have this triggering logic.
171        raise NotImplementedError
172
173
174class _TabletDetachableBypasser(_BaseFwBypasser):
175    """Controls bypass logic of tablet/ detachable chromebook devices."""
176
177    def set_button(self, button, duration, info):
178        """Helper method that sets the button hold time for UI selections"""
179        self.servo.set_nocheck(button, duration)
180        time.sleep(self.faft_config.confirm_screen)
181        logging.info(info)
182
183
184    def bypass_dev_boot_usb(self):
185        """Bypass the dev mode firmware logic to boot USB.
186
187        On tablets/ detachables, recovery entered by pressing pwr, vol up
188        & vol down buttons for 10s.
189           Menu options seen in DEVELOPER WARNING screen:
190                 Developer Options
191                 Show Debug Info
192                 Enable Root Verification
193                 Power Off*
194                 Language
195           Menu options seen in DEV screen:
196                 Boot legacy BIOS
197                 Boot USB image
198                 Boot developer image*
199                 Cancel
200                 Power off
201                 Language
202        Vol up button selects previous item, vol down button selects
203        next item and pwr button selects current activated item.
204        """
205        self.trigger_dev_screen()
206        time.sleep(self.faft_config.firmware_screen)
207        self.set_button('volume_up_hold', 100, ('Selecting power as'
208                        ' enter key to select Boot USB Image'))
209        self.servo.power_short_press()
210
211
212    def bypass_rec_mode(self):
213        """Bypass the rec mode firmware logic to boot USB."""
214        self.servo.switch_usbkey('host')
215        time.sleep(self.faft_config.usb_plug)
216        self.servo.switch_usbkey('dut')
217        logging.info('Enabled dut_sees_usb')
218        if not self.client_host.ping_wait_up(
219                timeout=self.faft_config.delay_reboot_to_ping):
220            logging.info('ping timed out, try REC_ON')
221            psc = self.servo.get_power_state_controller()
222            psc.power_on(psc.REC_ON)
223
224
225    def bypass_dev_mode(self):
226        """Bypass the developer warning screen immediately to boot into
227        internal disk.
228
229        On tablets/detachables, press & holding the Volume down button for
230        3-seconds will quickly bypass the developer warning screen.
231        """
232        # Unit for the "volume_down_hold" console command is msec.
233        duration = (self.HOLD_VOL_DOWN_BUTTON_BYPASS + 0.1) * 1000
234        logging.info("Press and hold volume down button for %.1f seconds to "
235                     "immediately bypass the Developer warning screen.",
236                     self.HOLD_VOL_DOWN_BUTTON_BYPASS + 0.1)
237        # At maximum, device waits for twice of firmware_screen delay to
238        # bypass the Dev screen.
239        timeout = time.time() + (self.faft_config.firmware_screen * 2)
240        # To obtain a low firmware boot time, volume_down button pressed for
241        # every 3.1 seconds until firmware_screen delay has been reached.
242        while time.time() < timeout:
243            self.servo.set_nocheck('volume_down_hold', duration)
244            # After pressing 'volume_down_hold' button, wait for 0.1 seconds
245            # before start pressing the button for next iteration.
246            time.sleep(0.1)
247            if self.client_host.ping_wait_up(timeout=0.1):
248                break
249
250
251    def trigger_dev_screen(self):
252        """Helper method that transitions from DEVELOPER WARNING to DEV screen
253
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        Vol up button selects previous item, vol down button selects
268        next item and pwr button selects current activated item.
269        """
270        time.sleep(self.faft_config.firmware_screen)
271        self.servo.set_nocheck('volume_up_hold', 100)
272        time.sleep(self.faft_config.confirm_screen)
273        self.servo.set_nocheck('volume_up_hold', 100)
274        time.sleep(self.faft_config.confirm_screen)
275        self.set_button('volume_up_hold', 100, ('Selecting power '
276                        'as enter key to select Developer Options'))
277        self.servo.power_short_press()
278
279
280    def trigger_rec_to_dev(self):
281        """Trigger to the dev mode from the rec screen using vol up button.
282
283        On tablets/ detachables, recovery entered by pressing pwr, vol up
284        & vol down buttons for 10s. TO_DEV screen is entered by pressing
285        vol up & vol down buttons together on the INSERT screen.
286           Menu options seen in TO_DEV screen:
287                 Confirm enabling developer mode
288                 Cancel*
289                 Power off
290                 Language
291        Vol up button selects previous item, vol down button selects
292        next item and pwr button selects current activated item.
293        """
294        time.sleep(self.faft_config.firmware_screen)
295        self.set_button('volume_up_down_hold', 100, ('Enter Recovery Menu.'))
296        time.sleep(self.faft_config.confirm_screen)
297        self.set_button('volume_up_hold', 100, ('Selecting power as '
298                        'enter key to select Confirm Enabling Developer Mode'))
299        self.servo.power_short_press()
300        time.sleep(self.faft_config.firmware_screen)
301
302
303    def trigger_dev_to_normal(self):
304        """Trigger to the normal mode from the dev screen.
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 TO_NORM screen:
313                 Confirm Enabling Verified Boot*
314                 Cancel
315                 Power off
316                 Language
317        Vol up button selects previous item, vol down button selects
318        next item and pwr button selects current activated item.
319        """
320        time.sleep(self.faft_config.firmware_screen)
321        self.set_button('volume_up_hold', 100, ('Selecting '
322                        'Enable Root Verification using pwr '
323                        'button to enter TO_NORM screen'))
324        self.servo.power_short_press()
325        logging.info('Transitioning from DEV to TO_NORM screen.')
326        time.sleep(self.faft_config.firmware_screen)
327        logging.info('Selecting Confirm Enabling Verified '
328                        'Boot using pwr button in '
329                        'TO_NORM screen')
330        self.servo.power_short_press()
331
332    def trigger_dev_to_rec(self):
333        """Trigger to the TO_NORM screen from the dev screen.
334           Menu options seen in DEVELOPER WARNING screen:
335                 Developer Options
336                 Show Debug Info
337                 Enable Root Verification
338                 Power Off*
339                 Language
340           Menu options seen in TO_NORM screen:
341                 Confirm Enabling Verified Boot*
342                 Cancel
343                 Power off
344                 Language
345        Vol up button selects previous item, vol down button selects
346        next item and pwr button selects current activated item.
347        """
348        time.sleep(self.faft_config.firmware_screen)
349        self.set_button('volume_up_hold', 100, ('Selecting '
350                        'Enable Root Verification using pwr '
351                        'button to enter TO_NORM screen'))
352        self.servo.power_short_press()
353        logging.info('Transitioning from DEV to TO_NORM screen.')
354        time.sleep(self.faft_config.firmware_screen)
355
356        # In firmware_FwScreenPressPower, test will power off the DUT using
357        # Power button in second screen (TO_NORM screen) so scrolling to
358        # Power-off is necessary in this case. Hence scroll to Power-off as
359        # a generic action and wait for next action of either Lid close or
360        # power button press.
361        self.servo.set_nocheck('volume_down_hold', 100)
362        time.sleep(self.faft_config.confirm_screen)
363        self.servo.set_nocheck('volume_down_hold', 100)
364        time.sleep(self.faft_config.confirm_screen)
365
366
367class _BaseModeSwitcher(object):
368    """Base class that controls firmware mode switching."""
369
370    HOLD_VOL_DOWN_BUTTON_BYPASS = _BaseFwBypasser.HOLD_VOL_DOWN_BUTTON_BYPASS
371
372    FW_BYPASSER_CLASS = _BaseFwBypasser
373
374    def __init__(self, faft_framework):
375        self.faft_framework = faft_framework
376        self.client_host = faft_framework._client
377        self.faft_client = faft_framework.faft_client
378        self.servo = faft_framework.servo
379        self.faft_config = faft_framework.faft_config
380        self.checkers = faft_framework.checkers
381        self.bypasser = self._create_fw_bypasser()
382        self._backup_mode = None
383
384    def _create_fw_bypasser(self):
385        """Creates a proper firmware bypasser."""
386        return self.FW_BYPASSER_CLASS(self.faft_framework)
387
388    def setup_mode(self, mode):
389        """Setup for the requested mode.
390
391        It makes sure the system in the requested mode. If not, it tries to
392        do so.
393
394        @param mode: A string of mode, one of 'normal', 'dev', or 'rec'.
395        @raise TestFail: If the system not switched to expected mode after
396                         reboot_to_mode.
397
398        """
399        if not self.checkers.mode_checker(mode):
400            logging.info('System not in expected %s mode. Reboot into it.',
401                         mode)
402            if self._backup_mode is None:
403                # Only resume to normal/dev mode after test, not recovery.
404                self._backup_mode = 'dev' if mode == 'normal' else 'normal'
405            self.reboot_to_mode(mode)
406            if not self.checkers.mode_checker(mode):
407                raise error.TestFail('System not switched to expected %s'
408                        ' mode after setup_mode.' % mode)
409
410    def restore_mode(self):
411        """Restores original dev mode status if it has changed.
412
413        @raise TestFail: If the system not restored to expected mode.
414        """
415        if (self._backup_mode is not None and
416            not self.checkers.mode_checker(self._backup_mode)):
417            self.reboot_to_mode(self._backup_mode)
418            if not self.checkers.mode_checker(self._backup_mode):
419                raise error.TestFail('System not restored to expected %s'
420                        ' mode in cleanup.' % self._backup_mode)
421
422
423
424    def reboot_to_mode(self, to_mode, from_mode=None, sync_before_boot=True,
425                       wait_for_dut_up=True):
426        """Reboot and execute the mode switching sequence.
427
428        @param to_mode: The target mode, one of 'normal', 'dev', or 'rec'.
429        @param from_mode: The original mode, optional, one of 'normal, 'dev',
430                          or 'rec'.
431        @param sync_before_boot: True to sync to disk before booting.
432        @param wait_for_dut_up: True to wait DUT online again. False to do the
433                                reboot and mode switching sequence only and may
434                                need more operations to pass the firmware
435                                screen.
436        """
437        logging.info('-[ModeSwitcher]-[ start reboot_to_mode(%r, %r, %r) ]-',
438                     to_mode, from_mode, wait_for_dut_up)
439        if sync_before_boot:
440            self.faft_framework.blocking_sync()
441        if to_mode == 'rec':
442            self.enable_rec_mode_and_reboot(usb_state='dut')
443            if wait_for_dut_up:
444                self.wait_for_client()
445
446        elif to_mode == 'rec_force_mrc':
447            self._enable_rec_mode_force_mrc_and_reboot(usb_state='dut')
448            if wait_for_dut_up:
449                self.wait_for_client()
450
451        elif to_mode == 'dev':
452            self._enable_dev_mode_and_reboot()
453            if wait_for_dut_up:
454                self.bypass_dev_mode()
455                self.wait_for_client()
456
457        elif to_mode == 'normal':
458            self._enable_normal_mode_and_reboot()
459            if wait_for_dut_up:
460                self.wait_for_client()
461
462        else:
463            raise NotImplementedError(
464                    'Not supported mode switching from %s to %s' %
465                     (str(from_mode), to_mode))
466        logging.info('-[ModeSwitcher]-[ end reboot_to_mode(%r, %r, %r) ]-',
467                     to_mode, from_mode, wait_for_dut_up)
468
469    def simple_reboot(self, reboot_type='warm', sync_before_boot=True):
470        """Simple reboot method
471
472        Just reboot the DUT using either cold or warm reset.  Does not wait for
473        DUT to come back online.  Will wait for test to handle this.
474
475        @param reboot_type: A string of reboot type, 'warm' or 'cold'.
476                            If reboot_type != warm/cold, raise exception.
477        @param sync_before_boot: True to sync to disk before booting.
478                                 If sync_before_boot=False, DUT offline before
479                                 calling mode_aware_reboot.
480        """
481        if reboot_type == 'warm':
482            reboot_method = self.servo.get_power_state_controller().warm_reset
483        elif reboot_type == 'cold':
484            reboot_method = self.servo.get_power_state_controller().reset
485        else:
486            raise NotImplementedError('Not supported reboot_type: %s',
487                                      reboot_type)
488        if sync_before_boot:
489            boot_id = self.faft_framework.get_bootid()
490            self.faft_framework.blocking_sync()
491        logging.info("-[ModeSwitcher]-[ start simple_reboot(%r) ]-",
492                     reboot_type)
493        reboot_method()
494        if sync_before_boot:
495            self.wait_for_client_offline(orig_boot_id=boot_id)
496        logging.info("-[ModeSwitcher]-[ end simple_reboot(%r) ]-",
497                     reboot_type)
498
499    def mode_aware_reboot(self, reboot_type=None, reboot_method=None,
500                          sync_before_boot=True, wait_for_dut_up=True):
501        """Uses a mode-aware way to reboot DUT.
502
503        For example, if DUT is in dev mode, it requires pressing Ctrl-D to
504        bypass the developer screen.
505
506        @param reboot_type: A string of reboot type, one of 'warm', 'cold', or
507                            'custom'. Default is a warm reboot.
508        @param reboot_method: A custom method to do the reboot. Only use it if
509                              reboot_type='custom'.
510        @param sync_before_boot: True to sync to disk before booting.
511                                 If sync_before_boot=False, DUT offline before
512                                 calling mode_aware_reboot.
513        @param wait_for_dut_up: True to wait DUT online again. False to do the
514                                reboot only.
515        """
516        if reboot_type is None or reboot_type == 'warm':
517            reboot_method = self.servo.get_power_state_controller().warm_reset
518        elif reboot_type == 'cold':
519            reboot_method = self.servo.get_power_state_controller().reset
520        elif reboot_type != 'custom':
521            raise NotImplementedError('Not supported reboot_type: %s',
522                                      reboot_type)
523
524        logging.info("-[ModeSwitcher]-[ start mode_aware_reboot(%r, %s, ..) ]-",
525                     reboot_type, reboot_method.__name__)
526        is_dev = is_rec = is_devsw_boot = False
527        if sync_before_boot:
528            is_dev = self.checkers.mode_checker('dev')
529            is_rec = self.checkers.mode_checker('rec')
530            is_devsw_boot = self.checkers.crossystem_checker(
531                                               {'devsw_boot': '1'}, True)
532            boot_id = self.faft_framework.get_bootid()
533            self.faft_framework.blocking_sync()
534        if is_rec:
535            logging.info("-[mode_aware_reboot]-[ is_rec=%s is_dev_switch=%s ]-",
536                         is_rec, is_devsw_boot)
537        else:
538            logging.info("-[mode_aware_reboot]-[ is_dev=%s ]-", is_dev)
539        reboot_method()
540        if sync_before_boot:
541            self.wait_for_client_offline(orig_boot_id=boot_id)
542        # Encapsulating the behavior of skipping dev firmware screen,
543        # hitting ctrl-D
544        # Note that if booting from recovery mode, we can predict the next
545        # boot based on the developer switch position at boot (devsw_boot).
546        # If devsw_boot is True, we will call bypass_dev_mode after reboot.
547        if is_dev or is_devsw_boot:
548            self.bypass_dev_mode()
549        if wait_for_dut_up:
550            self.wait_for_client()
551        logging.info("-[ModeSwitcher]-[ end mode_aware_reboot(%r, %s, ..) ]-",
552                     reboot_type, reboot_method.__name__)
553
554
555    def enable_rec_mode_and_reboot(self, usb_state=None):
556        """Switch to rec mode and reboot.
557
558        This method emulates the behavior of the old physical recovery switch,
559        i.e. switch ON + reboot + switch OFF, and the new keyboard controlled
560        recovery mode, i.e. just press Power + Esc + Refresh.
561
562        @param usb_state: A string, one of 'dut', 'host', or 'off'.
563        """
564        psc = self.servo.get_power_state_controller()
565        psc.power_off()
566        if usb_state:
567            self.servo.switch_usbkey(usb_state)
568        psc.power_on(psc.REC_ON)
569
570
571    def _enable_rec_mode_force_mrc_and_reboot(self, usb_state=None):
572        """Switch to rec mode, enable force mrc cache retraining, and reboot.
573
574        This method emulates the behavior of the old physical recovery switch,
575        i.e. switch ON + reboot + switch OFF, and the new keyboard controlled
576        recovery mode, i.e. just press Power + Esc + Refresh.
577
578        @param usb_state: A string, one of 'dut', 'host', or 'off'.
579        """
580        psc = self.servo.get_power_state_controller()
581        psc.power_off()
582        if usb_state:
583            self.servo.switch_usbkey(usb_state)
584        psc.power_on(psc.REC_ON_FORCE_MRC)
585
586    def _disable_rec_mode_and_reboot(self, usb_state=None):
587        """Disable the rec mode and reboot.
588
589        It is achieved by calling power state controller to do a normal
590        power on.
591        """
592        psc = self.servo.get_power_state_controller()
593        psc.power_off()
594        time.sleep(self.faft_config.ec_boot_to_pwr_button)
595        psc.power_on(psc.REC_OFF)
596
597
598    def _enable_dev_mode_and_reboot(self):
599        """Switch to developer mode and reboot."""
600        raise NotImplementedError
601
602
603    def _enable_normal_mode_and_reboot(self):
604        """Switch to normal mode and reboot."""
605        raise NotImplementedError
606
607
608    # Redirects the following methods to FwBypasser
609    def bypass_dev_mode(self):
610        """Bypass the dev mode firmware logic to boot internal image."""
611        logging.info("-[bypass_dev_mode]-")
612        self.bypasser.bypass_dev_mode()
613
614
615    def bypass_dev_boot_usb(self):
616        """Bypass the dev mode firmware logic to boot USB."""
617        logging.info("-[bypass_dev_boot_usb]-")
618        self.bypasser.bypass_dev_boot_usb()
619
620
621    def bypass_rec_mode(self):
622        """Bypass the rec mode firmware logic to boot USB."""
623        logging.info("-[bypass_rec_mode]-")
624        self.bypasser.bypass_rec_mode()
625
626
627    def trigger_dev_to_rec(self):
628        """Trigger to the rec mode from the dev screen."""
629        self.bypasser.trigger_dev_to_rec()
630
631
632    def trigger_rec_to_dev(self):
633        """Trigger to the dev mode from the rec screen."""
634        self.bypasser.trigger_rec_to_dev()
635
636
637    def trigger_dev_to_normal(self):
638        """Trigger to the normal mode from the dev screen."""
639        self.bypasser.trigger_dev_to_normal()
640
641
642    def wait_for_client(self, timeout=180):
643        """Wait for the client to come back online.
644
645        New remote processes will be launched if their used flags are enabled.
646
647        @param timeout: Time in seconds to wait for the client SSH daemon to
648                        come up.
649        @raise ConnectionError: Failed to connect DUT.
650        """
651        logging.info("-[FAFT]-[ start wait_for_client ]---")
652        # Wait for the system to respond to ping before attempting ssh
653        if not self.client_host.ping_wait_up(timeout):
654            logging.warning("-[FAFT]-[ system did not respond to ping ]")
655        if self.client_host.wait_up(timeout):
656            # Check the FAFT client is avaiable.
657            self.faft_client.system.is_available()
658            # Stop update-engine as it may change firmware/kernel.
659            self.faft_framework.faft_client.updater.stop_daemon()
660        else:
661            logging.error('wait_for_client() timed out.')
662            raise ConnectionError('DUT is still down unexpectedly')
663        logging.info("-[FAFT]-[ end wait_for_client ]-----")
664
665
666    def wait_for_client_offline(self, timeout=60, orig_boot_id=None):
667        """Wait for the client to come offline.
668
669        @param timeout: Time in seconds to wait the client to come offline.
670        @param orig_boot_id: A string containing the original boot id.
671        @raise ConnectionError: Failed to wait DUT offline.
672        """
673        # When running against panther, we see that sometimes
674        # ping_wait_down() does not work correctly. There needs to
675        # be some investigation to the root cause.
676        # If we sleep for 120s before running get_boot_id(), it
677        # does succeed. But if we change this to ping_wait_down()
678        # there are implications on the wait time when running
679        # commands at the fw screens.
680        if not self.client_host.ping_wait_down(timeout):
681            if orig_boot_id and self.client_host.get_boot_id() != orig_boot_id:
682                logging.warn('Reboot done very quickly.')
683                return
684            raise ConnectionError('DUT is still up unexpectedly')
685
686
687class _KeyboardDevSwitcher(_BaseModeSwitcher):
688    """Class that switches firmware mode via keyboard combo."""
689
690    FW_BYPASSER_CLASS = _CtrlDBypasser
691
692    def _enable_dev_mode_and_reboot(self):
693        """Switch to developer mode and reboot."""
694        logging.info("Enabling keyboard controlled developer mode")
695        # Rebooting EC with rec mode on. Should power on AP.
696        # Plug out USB disk for preventing recovery boot without warning
697        self.enable_rec_mode_and_reboot(usb_state='host')
698        self.wait_for_client_offline()
699        self.bypasser.trigger_rec_to_dev()
700
701
702    def _enable_normal_mode_and_reboot(self):
703        """Switch to normal mode and reboot."""
704        logging.info("Disabling keyboard controlled developer mode")
705        self._disable_rec_mode_and_reboot()
706        self.wait_for_client_offline()
707        self.bypasser.trigger_dev_to_normal()
708
709
710class _JetstreamSwitcher(_BaseModeSwitcher):
711    """Class that switches firmware mode in Jetstream devices."""
712
713    FW_BYPASSER_CLASS = _JetstreamBypasser
714
715    def _enable_dev_mode_and_reboot(self):
716        """Switch to developer mode and reboot."""
717        logging.info("Enabling Jetstream developer mode")
718        self.enable_rec_mode_and_reboot(usb_state='host')
719        self.wait_for_client_offline()
720        self.bypasser.trigger_rec_to_dev()
721
722
723    def _enable_normal_mode_and_reboot(self):
724        """Switch to normal mode and reboot."""
725        logging.info("Disabling Jetstream developer mode")
726        self.servo.disable_development_mode()
727        self.enable_rec_mode_and_reboot(usb_state='host')
728        time.sleep(self.faft_config.firmware_screen)
729        self._disable_rec_mode_and_reboot(usb_state='host')
730
731
732class _TabletDetachableSwitcher(_BaseModeSwitcher):
733    """Class that switches fw mode in tablets/detachables with fw menu UI."""
734
735    FW_BYPASSER_CLASS = _TabletDetachableBypasser
736
737    def _enable_dev_mode_and_reboot(self):
738        """Switch to developer mode and reboot.
739
740        On tablets/ detachables, recovery entered by pressing pwr, vol up
741        & vol down buttons for 10s.
742           Menu options seen in RECOVERY screen:
743                 Enable Developer Mode
744                 Show Debug Info
745                 Power off*
746                 Language
747        """
748        logging.info('Enabling tablets/detachable recovery mode')
749        self.enable_rec_mode_and_reboot(usb_state='host')
750        self.wait_for_client_offline()
751        self.bypasser.trigger_rec_to_dev()
752
753
754    def _enable_normal_mode_and_reboot(self):
755        """Switch to normal mode and reboot.
756
757           Menu options seen in DEVELOPER WARNING screen:
758                 Developer Options
759                 Show Debug Info
760                 Enable Root Verification
761                 Power Off*
762                 Language
763           Menu options seen in TO_NORM screen:
764                 Confirm Enabling Verified Boot
765                 Cancel
766                 Power off*
767                 Language
768        Vol up button selects previous item, vol down button selects
769        next item and pwr button selects current activated item.
770        """
771        self._disable_rec_mode_and_reboot()
772        self.wait_for_client_offline()
773        self.bypasser.trigger_dev_to_normal()
774
775
776_SWITCHER_CLASSES = {
777    'keyboard_dev_switcher': _KeyboardDevSwitcher,
778    'jetstream_switcher': _JetstreamSwitcher,
779    'tablet_detachable_switcher': _TabletDetachableSwitcher}
780
781
782def create_mode_switcher(faft_framework):
783    """Creates a proper mode switcher.
784
785    @param faft_framework: The main FAFT framework object.
786    """
787    switcher_type = faft_framework.faft_config.mode_switcher_type
788    switcher_class = _SWITCHER_CLASSES.get(switcher_type, None)
789    if switcher_class is None:
790        raise NotImplementedError('Not supported mode_switcher_type: %s',
791                                  switcher_type)
792    else:
793        return switcher_class(faft_framework)