• 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.dark_resume_utils import DarkResumeUtils
11from autotest_lib.server.cros.faft.config.config import Config as FAFTConfig
12from autotest_lib.server.cros.servo import chrome_ec
13
14
15# Possible states base can be forced into.
16BASE_STATE = enum.Enum('ATTACH', 'DETACH', 'RESET')
17
18
19 # List of wake sources expected to cause a full resume.
20FULL_WAKE_SOURCES = ['PWR_BTN', 'LID_OPEN', 'BASE_ATTACH',
21                     'BASE_DETACH', 'INTERNAL_KB']
22
23# Max time taken by the system to resume.
24RESUME_DURATION_SECS = 5
25
26# Time in future after which RTC goes off.
27RTC_WAKE_SECS = 30
28
29# Max time taken by the system to suspend.
30SUSPEND_DURATION_SECS = 5
31
32# Time to allow lid transition to take effect.
33WAIT_TIME_LID_TRANSITION_SECS = 5
34
35
36class power_WakeSources(test.test):
37    """
38    Verify that wakes from input devices can trigger a full
39    resume. Currently tests :
40        1. power button
41        2. lid open
42        3. base attach
43        4. base detach
44
45    Also tests RTC triggers a dark resume.
46
47    """
48    version = 1
49
50    def _after_resume(self, wake_source):
51        """Cleanup to perform after resuming the device.
52
53        @param wake_source: Wake source that has been tested.
54        """
55        if wake_source in ['BASE_ATTACH', 'BASE_DETACH']:
56            self._force_base_state(BASE_STATE.RESET)
57
58    def _before_suspend(self, wake_source):
59        """Prep before suspend.
60
61        @param wake_source: Wake source that is going to be tested.
62
63        @return: Boolean, whether _before_suspend action is successful.
64        """
65        if wake_source == 'BASE_ATTACH':
66            # Force detach before suspend so that attach won't be ignored.
67            return self._force_base_state(BASE_STATE.DETACH)
68        if wake_source == 'BASE_DETACH':
69            # Force attach before suspend so that detach won't be ignored.
70            return self._force_base_state(BASE_STATE.ATTACH)
71        if wake_source == 'LID_OPEN':
72            # Set the power policy for lid closed action to suspend.
73            return self._host.run(
74                'set_power_policy --lid_closed_action suspend',
75                ignore_status=True).exit_status == 0
76        return True
77
78    def _force_base_state(self, base_state):
79        """Send EC command to force the |base_state|.
80
81        @param base_state: State to force base to. One of |BASE_STATE| enum.
82
83        @return: False if the command does not exist in the current EC build.
84
85        @raise error.TestFail : If base state change fails.
86        """
87        ec_cmd = 'basestate '
88        ec_arg = {
89            BASE_STATE.ATTACH: 'a',
90            BASE_STATE.DETACH: 'd',
91            BASE_STATE.RESET: 'r'
92        }
93
94        ec_cmd += ec_arg[base_state]
95
96        try:
97            self._ec.send_command(ec_cmd)
98        except error.TestFail as e:
99            if 'No control named' in str(e):
100                # Since the command is added recently, this might not exist on
101                # every board.
102                logging.warning('basestate command does not exist on the EC. '
103                                'Please verify the base state manually.')
104                return False
105            else:
106                raise e
107        return True
108
109    def _is_valid_wake_source(self, wake_source):
110        """Check if |wake_source| is valid for DUT.
111
112        @param wake_source: wake source to verify.
113        @return: False if |wake_source| is not valid for DUT, True otherwise
114        """
115        if wake_source.startswith('BASE'):
116            if self._host.run('which hammerd', ignore_status=True).\
117                exit_status == 0:
118                # Smoke test to see if EC has support to reset base.
119                return self._force_base_state(BASE_STATE.RESET)
120            else:
121                return False
122        if wake_source == 'LID_OPEN':
123            return self._dr_utils.host_has_lid()
124        if wake_source == 'INTERNAL_KB':
125            return self._faft_config.has_keyboard
126        return True
127
128    def _test_full_wake(self, wake_source):
129        """Test if |wake_source| triggers a full resume.
130
131        @param wake_source: wake source to test. One of |FULL_WAKE_SOURCES|.
132        @return: True, if we are able to successfully test the |wake source|
133            triggers a full wake.
134        """
135        is_success = True
136        logging.info('Testing wake by %s triggers a '
137                     'full wake when dark resume is enabled.', wake_source)
138        if not self._before_suspend(wake_source):
139            logging.error('Before suspend action failed for %s', wake_source)
140            is_success = False
141        else:
142            count_before = self._dr_utils.count_dark_resumes()
143            with self._dr_utils.suspend() as _:
144                logging.info('DUT suspended! Waiting to resume...')
145                # Wait at least |SUSPEND_DURATION_SECS| secs for the kernel to
146                # fully suspend.
147                time.sleep(SUSPEND_DURATION_SECS)
148                self._trigger_wake(wake_source)
149                # Wait at least |RESUME_DURATION_SECS| secs for the device to
150                # resume.
151                time.sleep(RESUME_DURATION_SECS)
152
153                if not self._host.is_up():
154                    logging.error('Device did not resume from suspend for %s',
155                                  wake_source)
156                    is_success = False
157
158            count_after = self._dr_utils.count_dark_resumes()
159            if count_before != count_after:
160                logging.error('%s caused a dark resume.', wake_source)
161                is_success = False
162        self._after_resume(wake_source)
163        return is_success
164
165    def _test_rtc(self):
166        """Suspend the device and test if RTC triggers a dark_resume.
167
168        @return boolean, true if RTC alarm caused a dark resume.
169        """
170
171        logging.info('Testing RTC triggers dark resume when enabled.')
172
173        count_before = self._dr_utils.count_dark_resumes()
174        with self._dr_utils.suspend(RTC_WAKE_SECS) as _:
175            logging.info('DUT suspended! Waiting to resume...')
176            time.sleep(SUSPEND_DURATION_SECS + RTC_WAKE_SECS +
177                       RESUME_DURATION_SECS)
178
179            if not self._host.is_up():
180                logging.error('Device did not resume from suspend for RTC')
181                return False
182
183        count_after = self._dr_utils.count_dark_resumes()
184        if count_before != count_after - 1:
185            logging.error(' RTC did not cause a dark resume.'
186                          'count before = %d, count after = %d',
187                          count_before, count_after)
188            return False
189        return True
190
191    def _trigger_wake(self, wake_source):
192        """Trigger wake using the given |wake_source|.
193
194        @param wake_source : wake_source that is being tested.
195            One of |FULL_WAKE_SOURCES|.
196        """
197        if wake_source == 'PWR_BTN':
198            self._host.servo.power_short_press()
199        elif wake_source == 'LID_OPEN':
200            self._host.servo.lid_close()
201            time.sleep(WAIT_TIME_LID_TRANSITION_SECS)
202            self._host.servo.lid_open()
203        elif wake_source == 'BASE_ATTACH':
204            self._force_base_state(BASE_STATE.ATTACH)
205        elif wake_source == 'BASE_DETACH':
206            self._force_base_state(BASE_STATE.DETACH)
207        elif wake_source == 'INTERNAL_KB':
208            self._host.servo.ctrl_key()
209
210    def cleanup(self):
211        """cleanup."""
212        self._dr_utils.stop_resuspend_on_dark_resume(False)
213        self._dr_utils.teardown()
214
215    def initialize(self, host):
216        """Initialize wake sources tests.
217
218        @param host: Host on which the test will be run.
219        """
220        self._host = host
221        self._dr_utils = DarkResumeUtils(host)
222        self._dr_utils.stop_resuspend_on_dark_resume()
223        self._ec = chrome_ec.ChromeEC(self._host.servo)
224        self._faft_config = FAFTConfig(self._host.get_platform())
225
226    def run_once(self):
227        """Body of the test."""
228
229        test_ws = set(ws for ws in FULL_WAKE_SOURCES if \
230            self._is_valid_wake_source(ws))
231        passed_ws = set(ws for ws in test_ws if self._test_full_wake(ws))
232        failed_ws = test_ws.difference(passed_ws)
233        skipped_ws = set(FULL_WAKE_SOURCES).difference(test_ws)
234
235        if self._test_rtc():
236            passed_ws.add('RTC')
237        else:
238            failed_ws.add('RTC')
239        if len(passed_ws):
240            logging.info('[%s] woke the device as expected.',
241                         ''.join(str(elem) + ', ' for elem in passed_ws))
242        if skipped_ws:
243            logging.info('[%s] are not wake sources on this platform. '
244                         'Please test manually if not the case.',
245                         ''.join(str(elem) + ', ' for elem in skipped_ws))
246
247        if len(failed_ws):
248            raise error.TestFail(
249                '[%s] wake sources did not behave as expected.'
250                % (''.join(str(elem) + ', ' for elem in failed_ws)))
251