xref: /aosp_15_r20/external/autotest/server/site_tests/firmware_ECLidSwitch/firmware_ECLidSwitch.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
1# Copyright (c) 2012 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
5from threading import Timer
6import logging
7import re
8import time
9
10from autotest_lib.client.common_lib import error
11from autotest_lib.server.cros.faft.firmware_test import FirmwareTest
12
13
14def delayed(seconds): # pylint:disable=missing-docstring
15    def decorator(f): # pylint:disable=missing-docstring
16        def wrapper(*args, **kargs): # pylint:disable=missing-docstring
17            t = Timer(seconds, f, args, kargs)
18            t.start()
19        return wrapper
20    return decorator
21
22
23class firmware_ECLidSwitch(FirmwareTest):
24    """
25    Servo based EC lid switch test.
26    """
27    version = 1
28
29    # Delay between closing and opening the lid
30    LID_DELAY = 1
31
32    # Delay to allow FAFT client receive command
33    RPC_DELAY = 2
34
35    # Delay between shutdown and wakeup by lid switch
36    WAKE_DELAY = 10
37
38    # Number of tries when checking power state
39    POWER_STATE_CHECK_TRIES = 50
40
41    # Delay between checking power state
42    POWER_STATE_CHECK_DELAY = 0.5
43
44    def initialize(self, host, cmdline_args):
45        super(firmware_ECLidSwitch, self).initialize(host, cmdline_args)
46        # Only run in normal mode
47        self.switcher.setup_mode('normal')
48
49    def cleanup(self):
50        self.faft_client.system.run_shell_command_get_status(
51                "rm -rf /tmp/power_manager")
52
53        return super().cleanup()
54
55    def _open_lid(self):
56        """Open lid by servo."""
57        self.servo.set('lid_open', 'yes')
58
59    def _close_lid(self):
60        """Close lid by servo."""
61        self.servo.set('lid_open', 'no')
62
63    @delayed(RPC_DELAY)
64    def delayed_open_lid(self):
65        """Delay by RPC_DELAY and then open lid by servo."""
66        self._open_lid()
67
68    @delayed(RPC_DELAY)
69    def delayed_close_lid(self):
70        """Delay by RPC_DELAY and then close lid by servo."""
71        self._close_lid()
72
73    def _wake_by_lid_switch(self):
74        """Wake DUT with lid switch."""
75        self._close_lid()
76        time.sleep(self.LID_DELAY)
77        self._open_lid()
78
79    def delayed_wake(self):
80        """
81        Wait for WAKE_DELAY, and then wake DUT with lid switch.
82        """
83        time.sleep(self.WAKE_DELAY)
84        self._wake_by_lid_switch()
85
86    def immediate_wake(self):
87        """Wake DUT with lid switch."""
88        self._wake_by_lid_switch()
89
90    def shutdown_cmd(self):
91        """Shut down the DUT but don't wait for ping failures."""
92        self.run_shutdown_cmd(wait_for_offline=False)
93
94    def shutdown_and_wake(self, shutdown_func, wake_func):
95        """Software shutdown and wake with check for power state
96
97        Args:
98          shutdown_func: Function to shut down DUT.
99          wake_func: Delayed function to wake DUT.
100        """
101
102        # Call shutdown function to power down device
103        logging.debug('calling shutdown_func')
104        shutdown_func()
105
106        # Check device shutdown to correct power state
107        shutdown_power_states = '|'.join(
108                [self.POWER_STATE_S5, self.POWER_STATE_G3])
109        if not self.wait_power_state(shutdown_power_states,
110                                     self.POWER_STATE_CHECK_TRIES,
111                                     self.POWER_STATE_CHECK_DELAY):
112            raise error.TestFail(
113                    'The device failed to reach %s after calling shutdown function.',
114                    shutdown_power_states)
115
116        # Call wake function to wake up device
117        logging.debug('calling wake_func')
118        wake_func()
119
120        # Check power state to verify device woke up to S0
121        wake_power_state = self.POWER_STATE_S0
122        if not self.wait_power_state(wake_power_state,
123                                     self.POWER_STATE_CHECK_TRIES,
124                                     self.POWER_STATE_CHECK_DELAY):
125            raise error.TestFail(
126                    'The device failed to reach %s after calling wake function.',
127                    wake_power_state)
128        # Wait for the DUT to boot and respond to ssh before we move on.
129        self.switcher.wait_for_client()
130
131    def _get_keyboard_backlight(self):
132        """Get keyboard backlight brightness.
133
134        Returns:
135          Backlight brightness percentage 0~100. If it is disabled, 0 is
136            returned.
137        """
138        cmd = 'ectool pwmgetkblight'
139        pattern_percent = re.compile(
140            'Current keyboard backlight percent: (\d*)')
141        pattern_disable = re.compile('Keyboard backlight disabled.')
142        lines = self.faft_client.system.run_shell_command_get_output(cmd)
143        for line in lines:
144            matched_percent = pattern_percent.match(line)
145            if matched_percent is not None:
146                return int(matched_percent.group(1))
147            matched_disable = pattern_disable.match(line)
148            if matched_disable is not None:
149                return 0
150        raise error.TestError('Cannot get keyboard backlight status.')
151
152    def _set_keyboard_backlight(self, value):
153        """Set keyboard backlight brightness.
154
155        Args:
156          value: Backlight brightness percentage 0~100.
157        """
158        cmd = 'ectool pwmsetkblight %d' % value
159        self.faft_client.system.run_shell_command(cmd)
160
161    def check_keycode(self):
162        """Check that lid open/close do not send power button keycode.
163
164        Returns:
165          True if no power button keycode is captured. Otherwise, False.
166        """
167        # Don't check the keycode if we don't have a keyboard.
168        if not self.check_ec_capability(['keyboard'], suppress_warning=True):
169            return True
170
171        self._open_lid()
172        self.delayed_close_lid()
173        if self.faft_client.system.check_keys([]) < 0:
174            return False
175        self.delayed_open_lid()
176        if self.faft_client.system.check_keys([]) < 0:
177            return False
178        return True
179
180    def check_backlight(self):
181        """Check if lid open/close controls keyboard backlight as expected.
182
183        Returns:
184          True if keyboard backlight is turned off when lid close and on when
185           lid open.
186        """
187        if not self.check_ec_capability(['kblight'], suppress_warning=True):
188            return True
189        ok = True
190        original_value = self._get_keyboard_backlight()
191        self._set_keyboard_backlight(100)
192
193        self._close_lid()
194        if self._get_keyboard_backlight() != 0:
195            logging.error("Keyboard backlight still on when lid close.")
196            ok = False
197        self._open_lid()
198        if self._get_keyboard_backlight() == 0:
199            logging.error("Keyboard backlight still off when lid open.")
200            ok = False
201
202        self._set_keyboard_backlight(original_value)
203        return ok
204
205    def check_keycode_and_backlight(self):
206        """
207        Disable powerd to prevent DUT shutting down during test. Then check
208        if lid switch event controls keycode and backlight as we expected.
209        """
210        ok = True
211        logging.info("Disable use_lid in powerd")
212        self.faft_client.system.run_shell_command(
213                "mkdir -p /tmp/power_manager && "
214                "echo 0 > /tmp/power_manager/use_lid && "
215                "mount --bind /tmp/power_manager /var/lib/power_manager && "
216                "restart powerd")
217        if not self.check_keycode():
218            logging.error("check_keycode failed.")
219            ok = False
220        if not self.check_backlight():
221            logging.error("check_backlight failed.")
222            ok = False
223        logging.info("Restarting powerd")
224        self.faft_client.system.run_shell_command(
225                'umount /var/lib/power_manager && restart powerd')
226        return ok
227
228    def run_once(self):
229        """Runs a single iteration of the test."""
230        if not self.check_ec_capability(['lid']):
231            raise error.TestNAError("Nothing needs to be tested on this device")
232
233        logging.info("Shut down and then wake up DUT after a delay.")
234        self.shutdown_and_wake(shutdown_func=self.shutdown_cmd,
235                               wake_func=self.delayed_wake)
236
237        logging.info("Shut down and then wake up DUT immediately.")
238        self.shutdown_and_wake(shutdown_func=self.shutdown_cmd,
239                               wake_func=self.immediate_wake)
240
241        logging.info("Close and then open the lid when not logged in.")
242        self.shutdown_and_wake(shutdown_func=self._close_lid,
243                               wake_func=self.immediate_wake)
244
245        logging.info("Check keycode and backlight.")
246        self.check_state(self.check_keycode_and_backlight)
247