• 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 autotest_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.power import servo_charger
14from autotest_lib.server.cros.servo import chrome_ec
15
16
17# Possible states base can be forced into.
18BASE_STATE = autotest_enum.AutotestEnum('ATTACH', 'DETACH', 'RESET')
19
20# Possible states for tablet mode as defined in common/tablet_mode.c via
21# crrev.com/c/1797370.
22TABLET_MODE = autotest_enum.AutotestEnum('ON', 'OFF', 'RESET')
23
24# List of wake sources expected to cause a full resume.
25FULL_WAKE_SOURCES = [
26    'PWR_BTN', 'LID_OPEN', 'BASE_ATTACH', 'BASE_DETACH', 'INTERNAL_KB',
27    'USB_KB', 'TABLET_MODE_ON', 'TABLET_MODE_OFF'
28]
29
30# List of wake sources expected to cause a dark resume.
31DARK_RESUME_SOURCES = ['RTC', 'AC_CONNECTED', 'AC_DISCONNECTED']
32
33# Max time taken by the device to resume. This gives enough time for the device
34# to establish network connection with the autotest server
35SECS_FOR_RESUMING = 15
36
37# Time in future after which RTC goes off when testing wake due to RTC alarm.
38RTC_WAKE_SECS = 20
39
40# Max time taken by the device to suspend. This includes the time powerd takes
41# trigger the suspend after receiving the suspend request from autotest script.
42SECS_FOR_SUSPENDING = 20
43
44# Time to allow lid transition to take effect.
45WAIT_TIME_LID_TRANSITION_SECS = 5
46
47# Time to wait for the DUT to see USB keyboard after restting the Atmega USB
48# emulator on Servo.
49USB_PRESENT_DELAY = 1
50
51
52class power_WakeSources(test.test):
53    """
54    Verify that wakes from input devices can trigger a full
55    resume. Currently tests :
56        1. power button
57        2. lid open
58        3. base attach
59        4. base detach
60
61    Also tests that dark resume wake sources work as expected, such as:
62        1. RTC
63        2. AC_CONNECTED
64        3. AC_DISCONNECTED
65
66    """
67    version = 1
68
69    def _after_resume(self, wake_source):
70        """Cleanup to perform after resuming the device.
71
72        @param wake_source: Wake source that has been tested.
73        """
74        if wake_source in ['BASE_ATTACH', 'BASE_DETACH']:
75            self._force_base_state(BASE_STATE.RESET)
76        elif wake_source in ['TABLET_MODE_ON', 'TABLET_MODE_OFF']:
77            self._force_tablet_mode(TABLET_MODE.RESET)
78        elif wake_source in ['AC_CONNECTED', 'AC_DISCONNECTED']:
79            self._chg_manager.start_charging()
80
81    def _before_suspend(self, wake_source):
82        """Prep before suspend.
83
84        @param wake_source: Wake source that is going to be tested.
85
86        @return: Boolean, whether _before_suspend action is successful.
87        """
88        if wake_source == 'BASE_ATTACH':
89            # Force detach before suspend so that attach won't be ignored.
90            self._force_base_state(BASE_STATE.DETACH)
91        elif wake_source == 'BASE_DETACH':
92            # Force attach before suspend so that detach won't be ignored.
93            self._force_base_state(BASE_STATE.ATTACH)
94        elif wake_source == 'LID_OPEN':
95            # Set the power policy for lid closed action to suspend.
96            return self._host.run(
97                'set_power_policy --lid_closed_action suspend',
98                ignore_status=True).exit_status == 0
99        elif wake_source == 'USB_KB':
100            # Initialize USB keyboard.
101            self._host.servo.set_nocheck('init_usb_keyboard', 'on')
102        elif wake_source == 'TABLET_MODE_ON':
103            self._force_tablet_mode(TABLET_MODE.OFF)
104        elif wake_source == 'TABLET_MODE_OFF':
105            self._force_tablet_mode(TABLET_MODE.ON)
106        elif wake_source == 'AC_CONNECTED':
107            self._chg_manager.stop_charging()
108        elif wake_source == 'AC_DISCONNECTED':
109            self._chg_manager.start_charging()
110        return True
111
112    def _force_tablet_mode(self, mode):
113        """Send EC command to force the tablet mode.
114
115        @param mode: mode to force. One of the |TABLET_MODE| enum.
116        """
117        ec_cmd = 'tabletmode '
118        ec_arg = {
119            TABLET_MODE.ON: 'on',
120            TABLET_MODE.OFF: 'off',
121            TABLET_MODE.RESET: 'r'
122        }
123
124        ec_cmd += ec_arg[mode]
125        self._ec.send_command(ec_cmd)
126
127    def _force_base_state(self, base_state):
128        """Send EC command to force the |base_state|.
129
130        @param base_state: State to force base to. One of |BASE_STATE| enum.
131        """
132        ec_cmd = 'basestate '
133        ec_arg = {
134            BASE_STATE.ATTACH: 'a',
135            BASE_STATE.DETACH: 'd',
136            BASE_STATE.RESET: 'r'
137        }
138
139        ec_cmd += ec_arg[base_state]
140        self._ec.send_command(ec_cmd)
141
142    def _is_valid_wake_source(self, wake_source):
143        """Check if |wake_source| is valid for DUT.
144
145        @param wake_source: wake source to verify.
146        @return: False if |wake_source| is not valid for DUT, True otherwise
147        """
148        if wake_source in ['BASE_ATTACH', 'BASE_DETACH']:
149            return self._ec.has_command('basestate')
150        if wake_source in ['TABLET_MODE_ON', 'TABLET_MODE_OFF']:
151            return self._ec.has_command('tabletmode')
152        if wake_source == 'LID_OPEN':
153            return self._dr_utils.host_has_lid()
154        if wake_source == 'INTERNAL_KB':
155            return self._faft_config.has_keyboard
156        if wake_source == 'USB_KB':
157            # Initialize USB keyboard.
158            self._host.servo.set_nocheck('init_usb_keyboard', 'on')
159            time.sleep(USB_PRESENT_DELAY)
160            # Check if DUT can see a wake capable Atmel USB keyboard.
161            if servo_keyboard_utils.is_servo_usb_keyboard_present(
162                    self._host):
163                if servo_keyboard_utils.is_servo_usb_wake_capable(
164                        self._host):
165                    return True
166                else:
167                    logging.warning(
168                        'Atmel USB keyboard does not have wake capability.'
169                        ' Please run firmware_FlashServoKeyboardMap Autotest '
170                        'to update the Atmel firmware.')
171                    return False
172            else:
173                logging.warning(
174                    'DUT cannot see a Atmel USB keyboard. '
175                    ' Please plug in USB C charger into Servo if using V4.')
176
177                return False
178        if wake_source in ['AC_CONNECTED', 'AC_DISCONNECTED']:
179            if not self._chg_manager:
180                logging.warning(
181                    'Unable to test AC connect/disconnect with this '
182                    'servo setup')
183                return False
184            # Check both the S0ix and S3 wake masks.
185            try:
186                s0ix_wake_mask = int(self._host.run(
187                        'ectool hostevent get %d' %
188                        chrome_ec.EC_HOST_EVENT_LAZY_WAKE_MASK_S0IX).stdout,
189                                     base=16)
190            except error.AutoservRunError as e:
191                s0ix_wake_mask = 0
192                logging.info(
193                        '"ectool hostevent get" failed for s0ix wake mask with'
194                        ' exception: %s', str(e))
195
196            try:
197                s3_wake_mask = int(self._host.run(
198                        'ectool hostevent get %d' %
199                        chrome_ec.EC_HOST_EVENT_LAZY_WAKE_MASK_S3).stdout,
200                                   base=16)
201            except error.AutoservRunError as e:
202                s3_wake_mask = 0
203                logging.info(
204                        '"ectool hostevent get" failed for s3 wake mask with'
205                        ' exception: %s', str(e))
206
207            wake_mask = s0ix_wake_mask | s3_wake_mask
208            supported = False
209            if wake_source == 'AC_CONNECTED':
210                supported = wake_mask & chrome_ec.HOSTEVENT_AC_CONNECTED
211            elif wake_source == 'AC_DISCONNECTED':
212                supported = wake_mask & chrome_ec.HOSTEVENT_AC_DISCONNECTED
213
214            if not supported:
215                logging.info(
216                        '%s not supported. Platforms launched in 2020 or before'
217                        ' may not require it. S0ix wake mask: 0x%x S3 wake'
218                        ' mask: 0x%x', wake_source, s0ix_wake_mask,
219                        s3_wake_mask)
220                return False
221
222        return True
223
224    def _test_wake(self, wake_source, full_wake):
225        """Test if |wake_source| triggers a full resume.
226
227        @param wake_source: wake source to test. One of |FULL_WAKE_SOURCES|.
228        @return: True, if we are able to successfully test the |wake source|
229            triggers a full wake.
230        """
231        is_success = True
232        logging.info(
233            'Testing wake by %s triggers a '
234            'full wake when dark resume is enabled.', wake_source)
235        if not self._before_suspend(wake_source):
236            logging.error('Before suspend action failed for %s', wake_source)
237            # Still run the _after_resume callback since we can do things like
238            # stop charging.
239            self._after_resume(wake_source)
240            return False
241
242        count_before = self._dr_utils.count_dark_resumes()
243        self._dr_utils.suspend(SECS_FOR_SUSPENDING + RTC_WAKE_SECS)
244        logging.info('DUT suspended! Waiting to resume...')
245        # Wait at least |SECS_FOR_SUSPENDING| secs for the kernel to
246        # fully suspend.
247        time.sleep(SECS_FOR_SUSPENDING)
248        self._trigger_wake(wake_source)
249        # Wait at least |SECS_FOR_RESUMING| secs for the device to
250        # resume.
251        time.sleep(SECS_FOR_RESUMING)
252
253        if not self._host.is_up_fast():
254            logging.error(
255                    'Device did not resume from suspend for %s.'
256                    ' Waking system with power button then RTC.', wake_source)
257            self._trigger_wake('PWR_BTN')
258            self._after_resume(wake_source)
259            if not self._host.is_up():
260                raise error.TestFail(
261                        'Device failed to wakeup from backup wake sources'
262                        ' (power button and RTC).')
263
264            return False
265
266        count_after = self._dr_utils.count_dark_resumes()
267        if full_wake:
268            if count_before != count_after:
269                logging.error('%s incorrectly caused a dark resume.',
270                              wake_source)
271                is_success = False
272            elif is_success:
273                logging.info('%s caused a full resume.', wake_source)
274        else:
275            if count_before == count_after:
276                logging.error('%s incorrectly caused a full resume.',
277                              wake_source)
278                is_success = False
279            elif is_success:
280                logging.info('%s caused a dark resume.', wake_source)
281
282        self._after_resume(wake_source)
283        return is_success
284
285    def _trigger_wake(self, wake_source):
286        """Trigger wake using the given |wake_source|.
287
288        @param wake_source : wake_source that is being tested.
289            One of |FULL_WAKE_SOURCES|.
290        """
291        if wake_source == 'PWR_BTN':
292            self._host.servo.power_short_press()
293        elif wake_source == 'LID_OPEN':
294            self._host.servo.lid_close()
295            time.sleep(WAIT_TIME_LID_TRANSITION_SECS)
296            self._host.servo.lid_open()
297        elif wake_source == 'BASE_ATTACH':
298            self._force_base_state(BASE_STATE.ATTACH)
299        elif wake_source == 'BASE_DETACH':
300            self._force_base_state(BASE_STATE.DETACH)
301        elif wake_source == 'TABLET_MODE_ON':
302            self._force_tablet_mode(TABLET_MODE.ON)
303        elif wake_source == 'TABLET_MODE_OFF':
304            self._force_tablet_mode(TABLET_MODE.OFF)
305        elif wake_source == 'INTERNAL_KB':
306            self._host.servo.ctrl_key()
307        elif wake_source == 'USB_KB':
308            self._host.servo.set_nocheck('usb_keyboard_enter_key', '10')
309        elif wake_source == 'RTC':
310            # The RTC will wake on its own. We just need to wait
311            time.sleep(RTC_WAKE_SECS)
312        elif wake_source == 'AC_CONNECTED':
313            self._chg_manager.start_charging()
314        elif wake_source == 'AC_DISCONNECTED':
315            self._chg_manager.stop_charging()
316
317    def cleanup(self):
318        """cleanup."""
319        self._dr_utils.stop_resuspend_on_dark_resume(False)
320        self._dr_utils.teardown()
321
322    def initialize(self, host):
323        """Initialize wake sources tests.
324
325        @param host: Host on which the test will be run.
326        """
327        self._host = host
328        self._dr_utils = DarkResumeUtils(host)
329        self._dr_utils.stop_resuspend_on_dark_resume()
330        self._ec = chrome_ec.ChromeEC(self._host.servo)
331        self._faft_config = FAFTConfig(self._host.get_platform())
332        self._kstr = host.get_kernel_version()
333        # TODO(b/168939843) : Look at implementing AC plug/unplug w/ non-PD RPMs
334        # in the lab.
335        try:
336            self._chg_manager = servo_charger.ServoV4ChargeManager(
337                host, host.servo)
338        except error.TestNAError:
339            logging.warning('Servo does not support AC switching.')
340            self._chg_manager = None
341
342    def run_once(self):
343        """Body of the test."""
344
345        test_ws = set(
346            ws for ws in FULL_WAKE_SOURCES if self._is_valid_wake_source(ws))
347        passed_ws = set(ws for ws in test_ws if self._test_wake(ws, True))
348        failed_ws = test_ws.difference(passed_ws)
349        skipped_ws = set(FULL_WAKE_SOURCES).difference(test_ws)
350
351        test_dark_ws = set(ws for ws in DARK_RESUME_SOURCES
352                           if self._is_valid_wake_source(ws))
353        skipped_ws.update(set(DARK_RESUME_SOURCES).difference(test_dark_ws))
354        for ws in test_dark_ws:
355            if self._test_wake(ws, False):
356                passed_ws.add(ws)
357            else:
358                failed_ws.add(ws)
359
360        test_keyval = {}
361
362        for ws in passed_ws:
363            test_keyval.update({ws: 'PASS'})
364        for ws in failed_ws:
365            test_keyval.update({ws: 'FAIL'})
366        for ws in skipped_ws:
367            test_keyval.update({ws: 'SKIPPED'})
368        self.write_test_keyval(test_keyval)
369
370        if passed_ws:
371            logging.info('[%s] woke the device as expected.',
372                         ''.join(str(elem) + ', ' for elem in passed_ws))
373
374        if skipped_ws:
375            logging.info(
376                '[%s] are not wake sources on this platform. '
377                'Please test manually if not the case.',
378                ''.join(str(elem) + ', ' for elem in skipped_ws))
379
380        if failed_ws:
381            raise error.TestFail(
382                '[%s] wake sources did not behave as expected.' %
383                (''.join(str(elem) + ', ' for elem in failed_ws)))
384