mode_switcher.py revision f9ded098bc87317cc0058823894f4bc4914cb9b9
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
8
9class _BaseFwBypasser(object):
10    """Base class that controls bypass logic for firmware screens."""
11
12    def __init__(self, servo, faft_config):
13        self.servo = servo
14        self.faft_config = faft_config
15
16
17    def bypass_dev_mode(self):
18        """Bypass the dev mode firmware logic to boot internal image."""
19        raise NotImplementedError
20
21
22    def bypass_dev_boot_usb(self):
23        """Bypass the dev mode firmware logic to boot USB."""
24        raise NotImplementedError
25
26
27    def bypass_rec_mode(self):
28        """Bypass the rec mode firmware logic to boot USB."""
29        raise NotImplementedError
30
31
32    def trigger_dev_to_rec(self):
33        """Trigger to the rec mode from the dev screen."""
34        raise NotImplementedError
35
36
37    def trigger_rec_to_dev(self):
38        """Trigger to the dev mode from the rec screen."""
39        raise NotImplementedError
40
41
42    def trigger_dev_to_normal(self):
43        """Trigger to the normal mode from the dev screen."""
44        raise NotImplementedError
45
46
47class _CtrlDBypasser(_BaseFwBypasser):
48    """Controls bypass logic via Ctrl-D combo."""
49
50    def bypass_dev_mode(self):
51        """Bypass the dev mode firmware logic to boot internal image."""
52        time.sleep(self.faft_config.firmware_screen)
53        self.servo.ctrl_d()
54
55
56    def bypass_dev_boot_usb(self):
57        """Bypass the dev mode firmware logic to boot USB."""
58        time.sleep(self.faft_config.firmware_screen)
59        self.servo.ctrl_u()
60
61
62    def bypass_rec_mode(self):
63        """Bypass the rec mode firmware logic to boot USB."""
64        self.servo.switch_usbkey('host')
65        time.sleep(self.faft_config.usb_plug)
66        self.servo.switch_usbkey('dut')
67
68
69    def trigger_dev_to_rec(self):
70        """Trigger to the rec mode from the dev screen."""
71        time.sleep(self.faft_config.firmware_screen)
72
73        # Pressing Enter for too long triggers a second key press.
74        # Let's press it without delay
75        self.servo.enter_key(press_secs=0)
76
77        # For Alex/ZGB, there is a dev warning screen in text mode.
78        # Skip it by pressing Ctrl-D.
79        if self.faft_config.need_dev_transition:
80            time.sleep(self.faft_config.legacy_text_screen)
81            self.servo.ctrl_d()
82
83
84    def trigger_rec_to_dev(self):
85        """Trigger to the dev mode from the rec screen."""
86        time.sleep(self.faft_config.firmware_screen)
87        self.servo.ctrl_d()
88        time.sleep(self.faft_config.confirm_screen)
89        if self.faft_config.rec_button_dev_switch:
90            logging.info('RECOVERY button pressed to switch to dev mode')
91            self.servo.toggle_recovery_switch()
92        else:
93            logging.info('ENTER pressed to switch to dev mode')
94            self.servo.enter_key()
95
96
97    def trigger_dev_to_normal(self):
98        """Trigger to the normal mode from the dev screen."""
99        time.sleep(self.faft_config.firmware_screen)
100        self.servo.enter_key()
101        time.sleep(self.faft_config.confirm_screen)
102        self.servo.enter_key()
103
104
105class _JetstreamBypasser(_BaseFwBypasser):
106    """Controls bypass logic of Jetstream devices."""
107
108    def bypass_dev_mode(self):
109        """Bypass the dev mode firmware logic to boot internal image."""
110        # Jetstream does nothing to bypass.
111        pass
112
113
114    def bypass_dev_boot_usb(self):
115        """Bypass the dev mode firmware logic to boot USB."""
116        # TODO: Confirm if it is a proper way to trigger dev boot USB.
117        # We can't verify it this time due to a bug that always boots into
118        # USB on dev mode.
119        self.servo.enable_development_mode()
120        self.servo.switch_usbkey('dut')
121        time.sleep(self.faft_config.firmware_screen)
122        self.servo.toggle_development_switch()
123
124
125    def bypass_rec_mode(self):
126        """Bypass the rec mode firmware logic to boot USB."""
127        self.servo.switch_usbkey('host')
128        time.sleep(self.faft_config.usb_plug)
129        self.servo.switch_usbkey('dut')
130
131
132    def trigger_dev_to_rec(self):
133        """Trigger to the rec mode from the dev screen."""
134        # Jetstream does not have this triggering logic.
135        raise NotImplementedError
136
137
138    def trigger_rec_to_dev(self):
139        """Trigger to the dev mode from the rec screen."""
140        self.servo.disable_development_mode()
141        time.sleep(self.faft_config.firmware_screen)
142        self.servo.toggle_development_switch()
143
144
145    def trigger_dev_to_normal(self):
146        """Trigger to the normal mode from the dev screen."""
147        # Jetstream does not have this triggering logic.
148        raise NotImplementedError
149
150
151def _create_fw_bypasser(servo, faft_config):
152    """Creates a proper firmware bypasser.
153
154    @param servo: A servo object controlling the servo device.
155    @param faft_config: A FAFT config object, which describes the type of
156                        firmware bypasser.
157    """
158    bypasser_type = faft_config.fw_bypasser_type
159    if bypasser_type == 'ctrl_d_bypasser':
160        logging.info('Create a CtrlDBypasser')
161        return _CtrlDBypasser(servo, faft_config)
162    if bypasser_type == 'jetstream_bypasser':
163        logging.info('Create a JetstreamBypasser')
164        return _JetstreamBypasser(servo, faft_config)
165    else:
166        raise NotImplementedError('Not supported fw_bypasser_type: %s',
167                                  bypasser_type)
168
169
170class _BaseModeSwitcher(object):
171    """Base class that controls firmware mode switching."""
172
173    def __init__(self, faft_framework):
174        self.faft_framework = faft_framework
175        self.faft_client = faft_framework.faft_client
176        self.servo = faft_framework.servo
177        self.faft_config = faft_framework.faft_config
178        self.checkers = faft_framework.checkers
179        self.bypasser = _create_fw_bypasser(self.servo, self.faft_config)
180        self._backup_mode = None
181
182
183    def setup_mode(self, mode):
184        """Setup for the requested mode.
185
186        It makes sure the system in the requested mode. If not, it tries to
187        do so.
188
189        @param mode: A string of mode, one of 'normal', 'dev', or 'rec'.
190        """
191        if not self.checkers.mode_checker(mode):
192            logging.info('System not in expected %s mode. Reboot into it.',
193                         mode)
194            if self._backup_mode is None:
195                # Only resume to normal/dev mode after test, not recovery.
196                self._backup_mode = 'dev' if mode == 'normal' else 'normal'
197            self.reboot_to_mode(mode)
198
199
200    def restore_mode(self):
201        """Restores original dev mode status if it has changed."""
202        if self._backup_mode is not None:
203            self.reboot_to_mode(self._backup_mode)
204
205
206    def reboot_to_mode(self, to_mode, from_mode=None, sync_before_boot=True,
207                       wait_for_dut_up=True):
208        """Reboot and execute the mode switching sequence.
209
210        @param to_mode: The target mode, one of 'normal', 'dev', or 'rec'.
211        @param from_mode: The original mode, optional, one of 'normal, 'dev',
212                          or 'rec'.
213        @param sync_before_boot: True to sync to disk before booting.
214        @param wait_for_dut_up: True to wait DUT online again. False to do the
215                                reboot and mode switching sequence only and may
216                                need more operations to pass the firmware
217                                screen.
218        """
219        logging.info('-[ModeSwitcher]-[ start reboot_to_mode(%r, %r, %r) ]-',
220                     to_mode, from_mode, wait_for_dut_up)
221        if sync_before_boot:
222            self.faft_framework.blocking_sync()
223        if to_mode == 'rec':
224            self._enable_rec_mode_and_reboot(usb_state='dut')
225            if wait_for_dut_up:
226                self.bypasser.bypass_rec_mode()
227                self.faft_framework.wait_for_client()
228
229        elif to_mode == 'dev':
230            self._enable_dev_mode_and_reboot()
231            if wait_for_dut_up:
232                self.bypasser.bypass_dev_mode()
233                self.faft_framework.wait_for_client()
234
235        elif to_mode == 'normal':
236            self._enable_normal_mode_and_reboot()
237            if wait_for_dut_up:
238                self.faft_framework.wait_for_client()
239
240        else:
241            raise NotImplementedError(
242                    'Not supported mode switching from %s to %s' %
243                     (str(from_mode), to_mode))
244        logging.info('-[ModeSwitcher]-[ end reboot_to_mode(%r, %r, %r) ]-',
245                     to_mode, from_mode, wait_for_dut_up)
246
247
248    def mode_aware_reboot(self, reboot_type=None, reboot_method=None,
249                          sync_before_boot=True, wait_for_dut_up=True):
250        """Uses a mode-aware way to reboot DUT.
251
252        For example, if DUT is in dev mode, it requires pressing Ctrl-D to
253        bypass the developer screen.
254
255        @param reboot_type: A string of reboot type, one of 'warm', 'cold', or
256                            'custom'. Default is a warm reboot.
257        @param reboot_method: A custom method to do the reboot. Only use it if
258                              reboot_type='custom'.
259        @param sync_before_boot: True to sync to disk before booting.
260        @param wait_for_dut_up: True to wait DUT online again. False to do the
261                                reboot only.
262        """
263        if reboot_type is None or reboot_type == 'warm':
264            reboot_method = self.servo.get_power_state_controller().warm_reset
265        elif reboot_type == 'cold':
266            reboot_method = self.servo.get_power_state_controller().reset
267        elif reboot_type != 'custom':
268            raise NotImplementedError('Not supported reboot_type: %s',
269                                      reboot_type)
270
271        logging.info("-[ModeSwitcher]-[ start mode_aware_reboot(%r, %s, ..) ]-",
272                     reboot_type, reboot_method.__name__)
273        is_normal = is_dev = False
274        if sync_before_boot:
275            if wait_for_dut_up:
276                is_normal = self.checkers.mode_checker('normal')
277                is_dev = self.checkers.mode_checker('dev')
278            boot_id = self.faft_framework.get_bootid()
279            self.faft_framework.blocking_sync()
280        reboot_method()
281        if sync_before_boot:
282            self.faft_framework.wait_for_client_offline(orig_boot_id=boot_id)
283        if wait_for_dut_up:
284            # For encapsulating the behavior of skipping firmware screen,
285            # e.g. requiring unplug and plug USB, the variants are not
286            # hard coded in tests. We keep this logic in this
287            # mode_aware_reboot method.
288            if not is_dev:
289                # In the normal/recovery boot flow, replugging USB does not
290                # affect the boot flow. But when something goes wrong, like
291                # firmware corrupted, it automatically leads to a recovery USB
292                # boot.
293                self.servo.switch_usbkey('host')
294            if not is_normal:
295                self.bypasser.bypass_dev_mode()
296            if not is_dev:
297                self.bypasser.bypass_rec_mode()
298            self.faft_framework.wait_for_kernel_up()
299        logging.info("-[ModeSwitcher]-[ end mode_aware_reboot(%r, %s, ..) ]-",
300                     reboot_type, reboot_method.__name__)
301
302
303    def _enable_rec_mode_and_reboot(self, usb_state=None):
304        """Switch to rec mode and reboot.
305
306        This method emulates the behavior of the old physical recovery switch,
307        i.e. switch ON + reboot + switch OFF, and the new keyboard controlled
308        recovery mode, i.e. just press Power + Esc + Refresh.
309
310        @param usb_state: A string, one of 'dut', 'host', or 'off'.
311        """
312        psc = self.servo.get_power_state_controller()
313        psc.power_off()
314        if usb_state:
315            self.servo.switch_usbkey(usb_state)
316        psc.power_on(psc.REC_ON)
317
318
319    def _disable_rec_mode_and_reboot(self, usb_state=None):
320        """Disable the rec mode and reboot.
321
322        It is achieved by calling power state controller to do a normal
323        power on.
324        """
325        psc = self.servo.get_power_state_controller()
326        psc.power_off()
327        psc.power_on(psc.REC_OFF)
328
329
330    def _enable_dev_mode_and_reboot(self):
331        """Switch to developer mode and reboot."""
332        raise NotImplementedError
333
334
335    def _enable_normal_mode_and_reboot(self):
336        """Switch to normal mode and reboot."""
337        raise NotImplementedError
338
339
340    # Redirects the following methods to FwBypasser
341    def bypass_dev_mode(self):
342        """Bypass the dev mode firmware logic to boot internal image."""
343        self.bypasser.bypass_dev_mode()
344
345
346    def bypass_dev_boot_usb(self):
347        """Bypass the dev mode firmware logic to boot USB."""
348        self.bypasser.bypass_dev_boot_usb()
349
350
351    def bypass_rec_mode(self):
352        """Bypass the rec mode firmware logic to boot USB."""
353        self.bypasser.bypass_rec_mode()
354
355
356    def trigger_dev_to_rec(self):
357        """Trigger to the rec mode from the dev screen."""
358        self.bypasser.trigger_dev_to_rec()
359
360
361    def trigger_rec_to_dev(self):
362        """Trigger to the dev mode from the rec screen."""
363        self.bypasser.trigger_rec_to_dev()
364
365
366    def trigger_dev_to_normal(self):
367        """Trigger to the normal mode from the dev screen."""
368        self.bypasser.trigger_dev_to_normal()
369
370
371class _PhysicalButtonSwitcher(_BaseModeSwitcher):
372    """Class that switches firmware mode via physical button."""
373
374    def _enable_dev_mode_and_reboot(self):
375        """Switch to developer mode and reboot."""
376        self.servo.enable_development_mode()
377        self.faft_client.system.run_shell_command(
378                'chromeos-firmwareupdate --mode todev && reboot')
379
380
381    def _enable_normal_mode_and_reboot(self):
382        """Switch to normal mode and reboot."""
383        self.servo.disable_development_mode()
384        self.faft_client.system.run_shell_command(
385                'chromeos-firmwareupdate --mode tonormal && reboot')
386
387
388class _KeyboardDevSwitcher(_BaseModeSwitcher):
389    """Class that switches firmware mode via keyboard combo."""
390
391    def _enable_dev_mode_and_reboot(self):
392        """Switch to developer mode and reboot."""
393        logging.info("Enabling keyboard controlled developer mode")
394        # Rebooting EC with rec mode on. Should power on AP.
395        # Plug out USB disk for preventing recovery boot without warning
396        self._enable_rec_mode_and_reboot(usb_state='host')
397        self.faft_framework.wait_for_client_offline()
398        self.bypasser.trigger_rec_to_dev()
399
400
401    def _enable_normal_mode_and_reboot(self):
402        """Switch to normal mode and reboot."""
403        logging.info("Disabling keyboard controlled developer mode")
404        self._disable_rec_mode_and_reboot()
405        self.faft_framework.wait_for_client_offline()
406        self.bypasser.trigger_dev_to_normal()
407
408
409class _JetstreamSwitcher(_BaseModeSwitcher):
410    """Class that switches firmware mode in Jetstream devices."""
411
412    def _enable_dev_mode_and_reboot(self):
413        """Switch to developer mode and reboot."""
414        logging.info("Enabling Jetstream developer mode")
415        self._enable_rec_mode_and_reboot(usb_state='host')
416        self.faft_framework.wait_for_client_offline()
417        self.bypasser.trigger_rec_to_dev()
418
419
420    def _enable_normal_mode_and_reboot(self):
421        """Switch to normal mode and reboot."""
422        logging.info("Disabling Jetstream developer mode")
423        self.servo.disable_development_mode()
424        self._enable_rec_mode_and_reboot(usb_state='host')
425        time.sleep(self.faft_config.firmware_screen)
426        self._disable_rec_mode_and_reboot(usb_state='host')
427
428
429def create_mode_switcher(faft_framework):
430    """Creates a proper mode switcher.
431
432    @param faft_framework: The main FAFT framework object.
433    """
434    switcher_type = faft_framework.faft_config.mode_switcher_type
435    if switcher_type == 'physical_button_switcher':
436        logging.info('Create a PhysicalButtonSwitcher')
437        return _PhysicalButtonSwitcher(faft_framework)
438    elif switcher_type == 'keyboard_dev_switcher':
439        logging.info('Create a KeyboardDevSwitcher')
440        return _KeyboardDevSwitcher(faft_framework)
441    elif switcher_type == 'jetstream_switcher':
442        logging.info('Create a JetstreamSwitcher')
443        return _JetstreamSwitcher(faft_framework)
444    else:
445        raise NotImplementedError('Not supported mode_switcher_type: %s',
446                                  switcher_type)
447