mode_switcher.py revision ed4d67be4d0faa1a267d33e86905df1153d78333
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
105def _create_fw_bypasser(servo, faft_config):
106    """Creates a proper firmware bypasser.
107
108    @param servo: A servo object controlling the servo device.
109    @param faft_config: A FAFT config object, which describes the type of
110                        firmware bypasser.
111    """
112    bypasser_type = faft_config.fw_bypasser_type
113    if bypasser_type == 'ctrl_d_bypasser':
114        logging.info('Create a CtrlDBypasser')
115        return _CtrlDBypasser(servo, faft_config)
116    else:
117        raise NotImplementedError('Not supported fw_bypasser_type: %s',
118                                  bypasser_type)
119
120
121class _BaseModeSwitcher(object):
122    """Base class that controls firmware mode switching."""
123
124    def __init__(self, faft_framework):
125        self.faft_framework = faft_framework
126        self.faft_client = faft_framework.faft_client
127        self.servo = faft_framework.servo
128        self.faft_config = faft_framework.faft_config
129        self.checkers = faft_framework.checkers
130        self.bypasser = _create_fw_bypasser(self.servo, self.faft_config)
131        self._backup_mode = None
132
133
134    def setup_mode(self, mode):
135        """Setup for the requested mode.
136
137        It makes sure the system in the requested mode. If not, it tries to
138        do so.
139
140        @param mode: A string of mode, one of 'normal', 'dev', or 'rec'.
141        """
142        if not self.checkers.mode_checker(mode):
143            logging.info('System not in expected %s mode. Reboot into it.',
144                         mode)
145            if self._backup_mode is None:
146                # Only resume to normal/dev mode after test, not recovery.
147                self._backup_mode = 'dev' if mode == 'normal' else 'normal'
148            self.reboot_to_mode(mode)
149
150
151    def restore_mode(self):
152        """Restores original dev mode status if it has changed."""
153        if self._backup_mode is not None:
154            self.reboot_to_mode(self._backup_mode)
155
156
157    def reboot_to_mode(self, to_mode, from_mode=None, sync_before_boot=True,
158                       wait_for_dut_up=True):
159        """Reboot and execute the mode switching sequence.
160
161        @param to_mode: The target mode, one of 'normal', 'dev', or 'rec'.
162        @param from_mode: The original mode, optional, one of 'normal, 'dev',
163                          or 'rec'.
164        @param sync_before_boot: True to sync to disk before booting.
165        @param wait_for_dut_up: True to wait DUT online again. False to do the
166                                reboot and mode switching sequence only and may
167                                need more operations to pass the firmware
168                                screen.
169        """
170        logging.info('-[ModeSwitcher]-[ start reboot_to_mode(%r, %r, %r) ]-',
171                     to_mode, from_mode, wait_for_dut_up)
172        if sync_before_boot:
173            self.faft_framework.blocking_sync()
174        if to_mode == 'rec':
175            self._enable_rec_mode_and_reboot(usb_state='dut')
176            if wait_for_dut_up:
177                self.bypasser.bypass_rec_mode()
178                self.faft_framework.wait_for_client()
179
180        elif to_mode == 'dev':
181            self._enable_dev_mode_and_reboot()
182            if wait_for_dut_up:
183                self.bypasser.bypass_dev_mode()
184                self.faft_framework.wait_for_client()
185
186        elif to_mode == 'normal':
187            self._enable_normal_mode_and_reboot()
188            if wait_for_dut_up:
189                self.faft_framework.wait_for_client()
190
191        else:
192            raise NotImplementedError(
193                    'Not supported mode switching from %s to %s' %
194                     (str(from_mode), to_mode))
195        logging.info('-[ModeSwitcher]-[ end reboot_to_mode(%r, %r, %r) ]-',
196                     to_mode, from_mode, wait_for_dut_up)
197
198
199    def mode_aware_reboot(self, reboot_type=None, reboot_method=None,
200                          sync_before_boot=True, wait_for_dut_up=True):
201        """Uses a mode-aware way to reboot DUT.
202
203        For example, if DUT is in dev mode, it requires pressing Ctrl-D to
204        bypass the developer screen.
205
206        @param reboot_type: A string of reboot type, one of 'warm', 'cold', or
207                            'custom'. Default is a warm reboot.
208        @param reboot_method: A custom method to do the reboot. Only use it if
209                              reboot_type='custom'.
210        @param sync_before_boot: True to sync to disk before booting.
211        @param wait_for_dut_up: True to wait DUT online again. False to do the
212                                reboot only.
213        """
214        if reboot_type is None or reboot_type == 'warm':
215            reboot_method = self.servo.get_power_state_controller().warm_reset
216        elif reboot_type == 'cold':
217            reboot_method = self.servo.get_power_state_controller().reset
218        elif reboot_type != 'custom':
219            raise NotImplementedError('Not supported reboot_type: %s',
220                                      reboot_type)
221
222        logging.info("-[ModeSwitcher]-[ start mode_aware_reboot(%r, %s, ..) ]-",
223                     reboot_type, reboot_method.__name__)
224        is_normal = is_dev = False
225        if sync_before_boot:
226            if wait_for_dut_up:
227                is_normal = self.checkers.mode_checker('normal')
228                is_dev = self.checkers.mode_checker('dev')
229            boot_id = self.faft_framework.get_bootid()
230            self.faft_framework.blocking_sync()
231        reboot_method()
232        if sync_before_boot:
233            self.faft_framework.wait_for_client_offline(orig_boot_id=boot_id)
234        if wait_for_dut_up:
235            # For encapsulating the behavior of skipping firmware screen,
236            # e.g. requiring unplug and plug USB, the variants are not
237            # hard coded in tests. We keep this logic in this
238            # mode_aware_reboot method.
239            if not is_dev:
240                # In the normal/recovery boot flow, replugging USB does not
241                # affect the boot flow. But when something goes wrong, like
242                # firmware corrupted, it automatically leads to a recovery USB
243                # boot.
244                self.servo.switch_usbkey('host')
245            if not is_normal:
246                self.bypasser.bypass_dev_mode()
247            if not is_dev:
248                self.bypasser.bypass_rec_mode()
249            self.faft_framework.wait_for_kernel_up()
250        logging.info("-[ModeSwitcher]-[ end mode_aware_reboot(%r, %s, ..) ]-",
251                     reboot_type, reboot_method.__name__)
252
253
254    def _enable_rec_mode_and_reboot(self, usb_state=None):
255        """Switch to rec mode and reboot.
256
257        This method emulates the behavior of the old physical recovery switch,
258        i.e. switch ON + reboot + switch OFF, and the new keyboard controlled
259        recovery mode, i.e. just press Power + Esc + Refresh.
260
261        @param usb_state: A string, one of 'dut', 'host', or 'off'.
262        """
263        psc = self.servo.get_power_state_controller()
264        psc.power_off()
265        if usb_state:
266            self.servo.switch_usbkey(usb_state)
267        psc.power_on(psc.REC_ON)
268
269
270    def _disable_rec_mode_and_reboot(self, usb_state=None):
271        """Disable the rec mode and reboot.
272
273        It is achieved by calling power state controller to do a normal
274        power on.
275        """
276        psc = self.servo.get_power_state_controller()
277        psc.power_off()
278        psc.power_on(psc.REC_OFF)
279
280
281    def _enable_dev_mode_and_reboot(self):
282        """Switch to developer mode and reboot."""
283        raise NotImplementedError
284
285
286    def _enable_normal_mode_and_reboot(self):
287        """Switch to normal mode and reboot."""
288        raise NotImplementedError
289
290
291    # Redirects the following methods to FwBypasser
292    def bypass_dev_mode(self):
293        """Bypass the dev mode firmware logic to boot internal image."""
294        self.bypasser.bypass_dev_mode()
295
296
297    def bypass_dev_boot_usb(self):
298        """Bypass the dev mode firmware logic to boot USB."""
299        self.bypasser.bypass_dev_boot_usb()
300
301
302    def bypass_rec_mode(self):
303        """Bypass the rec mode firmware logic to boot USB."""
304        self.bypasser.bypass_rec_mode()
305
306
307    def trigger_dev_to_rec(self):
308        """Trigger to the rec mode from the dev screen."""
309        self.bypasser.trigger_dev_to_rec()
310
311
312    def trigger_rec_to_dev(self):
313        """Trigger to the dev mode from the rec screen."""
314        self.bypasser.trigger_rec_to_dev()
315
316
317    def trigger_dev_to_normal(self):
318        """Trigger to the normal mode from the dev screen."""
319        self.bypasser.trigger_dev_to_normal()
320
321
322class _PhysicalButtonSwitcher(_BaseModeSwitcher):
323    """Class that switches firmware mode via physical button."""
324
325    def _enable_dev_mode_and_reboot(self):
326        """Switch to developer mode and reboot."""
327        self.servo.enable_development_mode()
328        self.faft_client.system.run_shell_command(
329                'chromeos-firmwareupdate --mode todev && reboot')
330
331
332    def _enable_normal_mode_and_reboot(self):
333        """Switch to normal mode and reboot."""
334        self.servo.disable_development_mode()
335        self.faft_client.system.run_shell_command(
336                'chromeos-firmwareupdate --mode tonormal && reboot')
337
338
339class _KeyboardDevSwitcher(_BaseModeSwitcher):
340    """Class that switches firmware mode via keyboard combo."""
341
342    def _enable_dev_mode_and_reboot(self):
343        """Switch to developer mode and reboot."""
344        logging.info("Enabling keyboard controlled developer mode")
345        # Rebooting EC with rec mode on. Should power on AP.
346        # Plug out USB disk for preventing recovery boot without warning
347        self._enable_rec_mode_and_reboot(usb_state='host')
348        self.faft_framework.wait_for_client_offline()
349        self.bypasser.trigger_rec_to_dev()
350
351
352    def _enable_normal_mode_and_reboot(self):
353        """Switch to normal mode and reboot."""
354        logging.info("Disabling keyboard controlled developer mode")
355        self._disable_rec_mode_and_reboot()
356        self.faft_framework.wait_for_client_offline()
357        self.bypasser.trigger_dev_to_normal()
358
359
360def create_mode_switcher(faft_framework):
361    """Creates a proper mode switcher.
362
363    @param faft_framework: The main FAFT framework object.
364    """
365    switcher_type = faft_framework.faft_config.mode_switcher_type
366    if switcher_type == 'physical_button_switcher':
367        logging.info('Create a PhysicalButtonSwitcher')
368        return _PhysicalButtonSwitcher(faft_framework)
369    elif switcher_type == 'keyboard_dev_switcher':
370        logging.info('Create a KeyboardDevSwitcher')
371        return _KeyboardDevSwitcher(faft_framework)
372    else:
373        raise NotImplementedError('Not supported mode_switcher_type: %s',
374                                  switcher_type)
375