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