• 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        self.servo.set_nocheck('volume_up_hold', 100)
268        time.sleep(self.faft_config.confirm_screen)
269        self.servo.set_nocheck('volume_up_hold', 100)
270        time.sleep(self.faft_config.confirm_screen)
271        self.set_button('volume_up_hold', 100, ('Selecting power '
272                        'as enter key to select Developer Options'))
273        self.servo.power_short_press()
274
275
276    def trigger_rec_to_dev(self):
277        """Trigger to the dev mode from the rec screen using vol up button.
278
279        On tablets/ detachables, recovery entered by pressing pwr, vol up
280        & vol down buttons for 10s. TO_DEV screen is entered by pressing
281        vol up & vol down buttons together on the INSERT screen.
282           Menu options seen in TO_DEV screen:
283                 Confirm enabling developer mode
284                 Cancel*
285                 Power off
286                 Language
287        Vol up button selects previous item, vol down button selects
288        next item and pwr button selects current activated item.
289        """
290        time.sleep(self.faft_config.firmware_screen)
291        self.set_button('volume_up_down_hold', 100, ('Enter Recovery Menu.'))
292        time.sleep(self.faft_config.confirm_screen)
293        self.set_button('volume_up_hold', 100, ('Selecting power as '
294                        'enter key to select Confirm Enabling Developer Mode'))
295        self.servo.power_short_press()
296        time.sleep(self.faft_config.firmware_screen)
297
298
299    def trigger_dev_to_normal(self):
300        """Trigger to the normal mode from the dev screen.
301
302           Menu options seen in DEVELOPER WARNING screen:
303                 Developer Options
304                 Show Debug Info
305                 Enable Root Verification
306                 Power Off*
307                 Language
308           Menu options seen in TO_NORM screen:
309                 Confirm Enabling Verified Boot*
310                 Cancel
311                 Power off
312                 Language
313        Vol up button selects previous item, vol down button selects
314        next item and pwr button selects current activated item.
315        """
316        time.sleep(self.faft_config.firmware_screen)
317        self.set_button('volume_up_hold', 100, ('Selecting '
318                        'Enable Root Verification using pwr '
319                        'button to enter TO_NORM screen'))
320        self.servo.power_short_press()
321        logging.info('Transitioning from DEV to TO_NORM screen.')
322        time.sleep(self.faft_config.firmware_screen)
323        logging.info('Selecting Confirm Enabling Verified '
324                        'Boot using pwr button in '
325                        'TO_NORM screen')
326        self.servo.power_short_press()
327
328    def trigger_dev_to_rec(self):
329        """Trigger to the TO_NORM screen from the dev screen.
330           Menu options seen in DEVELOPER WARNING screen:
331                 Developer Options
332                 Show Debug Info
333                 Enable Root Verification
334                 Power Off*
335                 Language
336           Menu options seen in TO_NORM screen:
337                 Confirm Enabling Verified Boot*
338                 Cancel
339                 Power off
340                 Language
341        Vol up button selects previous item, vol down button selects
342        next item and pwr button selects current activated item.
343        """
344        time.sleep(self.faft_config.firmware_screen)
345        self.set_button('volume_up_hold', 100, ('Selecting '
346                        'Enable Root Verification using pwr '
347                        'button to enter TO_NORM screen'))
348        self.servo.power_short_press()
349        logging.info('Transitioning from DEV to TO_NORM screen.')
350        time.sleep(self.faft_config.firmware_screen)
351
352        # In firmware_FwScreenPressPower, test will power off the DUT using
353        # Power button in second screen (TO_NORM screen) so scrolling to
354        # Power-off is necessary in this case. Hence scroll to Power-off as
355        # a generic action and wait for next action of either Lid close or
356        # power button press.
357        self.servo.set_nocheck('volume_down_hold', 100)
358        time.sleep(self.faft_config.confirm_screen)
359        self.servo.set_nocheck('volume_down_hold', 100)
360        time.sleep(self.faft_config.confirm_screen)
361
362def _create_fw_bypasser(faft_framework):
363    """Creates a proper firmware bypasser.
364
365    @param faft_framework: The main FAFT framework object.
366    """
367    bypasser_type = faft_framework.faft_config.fw_bypasser_type
368    if bypasser_type == 'ctrl_d_bypasser':
369        logging.info('Create a CtrlDBypasser')
370        return _CtrlDBypasser(faft_framework)
371    elif bypasser_type == 'jetstream_bypasser':
372        logging.info('Create a JetstreamBypasser')
373        return _JetstreamBypasser(faft_framework)
374    elif bypasser_type == 'ryu_bypasser':
375        # FIXME Create an RyuBypasser
376        logging.info('Create a CtrlDBypasser')
377        return _CtrlDBypasser(faft_framework)
378    elif bypasser_type == 'tablet_detachable_bypasser':
379        logging.info('Create a TabletDetachableBypasser')
380        return _TabletDetachableBypasser(faft_framework)
381    else:
382        raise NotImplementedError('Not supported fw_bypasser_type: %s',
383                                  bypasser_type)
384
385
386class _BaseModeSwitcher(object):
387    """Base class that controls firmware mode switching."""
388
389    def __init__(self, faft_framework):
390        self.faft_framework = faft_framework
391        self.client_host = faft_framework._client
392        self.faft_client = faft_framework.faft_client
393        self.servo = faft_framework.servo
394        self.faft_config = faft_framework.faft_config
395        self.checkers = faft_framework.checkers
396        self.bypasser = _create_fw_bypasser(faft_framework)
397        self._backup_mode = None
398
399
400    def setup_mode(self, mode):
401        """Setup for the requested mode.
402
403        It makes sure the system in the requested mode. If not, it tries to
404        do so.
405
406        @param mode: A string of mode, one of 'normal', 'dev', or 'rec'.
407        @raise TestFail: If the system not switched to expected mode after
408                         reboot_to_mode.
409
410        """
411        if not self.checkers.mode_checker(mode):
412            logging.info('System not in expected %s mode. Reboot into it.',
413                         mode)
414            if self._backup_mode is None:
415                # Only resume to normal/dev mode after test, not recovery.
416                self._backup_mode = 'dev' if mode == 'normal' else 'normal'
417            self.reboot_to_mode(mode)
418            if not self.checkers.mode_checker(mode):
419                raise error.TestFail('System not switched to expected %s'
420                        ' mode after setup_mode.' % mode)
421
422    def restore_mode(self):
423        """Restores original dev mode status if it has changed.
424
425        @raise TestFail: If the system not restored to expected mode.
426        """
427        if (self._backup_mode is not None and
428            not self.checkers.mode_checker(self._backup_mode)):
429            self.reboot_to_mode(self._backup_mode)
430            if not self.checkers.mode_checker(self._backup_mode):
431                raise error.TestFail('System not restored to expected %s'
432                        ' mode in cleanup.' % self._backup_mode)
433
434
435
436    def reboot_to_mode(self, to_mode, from_mode=None, sync_before_boot=True,
437                       wait_for_dut_up=True):
438        """Reboot and execute the mode switching sequence.
439
440        @param to_mode: The target mode, one of 'normal', 'dev', or 'rec'.
441        @param from_mode: The original mode, optional, one of 'normal, 'dev',
442                          or 'rec'.
443        @param sync_before_boot: True to sync to disk before booting.
444        @param wait_for_dut_up: True to wait DUT online again. False to do the
445                                reboot and mode switching sequence only and may
446                                need more operations to pass the firmware
447                                screen.
448        """
449        logging.info('-[ModeSwitcher]-[ start reboot_to_mode(%r, %r, %r) ]-',
450                     to_mode, from_mode, wait_for_dut_up)
451        if sync_before_boot:
452            self.faft_framework.blocking_sync()
453        if to_mode == 'rec':
454            self._enable_rec_mode_and_reboot(usb_state='dut')
455            if wait_for_dut_up:
456                self.wait_for_client()
457
458        elif to_mode == 'dev':
459            self._enable_dev_mode_and_reboot()
460            if wait_for_dut_up:
461                self.bypass_dev_mode()
462                self.wait_for_client()
463
464        elif to_mode == 'normal':
465            self._enable_normal_mode_and_reboot()
466            if wait_for_dut_up:
467                self.wait_for_client()
468
469        else:
470            raise NotImplementedError(
471                    'Not supported mode switching from %s to %s' %
472                     (str(from_mode), to_mode))
473        logging.info('-[ModeSwitcher]-[ end reboot_to_mode(%r, %r, %r) ]-',
474                     to_mode, from_mode, wait_for_dut_up)
475
476    def simple_reboot(self, reboot_type='warm', sync_before_boot=True):
477        """Simple reboot method
478
479        Just reboot the DUT using either cold or warm reset.  Does not wait for
480        DUT to come back online.  Will wait for test to handle this.
481
482        @param reboot_type: A string of reboot type, 'warm' or 'cold'.
483                            If reboot_type != warm/cold, raise exception.
484        @param sync_before_boot: True to sync to disk before booting.
485                                 If sync_before_boot=False, DUT offline before
486                                 calling mode_aware_reboot.
487        """
488        if reboot_type == 'warm':
489            reboot_method = self.servo.get_power_state_controller().warm_reset
490        elif reboot_type == 'cold':
491            reboot_method = self.servo.get_power_state_controller().reset
492        else:
493            raise NotImplementedError('Not supported reboot_type: %s',
494                                      reboot_type)
495        if sync_before_boot:
496            boot_id = self.faft_framework.get_bootid()
497            self.faft_framework.blocking_sync()
498        logging.info("-[ModeSwitcher]-[ start simple_reboot(%r) ]-",
499                     reboot_type)
500        reboot_method()
501        if sync_before_boot:
502            self.wait_for_client_offline(orig_boot_id=boot_id)
503        logging.info("-[ModeSwitcher]-[ end simple_reboot(%r) ]-",
504                     reboot_type)
505
506    def mode_aware_reboot(self, reboot_type=None, reboot_method=None,
507                          sync_before_boot=True, wait_for_dut_up=True):
508        """Uses a mode-aware way to reboot DUT.
509
510        For example, if DUT is in dev mode, it requires pressing Ctrl-D to
511        bypass the developer screen.
512
513        @param reboot_type: A string of reboot type, one of 'warm', 'cold', or
514                            'custom'. Default is a warm reboot.
515        @param reboot_method: A custom method to do the reboot. Only use it if
516                              reboot_type='custom'.
517        @param sync_before_boot: True to sync to disk before booting.
518                                 If sync_before_boot=False, DUT offline before
519                                 calling mode_aware_reboot.
520        @param wait_for_dut_up: True to wait DUT online again. False to do the
521                                reboot only.
522        """
523        if reboot_type is None or reboot_type == 'warm':
524            reboot_method = self.servo.get_power_state_controller().warm_reset
525        elif reboot_type == 'cold':
526            reboot_method = self.servo.get_power_state_controller().reset
527        elif reboot_type != 'custom':
528            raise NotImplementedError('Not supported reboot_type: %s',
529                                      reboot_type)
530
531        logging.info("-[ModeSwitcher]-[ start mode_aware_reboot(%r, %s, ..) ]-",
532                     reboot_type, reboot_method.__name__)
533        is_dev = is_rec = is_devsw_boot = False
534        if sync_before_boot:
535            is_dev = self.checkers.mode_checker('dev')
536            is_rec = self.checkers.mode_checker('rec')
537            is_devsw_boot = self.checkers.crossystem_checker(
538                                               {'devsw_boot': '1'}, True)
539            boot_id = self.faft_framework.get_bootid()
540            self.faft_framework.blocking_sync()
541        if is_rec:
542            logging.info("-[mode_aware_reboot]-[ is_rec=%s is_dev_switch=%s ]-",
543                         is_rec, is_devsw_boot)
544        else:
545            logging.info("-[mode_aware_reboot]-[ is_dev=%s ]-", is_dev)
546        reboot_method()
547        if sync_before_boot:
548            self.wait_for_client_offline(orig_boot_id=boot_id)
549        # Encapsulating the behavior of skipping dev firmware screen,
550        # hitting ctrl-D
551        # Note that if booting from recovery mode, we can predict the next
552        # boot based on the developer switch position at boot (devsw_boot).
553        # If devsw_boot is True, we will call bypass_dev_mode after reboot.
554        if is_dev or is_devsw_boot:
555            self.bypass_dev_mode()
556        if wait_for_dut_up:
557            self.wait_for_client()
558        logging.info("-[ModeSwitcher]-[ end mode_aware_reboot(%r, %s, ..) ]-",
559                     reboot_type, reboot_method.__name__)
560
561
562    def _enable_rec_mode_and_reboot(self, usb_state=None):
563        """Switch to rec mode and reboot.
564
565        This method emulates the behavior of the old physical recovery switch,
566        i.e. switch ON + reboot + switch OFF, and the new keyboard controlled
567        recovery mode, i.e. just press Power + Esc + Refresh.
568
569        @param usb_state: A string, one of 'dut', 'host', or 'off'.
570        """
571        psc = self.servo.get_power_state_controller()
572        psc.power_off()
573        if usb_state:
574            self.servo.switch_usbkey(usb_state)
575        psc.power_on(psc.REC_ON)
576
577
578    def _disable_rec_mode_and_reboot(self, usb_state=None):
579        """Disable the rec mode and reboot.
580
581        It is achieved by calling power state controller to do a normal
582        power on.
583        """
584        psc = self.servo.get_power_state_controller()
585        psc.power_off()
586        time.sleep(self.faft_config.ec_boot_to_pwr_button)
587        psc.power_on(psc.REC_OFF)
588
589
590    def _enable_dev_mode_and_reboot(self):
591        """Switch to developer mode and reboot."""
592        raise NotImplementedError
593
594
595    def _enable_normal_mode_and_reboot(self):
596        """Switch to normal mode and reboot."""
597        raise NotImplementedError
598
599
600    # Redirects the following methods to FwBypasser
601    def bypass_dev_mode(self):
602        """Bypass the dev mode firmware logic to boot internal image."""
603        logging.info("-[bypass_dev_mode]-")
604        self.bypasser.bypass_dev_mode()
605
606
607    def bypass_dev_boot_usb(self):
608        """Bypass the dev mode firmware logic to boot USB."""
609        logging.info("-[bypass_dev_boot_usb]-")
610        self.bypasser.bypass_dev_boot_usb()
611
612
613    def bypass_rec_mode(self):
614        """Bypass the rec mode firmware logic to boot USB."""
615        logging.info("-[bypass_rec_mode]-")
616        self.bypasser.bypass_rec_mode()
617
618
619    def trigger_dev_to_rec(self):
620        """Trigger to the rec mode from the dev screen."""
621        self.bypasser.trigger_dev_to_rec()
622
623
624    def trigger_rec_to_dev(self):
625        """Trigger to the dev mode from the rec screen."""
626        self.bypasser.trigger_rec_to_dev()
627
628
629    def trigger_dev_to_normal(self):
630        """Trigger to the normal mode from the dev screen."""
631        self.bypasser.trigger_dev_to_normal()
632
633
634    def wait_for_client(self, timeout=180):
635        """Wait for the client to come back online.
636
637        New remote processes will be launched if their used flags are enabled.
638
639        @param timeout: Time in seconds to wait for the client SSH daemon to
640                        come up.
641        @raise ConnectionError: Failed to connect DUT.
642        """
643        logging.info("-[FAFT]-[ start wait_for_client ]---")
644        # Wait for the system to respond to ping before attempting ssh
645        if not self.client_host.ping_wait_up(timeout):
646            logging.warning("-[FAFT]-[ system did not respond to ping ]")
647        if self.client_host.wait_up(timeout):
648            # Check the FAFT client is avaiable.
649            self.faft_client.system.is_available()
650            # Stop update-engine as it may change firmware/kernel.
651            self.faft_framework.faft_client.updater.stop_daemon()
652        else:
653            logging.error('wait_for_client() timed out.')
654            raise ConnectionError('DUT is still down unexpectedly')
655        logging.info("-[FAFT]-[ end wait_for_client ]-----")
656
657
658    def wait_for_client_offline(self, timeout=60, orig_boot_id=None):
659        """Wait for the client to come offline.
660
661        @param timeout: Time in seconds to wait the client to come offline.
662        @param orig_boot_id: A string containing the original boot id.
663        @raise ConnectionError: Failed to wait DUT offline.
664        """
665        # When running against panther, we see that sometimes
666        # ping_wait_down() does not work correctly. There needs to
667        # be some investigation to the root cause.
668        # If we sleep for 120s before running get_boot_id(), it
669        # does succeed. But if we change this to ping_wait_down()
670        # there are implications on the wait time when running
671        # commands at the fw screens.
672        if not self.client_host.ping_wait_down(timeout):
673            if orig_boot_id and self.client_host.get_boot_id() != orig_boot_id:
674                logging.warn('Reboot done very quickly.')
675                return
676            raise ConnectionError('DUT is still up unexpectedly')
677
678
679class _PhysicalButtonSwitcher(_BaseModeSwitcher):
680    """Class that switches firmware mode via physical button."""
681
682    def _enable_dev_mode_and_reboot(self):
683        """Switch to developer mode and reboot."""
684        self.servo.enable_development_mode()
685        self.faft_client.system.run_shell_command(
686                'chromeos-firmwareupdate --mode todev && reboot')
687
688
689    def _enable_normal_mode_and_reboot(self):
690        """Switch to normal mode and reboot."""
691        self.servo.disable_development_mode()
692        self.faft_client.system.run_shell_command(
693                'chromeos-firmwareupdate --mode tonormal && reboot')
694
695
696class _KeyboardDevSwitcher(_BaseModeSwitcher):
697    """Class that switches firmware mode via keyboard combo."""
698
699    def _enable_dev_mode_and_reboot(self):
700        """Switch to developer mode and reboot."""
701        logging.info("Enabling keyboard controlled developer mode")
702        # Rebooting EC with rec mode on. Should power on AP.
703        # Plug out USB disk for preventing recovery boot without warning
704        self._enable_rec_mode_and_reboot(usb_state='host')
705        self.wait_for_client_offline()
706        self.bypasser.trigger_rec_to_dev()
707
708
709    def _enable_normal_mode_and_reboot(self):
710        """Switch to normal mode and reboot."""
711        logging.info("Disabling keyboard controlled developer mode")
712        self._disable_rec_mode_and_reboot()
713        self.wait_for_client_offline()
714        self.bypasser.trigger_dev_to_normal()
715
716
717class _JetstreamSwitcher(_BaseModeSwitcher):
718    """Class that switches firmware mode in Jetstream devices."""
719
720    def _enable_dev_mode_and_reboot(self):
721        """Switch to developer mode and reboot."""
722        logging.info("Enabling Jetstream developer mode")
723        self._enable_rec_mode_and_reboot(usb_state='host')
724        self.wait_for_client_offline()
725        self.bypasser.trigger_rec_to_dev()
726
727
728    def _enable_normal_mode_and_reboot(self):
729        """Switch to normal mode and reboot."""
730        logging.info("Disabling Jetstream developer mode")
731        self.servo.disable_development_mode()
732        self._enable_rec_mode_and_reboot(usb_state='host')
733        time.sleep(self.faft_config.firmware_screen)
734        self._disable_rec_mode_and_reboot(usb_state='host')
735
736
737class _TabletDetachableSwitcher(_BaseModeSwitcher):
738    """Class that switches fw mode in tablets/detachables with fw menu UI."""
739
740    def _enable_dev_mode_and_reboot(self):
741        """Switch to developer mode and reboot.
742
743        On tablets/ detachables, recovery entered by pressing pwr, vol up
744        & vol down buttons for 10s.
745           Menu options seen in RECOVERY screen:
746                 Enable Developer Mode
747                 Show Debug Info
748                 Power off*
749                 Language
750        """
751        logging.info('Enabling tablets/detachable recovery mode')
752        self._enable_rec_mode_and_reboot(usb_state='host')
753        self.wait_for_client_offline()
754        self.bypasser.trigger_rec_to_dev()
755
756
757    def _enable_normal_mode_and_reboot(self):
758        """Switch to normal mode and reboot.
759
760           Menu options seen in DEVELOPER WARNING screen:
761                 Developer Options
762                 Show Debug Info
763                 Enable Root Verification
764                 Power Off*
765                 Language
766           Menu options seen in TO_NORM screen:
767                 Confirm Enabling Verified Boot
768                 Cancel
769                 Power off*
770                 Language
771        Vol up button selects previous item, vol down button selects
772        next item and pwr button selects current activated item.
773        """
774        self._disable_rec_mode_and_reboot()
775        self.wait_for_client_offline()
776        self.bypasser.trigger_dev_to_normal()
777
778
779class _RyuSwitcher(_BaseModeSwitcher):
780    """Class that switches firmware mode via physical button."""
781
782    FASTBOOT_OEM_DELAY = 10
783    RECOVERY_TIMEOUT = 2400
784    RECOVERY_SETUP = 60
785    ANDROID_BOOTUP = 600
786    FWTOOL_STARTUP_DELAY = 30
787
788    def wait_for_client(self, timeout=180):
789        """Wait for the client to come back online.
790
791        New remote processes will be launched if their used flags are enabled.
792
793        @param timeout: Time in seconds to wait for the client SSH daemon to
794                        come up.
795        @raise ConnectionError: Failed to connect DUT.
796        """
797        if not self.faft_client.system.wait_for_client(timeout):
798            raise ConnectionError('DUT is still down unexpectedly')
799
800        # there's a conflict between fwtool and crossystem trying to access
801        # the nvram after the OS boots up.  Temporarily put a hard wait of
802        # 30 seconds to try to wait for fwtool to finish up.
803        time.sleep(self.FWTOOL_STARTUP_DELAY)
804
805
806    def wait_for_client_offline(self, timeout=60, orig_boot_id=None):
807        """Wait for the client to come offline.
808
809        @param timeout: Time in seconds to wait the client to come offline.
810        @param orig_boot_id: A string containing the original boot id.
811        @raise ConnectionError: Failed to wait DUT offline.
812        """
813        # TODO: Add a way to check orig_boot_id
814        if not self.faft_client.system.wait_for_client_offline(timeout):
815            raise ConnectionError('DUT is still up unexpectedly')
816
817    def print_recovery_warning(self):
818        """Print recovery warning"""
819        logging.info("***")
820        logging.info("*** Entering recovery mode.  This may take awhile ***")
821        logging.info("***")
822        # wait a minute for DUT to get settled into wipe stage
823        time.sleep(self.RECOVERY_SETUP)
824
825    def is_fastboot_mode(self):
826        """Return True if DUT in fastboot mode, False otherwise"""
827        result = self.faft_client.host.run_shell_command_get_output(
828            'fastboot devices')
829        if not result:
830            return False
831        else:
832            return True
833
834    def wait_for_client_fastboot(self, timeout=30):
835        """Wait for the client to come online in fastboot mode
836
837        @param timeout: Time in seconds to wait the client
838        @raise ConnectionError: Failed to wait DUT offline.
839        """
840        utils.wait_for_value(self.is_fastboot_mode, True, timeout_sec=timeout)
841
842    def _run_cmd(self, args):
843        """Wrapper for run_shell_command
844
845        For Process creation
846        """
847        return self.faft_client.host.run_shell_command(args)
848
849    def _enable_dev_mode_and_reboot(self):
850        """Switch to developer mode and reboot."""
851        logging.info("Entering RyuSwitcher: _enable_dev_mode_and_reboot")
852        try:
853            self.faft_client.system.run_shell_command('reboot bootloader')
854            self.wait_for_client_fastboot()
855
856            process = Process(
857                target=self._run_cmd,
858                args=('fastboot oem unlock',))
859            process.start()
860
861            # need a slight delay to give the ap time to get into valid state
862            time.sleep(self.FASTBOOT_OEM_DELAY)
863            self.servo.power_key(self.faft_config.hold_pwr_button_poweron)
864            process.join()
865
866            self.print_recovery_warning()
867            self.wait_for_client_fastboot(self.RECOVERY_TIMEOUT)
868            self.faft_client.host.run_shell_command('fastboot continue')
869            self.wait_for_client(self.ANDROID_BOOTUP)
870
871        # need to reset DUT into clean state
872        except shell_wrapper.ShellError:
873            raise error.TestError('Error executing shell command')
874        except ConnectionError:
875            raise error.TestError('Timed out waiting for DUT to exit recovery')
876        except:
877            raise error.TestError('Unexpected Exception: %s' % sys.exc_info()[0])
878        logging.info("Exiting RyuSwitcher: _enable_dev_mode_and_reboot")
879
880    def _enable_normal_mode_and_reboot(self):
881        """Switch to normal mode and reboot."""
882        try:
883            self.faft_client.system.run_shell_command('reboot bootloader')
884            self.wait_for_client_fastboot()
885
886            process = Process(
887                target=self._run_cmd,
888                args=('fastboot oem lock',))
889            process.start()
890
891            # need a slight delay to give the ap time to get into valid state
892            time.sleep(self.FASTBOOT_OEM_DELAY)
893            self.servo.power_key(self.faft_config.hold_pwr_button_poweron)
894            process.join()
895
896            self.print_recovery_warning()
897            self.wait_for_client_fastboot(self.RECOVERY_TIMEOUT)
898            self.faft_client.host.run_shell_command('fastboot continue')
899            self.wait_for_client(self.ANDROID_BOOTUP)
900
901        # need to reset DUT into clean state
902        except shell_wrapper.ShellError:
903            raise error.TestError('Error executing shell command')
904        except ConnectionError:
905            raise error.TestError('Timed out waiting for DUT to exit recovery')
906        except:
907            raise error.TestError('Unexpected Exception: %s' % sys.exc_info()[0])
908        logging.info("Exiting RyuSwitcher: _enable_normal_mode_and_reboot")
909
910def create_mode_switcher(faft_framework):
911    """Creates a proper mode switcher.
912
913    @param faft_framework: The main FAFT framework object.
914    """
915    switcher_type = faft_framework.faft_config.mode_switcher_type
916    if switcher_type == 'physical_button_switcher':
917        logging.info('Create a PhysicalButtonSwitcher')
918        return _PhysicalButtonSwitcher(faft_framework)
919    elif switcher_type == 'keyboard_dev_switcher':
920        logging.info('Create a KeyboardDevSwitcher')
921        return _KeyboardDevSwitcher(faft_framework)
922    elif switcher_type == 'jetstream_switcher':
923        logging.info('Create a JetstreamSwitcher')
924        return _JetstreamSwitcher(faft_framework)
925    elif switcher_type == 'ryu_switcher':
926        logging.info('Create a RyuSwitcher')
927        return _RyuSwitcher(faft_framework)
928    elif switcher_type == 'tablet_detachable_switcher':
929        logging.info('Create a TabletDetachableSwitcher')
930        return _TabletDetachableSwitcher(faft_framework)
931    else:
932        raise NotImplementedError('Not supported mode_switcher_type: %s',
933                                  switcher_type)
934