• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2018 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import logging
6import time
7
8from autotest_lib.client.common_lib import enum, error
9from autotest_lib.server import test
10from autotest_lib.server.cros import servo_keyboard_utils
11from autotest_lib.server.cros.dark_resume_utils import DarkResumeUtils
12from autotest_lib.server.cros.faft.utils.config import Config as FAFTConfig
13from autotest_lib.server.cros.servo import chrome_ec
14
15
16# Possible states base can be forced into.
17BASE_STATE = enum.Enum('ATTACH', 'DETACH', 'RESET')
18
19# Possible states for tablet mode as defined in common/tablet_mode.c via
20# crrev.com/c/1797370.
21TABLET_MODE = enum.Enum('ON', 'OFF', 'RESET')
22
23# List of wake sources expected to cause a full resume.
24FULL_WAKE_SOURCES = [
25    'PWR_BTN', 'LID_OPEN', 'BASE_ATTACH', 'BASE_DETACH', 'INTERNAL_KB',
26    'USB_KB', 'TABLET_MODE_ON', 'TABLET_MODE_OFF'
27]
28
29# Max time taken by the device to resume. This gives enough time for the device
30# to establish network connection with the autotest server
31SECS_FOR_RESUMING = 15
32
33# Time in future after which RTC goes off when testing other wake sources.
34BACKUP_RTC_SECS = 60
35
36# Time in future after which RTC goes off when testing wake due to RTC alarm.
37RTC_WAKE_SECS = 10
38
39# Max time taken by the device to suspend. This includes the time powerd takes
40# trigger the suspend after receiving the suspend request from autotest script.
41SECS_FOR_SUSPENDING = 20
42
43# Time to allow lid transition to take effect.
44WAIT_TIME_LID_TRANSITION_SECS = 5
45
46# Time to wait for the DUT to see USB keyboard after restting the Atmega USB
47# emulator on Servo.
48USB_PRESENT_DELAY = 1
49
50
51class power_WakeSources(test.test):
52    """
53    Verify that wakes from input devices can trigger a full
54    resume. Currently tests :
55        1. power button
56        2. lid open
57        3. base attach
58        4. base detach
59
60    Also tests RTC triggers a dark resume.
61
62    """
63    version = 1
64
65    def _after_resume(self, wake_source):
66        """Cleanup to perform after resuming the device.
67
68        @param wake_source: Wake source that has been tested.
69        """
70        if wake_source in ['BASE_ATTACH', 'BASE_DETACH']:
71            self._force_base_state(BASE_STATE.RESET)
72        if wake_source in ['TABLET_MODE_ON', 'TABLET_MODE_OFF']:
73            self._force_tablet_mode(TABLET_MODE.RESET)
74
75    def _before_suspend(self, wake_source):
76        """Prep before suspend.
77
78        @param wake_source: Wake source that is going to be tested.
79
80        @return: Boolean, whether _before_suspend action is successful.
81        """
82        if wake_source == 'BASE_ATTACH':
83            # Force detach before suspend so that attach won't be ignored.
84            self._force_base_state(BASE_STATE.DETACH)
85            return True
86        if wake_source == 'BASE_DETACH':
87            # Force attach before suspend so that detach won't be ignored.
88            self._force_base_state(BASE_STATE.ATTACH)
89            return True
90        if wake_source == 'LID_OPEN':
91            # Set the power policy for lid closed action to suspend.
92            return self._host.run(
93                'set_power_policy --lid_closed_action suspend',
94                ignore_status=True).exit_status == 0
95        if wake_source == 'USB_KB':
96            # Initialize USB keyboard.
97            self._host.servo.set_nocheck('init_usb_keyboard', 'on')
98            return True
99        if wake_source == 'TABLET_MODE_ON':
100            self._force_tablet_mode(TABLET_MODE.OFF)
101            return True
102        if wake_source == 'TABLET_MODE_OFF':
103            self._force_tablet_mode(TABLET_MODE.ON)
104            return True
105        return True
106
107    def _force_tablet_mode(self, mode):
108        """Send EC command to force the tablet mode.
109
110        @param mode: mode to force. One of the |TABLET_MODE| enum.
111        """
112        ec_cmd = 'tabletmode '
113        ec_arg = {
114            TABLET_MODE.ON: 'on',
115            TABLET_MODE.OFF: 'off',
116            TABLET_MODE.RESET: 'r'
117        }
118
119        ec_cmd += ec_arg[mode]
120        self._ec.send_command(ec_cmd)
121
122    def _force_base_state(self, base_state):
123        """Send EC command to force the |base_state|.
124
125        @param base_state: State to force base to. One of |BASE_STATE| enum.
126        """
127        ec_cmd = 'basestate '
128        ec_arg = {
129            BASE_STATE.ATTACH: 'a',
130            BASE_STATE.DETACH: 'd',
131            BASE_STATE.RESET: 'r'
132        }
133
134        ec_cmd += ec_arg[base_state]
135        self._ec.send_command(ec_cmd)
136
137    def _is_valid_wake_source(self, wake_source):
138        """Check if |wake_source| is valid for DUT.
139
140        @param wake_source: wake source to verify.
141        @return: False if |wake_source| is not valid for DUT, True otherwise
142        """
143        if wake_source.startswith('BASE'):
144            return self._ec.has_command('basestate')
145        if wake_source.startswith('TABLET_MODE'):
146            return self._ec.has_command('tabletmode')
147        if wake_source == 'LID_OPEN':
148            return self._dr_utils.host_has_lid()
149        if wake_source == 'INTERNAL_KB':
150            return self._faft_config.has_keyboard
151        if wake_source == 'USB_KB':
152            # Initialize USB keyboard.
153            self._host.servo.set_nocheck('init_usb_keyboard', 'on')
154            time.sleep(USB_PRESENT_DELAY)
155            # Check if DUT can see a wake capable Atmel USB keyboard.
156            if servo_keyboard_utils.is_servo_usb_keyboard_present(
157                    self._host):
158                if servo_keyboard_utils.is_servo_usb_wake_capable(
159                        self._host):
160                    return True
161                else:
162                    logging.warning(
163                        'Atmel USB keyboard does not have wake capability.'
164                        ' Please run firmware_FlashServoKeyboardMap Autotest '
165                        'to update the Atmel firmware.')
166                    return False
167            else:
168                logging.warning(
169                    'DUT cannot see a Atmel USB keyboard. '
170                    ' Please plug in USB C charger into Servo if using V4.')
171
172                return False
173
174        return True
175
176    def _test_full_wake(self, wake_source):
177        """Test if |wake_source| triggers a full resume.
178
179        @param wake_source: wake source to test. One of |FULL_WAKE_SOURCES|.
180        @return: True, if we are able to successfully test the |wake source|
181            triggers a full wake.
182        """
183        is_success = True
184        logging.info(
185            'Testing wake by %s triggers a '
186            'full wake when dark resume is enabled.', wake_source)
187        if not self._before_suspend(wake_source):
188            logging.error('Before suspend action failed for %s', wake_source)
189            is_success = False
190        else:
191            count_before = self._dr_utils.count_dark_resumes()
192            self._dr_utils.suspend(BACKUP_RTC_SECS)
193            logging.info('DUT suspended! Waiting to resume...')
194            # Wait at least |SECS_FOR_SUSPENDING| secs for the kernel to
195            # fully suspend.
196            time.sleep(SECS_FOR_SUSPENDING)
197            self._trigger_wake(wake_source)
198            # Wait at least |SECS_FOR_RESUMING| secs for the device to
199            # resume.
200            time.sleep(SECS_FOR_RESUMING)
201
202            if not self._host.is_up_fast():
203                logging.error('Device did not resume from suspend for %s.'
204                              ' Waiting for backup RTC to wake the system.',
205                              wake_source)
206                time.sleep(BACKUP_RTC_SECS -
207                           SECS_FOR_SUSPENDING - SECS_FOR_RESUMING)
208                is_success = False
209            if not self._host.is_up():
210                raise error.TestFail(
211                    'Device failed to wakeup from backup RTC.')
212
213            count_after = self._dr_utils.count_dark_resumes()
214            if is_success and count_before != count_after:
215                logging.error('%s caused a dark resume.', wake_source)
216                is_success = False
217            elif is_success:
218                logging.info('%s caused a full resume.', wake_source)
219        self._after_resume(wake_source)
220        return is_success
221
222    def _test_rtc(self):
223        """Suspend the device and test if RTC triggers a dark_resume.
224
225        @return boolean, true if RTC alarm caused a dark resume.
226        """
227
228        logging.info('Testing RTC triggers dark resume when enabled.')
229
230        count_before = self._dr_utils.count_dark_resumes()
231        self._dr_utils.suspend(SECS_FOR_SUSPENDING + RTC_WAKE_SECS)
232        logging.info('DUT suspended! Waiting to resume...')
233        time.sleep(SECS_FOR_SUSPENDING + RTC_WAKE_SECS +
234                   SECS_FOR_RESUMING)
235
236        if not self._host.is_up():
237            logging.error('Device did not resume from suspend for RTC')
238            return False
239
240        count_after = self._dr_utils.count_dark_resumes()
241        if count_before != count_after - 1:
242            logging.error(
243                'RTC did not cause a dark resume.'
244                'count before = %d, count after = %d', count_before,
245                count_after)
246            return False
247        return True
248
249    def _trigger_wake(self, wake_source):
250        """Trigger wake using the given |wake_source|.
251
252        @param wake_source : wake_source that is being tested.
253            One of |FULL_WAKE_SOURCES|.
254        """
255        if wake_source == 'PWR_BTN':
256            self._host.servo.power_short_press()
257        elif wake_source == 'LID_OPEN':
258            self._host.servo.lid_close()
259            time.sleep(WAIT_TIME_LID_TRANSITION_SECS)
260            self._host.servo.lid_open()
261        elif wake_source == 'BASE_ATTACH':
262            self._force_base_state(BASE_STATE.ATTACH)
263        elif wake_source == 'BASE_DETACH':
264            self._force_base_state(BASE_STATE.DETACH)
265        elif wake_source == 'TABLET_MODE_ON':
266            self._force_tablet_mode(TABLET_MODE.ON)
267        elif wake_source == 'TABLET_MODE_OFF':
268            self._force_tablet_mode(TABLET_MODE.OFF)
269        elif wake_source == 'INTERNAL_KB':
270            self._host.servo.ctrl_key()
271        elif wake_source == 'USB_KB':
272            self._host.servo.set_nocheck('usb_keyboard_enter_key', '10')
273
274    def cleanup(self):
275        """cleanup."""
276        self._dr_utils.stop_resuspend_on_dark_resume(False)
277        self._dr_utils.teardown()
278
279    def initialize(self, host):
280        """Initialize wake sources tests.
281
282        @param host: Host on which the test will be run.
283        """
284        self._host = host
285        self._dr_utils = DarkResumeUtils(host)
286        self._dr_utils.stop_resuspend_on_dark_resume()
287        self._ec = chrome_ec.ChromeEC(self._host.servo)
288        self._faft_config = FAFTConfig(self._host.get_platform())
289
290    def run_once(self):
291        """Body of the test."""
292
293        test_ws = set(
294            ws for ws in FULL_WAKE_SOURCES if self._is_valid_wake_source(ws))
295        passed_ws = set(ws for ws in test_ws if self._test_full_wake(ws))
296        failed_ws = test_ws.difference(passed_ws)
297        skipped_ws = set(FULL_WAKE_SOURCES).difference(test_ws)
298
299        if self._test_rtc():
300            passed_ws.add('RTC')
301        else:
302            failed_ws.add('RTC')
303        test_keyval = {}
304
305        for ws in passed_ws:
306            test_keyval.update({ws: 'PASS'})
307        for ws in failed_ws:
308            test_keyval.update({ws: 'FAIL'})
309        for ws in skipped_ws:
310            test_keyval.update({ws: 'SKIPPED'})
311        self.write_test_keyval(test_keyval)
312
313        if len(passed_ws):
314            logging.info('[%s] woke the device as expected.',
315                         ''.join(str(elem) + ', ' for elem in passed_ws))
316        if skipped_ws:
317            logging.info(
318                '[%s] are not wake sources on this platform. '
319                'Please test manually if not the case.',
320                ''.join(str(elem) + ', ' for elem in skipped_ws))
321
322        if len(failed_ws):
323            raise error.TestFail(
324                '[%s] wake sources did not behave as expected.' %
325                (''.join(str(elem) + ', ' for elem in failed_ws)))
326