xref: /aosp_15_r20/external/autotest/client/cros/power/power_utils.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
1*9c5db199SXin Li# Lint as: python2, python3
2*9c5db199SXin Li# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
3*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be
4*9c5db199SXin Li# found in the LICENSE file.
5*9c5db199SXin Liimport glob
6*9c5db199SXin Liimport logging
7*9c5db199SXin Liimport os
8*9c5db199SXin Liimport re
9*9c5db199SXin Liimport shutil
10*9c5db199SXin Liimport sys
11*9c5db199SXin Liimport time
12*9c5db199SXin Lifrom autotest_lib.client.bin import utils
13*9c5db199SXin Lifrom autotest_lib.client.bin.input.input_device import InputDevice
14*9c5db199SXin Lifrom autotest_lib.client.common_lib import error
15*9c5db199SXin Lifrom autotest_lib.client.cros import upstart
16*9c5db199SXin Lifrom six.moves import range
17*9c5db199SXin Li
18*9c5db199SXin Li
19*9c5db199SXin Li# Possible display power settings. Copied from chromeos::DisplayPowerState
20*9c5db199SXin Li# in Chrome's dbus service constants.
21*9c5db199SXin LiDISPLAY_POWER_ALL_ON = 0
22*9c5db199SXin LiDISPLAY_POWER_ALL_OFF = 1
23*9c5db199SXin LiDISPLAY_POWER_INTERNAL_OFF_EXTERNAL_ON = 2
24*9c5db199SXin LiDISPLAY_POWER_INTERNAL_ON_EXTERNAL_OFF = 3
25*9c5db199SXin Li# for bounds checking
26*9c5db199SXin LiDISPLAY_POWER_MAX = 4
27*9c5db199SXin Li
28*9c5db199SXin Li# Retry times for ectool chargecontrol
29*9c5db199SXin LiECTOOL_CHARGECONTROL_RETRY_TIMES = 3
30*9c5db199SXin Li
31*9c5db199SXin Li
32*9c5db199SXin Lidef get_x86_cpu_arch():
33*9c5db199SXin Li    """Identify CPU architectural type.
34*9c5db199SXin Li
35*9c5db199SXin Li    Intel's processor naming conventions is a mine field of inconsistencies.
36*9c5db199SXin Li    Armed with that, this method simply tries to identify the architecture of
37*9c5db199SXin Li    systems we care about.
38*9c5db199SXin Li
39*9c5db199SXin Li    TODO(tbroch) grow method to cover processors numbers outlined in:
40*9c5db199SXin Li        http://www.intel.com/content/www/us/en/processors/processor-numbers.html
41*9c5db199SXin Li        perhaps returning more information ( brand, generation, features )
42*9c5db199SXin Li
43*9c5db199SXin Li    Returns:
44*9c5db199SXin Li      String, explicitly (Atom, Core, Celeron) or None
45*9c5db199SXin Li    """
46*9c5db199SXin Li    cpuinfo = utils.read_file('/proc/cpuinfo')
47*9c5db199SXin Li
48*9c5db199SXin Li    if re.search(r'AMD.*[AE][269]-9[0-9][0-9][0-9].*RADEON.*R[245]', cpuinfo):
49*9c5db199SXin Li        return 'Stoney'
50*9c5db199SXin Li    if re.search(r'AMD.*Ryzen.*Radeon.*', cpuinfo):
51*9c5db199SXin Li        return 'Ryzen'
52*9c5db199SXin Li    if re.search(r'Intel.*Atom.*[NZ][2-6]', cpuinfo):
53*9c5db199SXin Li        return 'Atom'
54*9c5db199SXin Li    if re.search(r'Intel.*Celeron.*N2[89][0-9][0-9]', cpuinfo):
55*9c5db199SXin Li        return 'Celeron N2000'
56*9c5db199SXin Li    if re.search(r'Intel.*Celeron.*N3[0-9][0-9][0-9]', cpuinfo):
57*9c5db199SXin Li        return 'Celeron N3000'
58*9c5db199SXin Li    if re.search(r'Intel.*Celeron.*[0-9]{3,4}', cpuinfo):
59*9c5db199SXin Li        return 'Celeron'
60*9c5db199SXin Li    # https://ark.intel.com/products/series/94028/5th-Generation-Intel-Core-M-Processors
61*9c5db199SXin Li    # https://ark.intel.com/products/series/94025/6th-Generation-Intel-Core-m-Processors
62*9c5db199SXin Li    # https://ark.intel.com/products/series/95542/7th-Generation-Intel-Core-m-Processors
63*9c5db199SXin Li    if re.search(r'Intel.*Core.*[mM][357]-[567][Y0-9][0-9][0-9]', cpuinfo):
64*9c5db199SXin Li        return 'Core M'
65*9c5db199SXin Li    if re.search(r'Intel.*Core.*i[357]-[234][0-9][0-9][0-9]', cpuinfo):
66*9c5db199SXin Li        return 'Core'
67*9c5db199SXin Li
68*9c5db199SXin Li    logging.info(cpuinfo)
69*9c5db199SXin Li    return None
70*9c5db199SXin Li
71*9c5db199SXin Li
72*9c5db199SXin Lidef has_rapl_support():
73*9c5db199SXin Li    """Identify if CPU microarchitecture supports RAPL energy profile.
74*9c5db199SXin Li
75*9c5db199SXin Li    TODO(harry.pan): Since Sandy Bridge, all microarchitectures have RAPL
76*9c5db199SXin Li    in various power domains. With that said, the Silvermont and Airmont
77*9c5db199SXin Li    support RAPL as well, while the ESU (Energy Status Unit of MSR 606H)
78*9c5db199SXin Li    are in different multipiler against others, hense not list by far.
79*9c5db199SXin Li
80*9c5db199SXin Li    Returns:
81*9c5db199SXin Li        Boolean, True if RAPL supported, False otherwise.
82*9c5db199SXin Li    """
83*9c5db199SXin Li    rapl_set = set(["Haswell", "Haswell-E", "Broadwell", "Skylake", "Goldmont",
84*9c5db199SXin Li                    "Kaby Lake", "Comet Lake", "Ice Lake", "Tiger Lake",
85*9c5db199SXin Li                    "Tremont"])
86*9c5db199SXin Li    cpu_uarch = utils.get_intel_cpu_uarch()
87*9c5db199SXin Li    if (cpu_uarch in rapl_set):
88*9c5db199SXin Li        return True
89*9c5db199SXin Li    else:
90*9c5db199SXin Li        # The cpu_uarch here is either unlisted uarch, or family_model.
91*9c5db199SXin Li        logging.debug("%s is not in RAPL support collection", cpu_uarch)
92*9c5db199SXin Li    return False
93*9c5db199SXin Li
94*9c5db199SXin Li
95*9c5db199SXin Lidef has_powercap_support():
96*9c5db199SXin Li    """Identify if OS supports powercap sysfs.
97*9c5db199SXin Li
98*9c5db199SXin Li    Returns:
99*9c5db199SXin Li        Boolean, True if powercap supported, False otherwise.
100*9c5db199SXin Li    """
101*9c5db199SXin Li    return os.path.isdir('/sys/devices/virtual/powercap/intel-rapl/')
102*9c5db199SXin Li
103*9c5db199SXin Li
104*9c5db199SXin Lidef has_lid():
105*9c5db199SXin Li    """
106*9c5db199SXin Li    Checks whether the device has lid.
107*9c5db199SXin Li
108*9c5db199SXin Li    @return: Returns True if the device has a lid, False otherwise.
109*9c5db199SXin Li    """
110*9c5db199SXin Li    INPUT_DEVICE_LIST = "/dev/input/event*"
111*9c5db199SXin Li
112*9c5db199SXin Li    return any(InputDevice(node).is_lid() for node in
113*9c5db199SXin Li               glob.glob(INPUT_DEVICE_LIST))
114*9c5db199SXin Li
115*9c5db199SXin Li
116*9c5db199SXin Lidef _call_dbus_method(destination, path, interface, method_name, args):
117*9c5db199SXin Li    """Performs a generic dbus method call."""
118*9c5db199SXin Li    command = ('dbus-send --type=method_call --system '
119*9c5db199SXin Li               '--dest=%s %s %s.%s %s') % (destination, path, interface,
120*9c5db199SXin Li                                           method_name, args)
121*9c5db199SXin Li    utils.system_output(command)
122*9c5db199SXin Li
123*9c5db199SXin Li
124*9c5db199SXin Lidef call_powerd_dbus_method(method_name, args=''):
125*9c5db199SXin Li    """
126*9c5db199SXin Li    Calls a dbus method exposed by powerd.
127*9c5db199SXin Li
128*9c5db199SXin Li    Arguments:
129*9c5db199SXin Li    @param method_name: name of the dbus method.
130*9c5db199SXin Li    @param args: string containing args to dbus method call.
131*9c5db199SXin Li    """
132*9c5db199SXin Li    _call_dbus_method(destination='org.chromium.PowerManager',
133*9c5db199SXin Li                      path='/org/chromium/PowerManager',
134*9c5db199SXin Li                      interface='org.chromium.PowerManager',
135*9c5db199SXin Li                      method_name=method_name, args=args)
136*9c5db199SXin Li
137*9c5db199SXin Li
138*9c5db199SXin Lidef get_power_supply():
139*9c5db199SXin Li    """
140*9c5db199SXin Li    Determine what type of power supply the host has.
141*9c5db199SXin Li
142*9c5db199SXin Li    Copied from server/host/cros_hosts.py
143*9c5db199SXin Li
144*9c5db199SXin Li    @returns a string representing this host's power supply.
145*9c5db199SXin Li             'power:battery' when the device has a battery intended for
146*9c5db199SXin Li                    extended use
147*9c5db199SXin Li             'power:AC_primary' when the device has a battery not intended
148*9c5db199SXin Li                    for extended use (for moving the machine, etc)
149*9c5db199SXin Li             'power:AC_only' when the device has no battery at all.
150*9c5db199SXin Li    """
151*9c5db199SXin Li    try:
152*9c5db199SXin Li        psu = utils.system_output('cros_config /hardware-properties psu-type')
153*9c5db199SXin Li    except Exception:
154*9c5db199SXin Li        # Assume battery if unspecified in cros_config.
155*9c5db199SXin Li        return 'power:battery'
156*9c5db199SXin Li
157*9c5db199SXin Li    psu_str = psu.strip()
158*9c5db199SXin Li    if psu_str == 'unknown':
159*9c5db199SXin Li        return None
160*9c5db199SXin Li
161*9c5db199SXin Li    return 'power:%s' % psu_str
162*9c5db199SXin Li
163*9c5db199SXin Li
164*9c5db199SXin Lidef get_sleep_state():
165*9c5db199SXin Li    """
166*9c5db199SXin Li    Returns the current powerd configuration of the sleep state.
167*9c5db199SXin Li    Can be "freeze" or "mem".
168*9c5db199SXin Li    """
169*9c5db199SXin Li    cmd = 'check_powerd_config --suspend_to_idle'
170*9c5db199SXin Li    result = utils.run(cmd, ignore_status=True)
171*9c5db199SXin Li    return 'freeze' if result.exit_status == 0 else 'mem'
172*9c5db199SXin Li
173*9c5db199SXin Li
174*9c5db199SXin Lidef has_battery():
175*9c5db199SXin Li    """Determine if DUT has a battery.
176*9c5db199SXin Li
177*9c5db199SXin Li    Returns:
178*9c5db199SXin Li        Boolean, False if known not to have battery, True otherwise.
179*9c5db199SXin Li    """
180*9c5db199SXin Li    return get_power_supply() == 'power:battery'
181*9c5db199SXin Li
182*9c5db199SXin Li
183*9c5db199SXin Lidef get_low_battery_shutdown_percent():
184*9c5db199SXin Li    """Get the percent-based low-battery shutdown threshold.
185*9c5db199SXin Li
186*9c5db199SXin Li    Returns:
187*9c5db199SXin Li        Float, percent-based low-battery shutdown threshold. 0 if error.
188*9c5db199SXin Li    """
189*9c5db199SXin Li    ret = 0.0
190*9c5db199SXin Li    try:
191*9c5db199SXin Li        command = 'check_powerd_config --low_battery_shutdown_percent'
192*9c5db199SXin Li        ret = float(utils.run(command).stdout)
193*9c5db199SXin Li    except error.CmdError:
194*9c5db199SXin Li        logging.debug("Can't run %s", command)
195*9c5db199SXin Li    except ValueError:
196*9c5db199SXin Li        logging.debug("Didn't get number from %s", command)
197*9c5db199SXin Li
198*9c5db199SXin Li    return ret
199*9c5db199SXin Li
200*9c5db199SXin Li
201*9c5db199SXin Lidef has_hammer():
202*9c5db199SXin Li    """Check whether DUT has hammer device or not.
203*9c5db199SXin Li
204*9c5db199SXin Li    Returns:
205*9c5db199SXin Li        boolean whether device has hammer or not
206*9c5db199SXin Li    """
207*9c5db199SXin Li    command = 'grep Hammer /sys/bus/usb/devices/*/product'
208*9c5db199SXin Li    return utils.run(command, ignore_status=True).exit_status == 0
209*9c5db199SXin Li
210*9c5db199SXin Li
211*9c5db199SXin Lidef _charge_control_by_ectool(is_charge, ignore_status):
212*9c5db199SXin Li    """execute ectool command.
213*9c5db199SXin Li
214*9c5db199SXin Li    Args:
215*9c5db199SXin Li      is_charge: Boolean, True for charging, False for discharging.
216*9c5db199SXin Li      ignore_status: do not raise an exception.
217*9c5db199SXin Li
218*9c5db199SXin Li    Returns:
219*9c5db199SXin Li      Boolean, True if the command success, False otherwise.
220*9c5db199SXin Li
221*9c5db199SXin Li    Raises:
222*9c5db199SXin Li      error.CmdError: if ectool returns non-zero exit status.
223*9c5db199SXin Li    """
224*9c5db199SXin Li    ec_cmd_discharge = 'ectool chargecontrol discharge'
225*9c5db199SXin Li    ec_cmd_normal = 'ectool chargecontrol normal'
226*9c5db199SXin Li    try:
227*9c5db199SXin Li        if is_charge:
228*9c5db199SXin Li            utils.run(ec_cmd_normal)
229*9c5db199SXin Li        else:
230*9c5db199SXin Li            utils.run(ec_cmd_discharge)
231*9c5db199SXin Li    except error.CmdError as e:
232*9c5db199SXin Li        logging.warning('Unable to use ectool: %s', e)
233*9c5db199SXin Li        if ignore_status:
234*9c5db199SXin Li            return False
235*9c5db199SXin Li        else:
236*9c5db199SXin Li            raise e
237*9c5db199SXin Li
238*9c5db199SXin Li    return True
239*9c5db199SXin Li
240*9c5db199SXin Li
241*9c5db199SXin Lidef charge_control_by_ectool(is_charge, ignore_status=True):
242*9c5db199SXin Li    """Force the battery behavior by the is_charge paremeter.
243*9c5db199SXin Li
244*9c5db199SXin Li    Args:
245*9c5db199SXin Li      is_charge: Boolean, True for charging, False for discharging.
246*9c5db199SXin Li      ignore_status: do not raise an exception.
247*9c5db199SXin Li
248*9c5db199SXin Li    Returns:
249*9c5db199SXin Li      Boolean, True if the command success, False otherwise.
250*9c5db199SXin Li
251*9c5db199SXin Li    Raises:
252*9c5db199SXin Li      error.CmdError: if ectool returns non-zero exit status.
253*9c5db199SXin Li    """
254*9c5db199SXin Li    for i in range(ECTOOL_CHARGECONTROL_RETRY_TIMES):
255*9c5db199SXin Li        if _charge_control_by_ectool(is_charge, ignore_status):
256*9c5db199SXin Li            return True
257*9c5db199SXin Li
258*9c5db199SXin Li    return False
259*9c5db199SXin Li
260*9c5db199SXin Li
261*9c5db199SXin Lidef get_core_keyvals(keyvals):
262*9c5db199SXin Li    """Get important keyvals to report.
263*9c5db199SXin Li
264*9c5db199SXin Li    Remove the following types of non-important keyvals.
265*9c5db199SXin Li    - Minor checkpoints. (start with underscore)
266*9c5db199SXin Li    - Individual cpu / gpu frequency buckets.
267*9c5db199SXin Li      (regex '[cg]pu(freq(_\d+)+)?_\d{3,}')
268*9c5db199SXin Li    - Specific idle states from cpuidle/cpupkg.
269*9c5db199SXin Li      (regex '.*cpu(idle|pkg)[ABD-Za-z0-9_\-]+C[^0].*')
270*9c5db199SXin Li
271*9c5db199SXin Li    Args:
272*9c5db199SXin Li      keyvals: keyvals to remove non-important ones.
273*9c5db199SXin Li
274*9c5db199SXin Li    Returns:
275*9c5db199SXin Li      Dictionary, keyvals with non-important ones removed.
276*9c5db199SXin Li    """
277*9c5db199SXin Li    matcher = re.compile(r"""
278*9c5db199SXin Li                         _.*|
279*9c5db199SXin Li                         .*_[cg]pu(freq(_\d+)+)?_\d{3,}_.*|
280*9c5db199SXin Li                         .*cpu(idle|pkg)[ABD-Za-z0-9_\-]+C[^0].*
281*9c5db199SXin Li                         """, re.X)
282*9c5db199SXin Li    return {k: v for k, v in keyvals.items() if not matcher.match(k)}
283*9c5db199SXin Li
284*9c5db199SXin Li
285*9c5db199SXin Li# TODO(b/220192766): Remove when Python 2 completely phase out.
286*9c5db199SXin Lidef encoding_kwargs():
287*9c5db199SXin Li    """Use encoding kwarg if it is running in Python 3+.
288*9c5db199SXin Li    """
289*9c5db199SXin Li    if sys.version_info.major > 2:
290*9c5db199SXin Li        return {'encoding': 'utf-8'}
291*9c5db199SXin Li    else:
292*9c5db199SXin Li        return {}
293*9c5db199SXin Li
294*9c5db199SXin Li
295*9c5db199SXin Liclass BacklightException(Exception):
296*9c5db199SXin Li    """Class for Backlight exceptions."""
297*9c5db199SXin Li
298*9c5db199SXin Li
299*9c5db199SXin Liclass Backlight(object):
300*9c5db199SXin Li    """Class for control of built-in panel backlight.
301*9c5db199SXin Li
302*9c5db199SXin Li    Public methods:
303*9c5db199SXin Li       set_level: Set backlight level to the given brightness.
304*9c5db199SXin Li       set_percent: Set backlight level to the given brightness percent.
305*9c5db199SXin Li       set_default: Set backlight to CrOS default.
306*9c5db199SXin Li
307*9c5db199SXin Li       get_level: Get backlight level currently.
308*9c5db199SXin Li       get_max_level: Get maximum backight level.
309*9c5db199SXin Li       get_percent: Get backlight percent currently.
310*9c5db199SXin Li       restore: Restore backlight to initial level when instance created.
311*9c5db199SXin Li
312*9c5db199SXin Li    Public attributes:
313*9c5db199SXin Li        default_brightness_percent: float of default brightness.
314*9c5db199SXin Li        force_battery: bool; if True, force backlight_tool to assume that the
315*9c5db199SXin Li                       device is on battery and have AC disconnected; if False,
316*9c5db199SXin Li                       use the device's real power source.
317*9c5db199SXin Li
318*9c5db199SXin Li    Private methods:
319*9c5db199SXin Li        _try_bl_cmd: run a backlight command.
320*9c5db199SXin Li
321*9c5db199SXin Li    Private attributes:
322*9c5db199SXin Li        _init_level: integer of backlight level when object instantiated.
323*9c5db199SXin Li        _can_control_bl: boolean determining whether backlight can be controlled
324*9c5db199SXin Li                         or queried
325*9c5db199SXin Li    """
326*9c5db199SXin Li    # Default brightness is based on expected average use case.
327*9c5db199SXin Li    # See http://www.chromium.org/chromium-os/testing/power-testing for more
328*9c5db199SXin Li    # details.
329*9c5db199SXin Li
330*9c5db199SXin Li    def __init__(self, default_brightness_percent=0, force_battery=False):
331*9c5db199SXin Li        """Constructor.
332*9c5db199SXin Li
333*9c5db199SXin Li        attributes:
334*9c5db199SXin Li        """
335*9c5db199SXin Li        self._init_level = None
336*9c5db199SXin Li        self.default_brightness_percent = default_brightness_percent
337*9c5db199SXin Li
338*9c5db199SXin Li        self._can_control_bl = True
339*9c5db199SXin Li        try:
340*9c5db199SXin Li            self._init_level = self.get_level()
341*9c5db199SXin Li        except error.TestFail:
342*9c5db199SXin Li            self._can_control_bl = False
343*9c5db199SXin Li
344*9c5db199SXin Li        logging.debug("device can_control_bl: %s", self._can_control_bl)
345*9c5db199SXin Li        if not self._can_control_bl:
346*9c5db199SXin Li            return
347*9c5db199SXin Li
348*9c5db199SXin Li        if not self.default_brightness_percent:
349*9c5db199SXin Li            force_battery_arg = "--force_battery " if force_battery else ""
350*9c5db199SXin Li            cmd = ("backlight_tool --get_initial_brightness --lux=150 " +
351*9c5db199SXin Li                   force_battery_arg + "2>/dev/null")
352*9c5db199SXin Li            try:
353*9c5db199SXin Li                level = float(utils.system_output(cmd).rstrip())
354*9c5db199SXin Li                self.default_brightness_percent = \
355*9c5db199SXin Li                    (level / self.get_max_level()) * 100
356*9c5db199SXin Li                logging.info("Default backlight brightness percent = %f%s",
357*9c5db199SXin Li                             self.default_brightness_percent,
358*9c5db199SXin Li                             " with force battery" if force_battery else "")
359*9c5db199SXin Li            except error.CmdError:
360*9c5db199SXin Li                self.default_brightness_percent = 40.0
361*9c5db199SXin Li                logging.warning("Unable to determine default backlight "
362*9c5db199SXin Li                                "brightness percent.  Setting to %f",
363*9c5db199SXin Li                                self.default_brightness_percent)
364*9c5db199SXin Li
365*9c5db199SXin Li    def _try_bl_cmd(self, arg_str):
366*9c5db199SXin Li        """Perform backlight command.
367*9c5db199SXin Li
368*9c5db199SXin Li        Args:
369*9c5db199SXin Li          arg_str:  String of additional arguments to backlight command.
370*9c5db199SXin Li
371*9c5db199SXin Li        Returns:
372*9c5db199SXin Li          String output of the backlight command.
373*9c5db199SXin Li
374*9c5db199SXin Li        Raises:
375*9c5db199SXin Li          error.TestFail: if 'cmd' returns non-zero exit status.
376*9c5db199SXin Li        """
377*9c5db199SXin Li        if not self._can_control_bl:
378*9c5db199SXin Li            return 0
379*9c5db199SXin Li        cmd = 'backlight_tool %s' % (arg_str)
380*9c5db199SXin Li        logging.debug("backlight_cmd: %s", cmd)
381*9c5db199SXin Li        try:
382*9c5db199SXin Li            return utils.system_output(cmd).rstrip()
383*9c5db199SXin Li        except error.CmdError:
384*9c5db199SXin Li            raise error.TestFail(cmd)
385*9c5db199SXin Li
386*9c5db199SXin Li    def set_level(self, level):
387*9c5db199SXin Li        """Set backlight level to the given brightness.
388*9c5db199SXin Li
389*9c5db199SXin Li        Args:
390*9c5db199SXin Li          level: integer of brightness to set
391*9c5db199SXin Li        """
392*9c5db199SXin Li        self._try_bl_cmd('--set_brightness=%d' % (level))
393*9c5db199SXin Li
394*9c5db199SXin Li    def set_percent(self, percent):
395*9c5db199SXin Li        """Set backlight level to the given brightness percent.
396*9c5db199SXin Li
397*9c5db199SXin Li        Args:
398*9c5db199SXin Li          percent: float between 0 and 100
399*9c5db199SXin Li        """
400*9c5db199SXin Li        self._try_bl_cmd('--set_brightness_percent=%f' % (percent))
401*9c5db199SXin Li
402*9c5db199SXin Li    def set_default(self):
403*9c5db199SXin Li        """Set backlight to CrOS default.
404*9c5db199SXin Li        """
405*9c5db199SXin Li        self.set_percent(self.default_brightness_percent)
406*9c5db199SXin Li
407*9c5db199SXin Li    def get_level(self):
408*9c5db199SXin Li        """Get backlight level currently.
409*9c5db199SXin Li
410*9c5db199SXin Li        Returns integer of current backlight level or zero if no backlight
411*9c5db199SXin Li        exists.
412*9c5db199SXin Li        """
413*9c5db199SXin Li        return int(self._try_bl_cmd('--get_brightness'))
414*9c5db199SXin Li
415*9c5db199SXin Li    def get_max_level(self):
416*9c5db199SXin Li        """Get maximum backight level.
417*9c5db199SXin Li
418*9c5db199SXin Li        Returns integer of maximum backlight level or zero if no backlight
419*9c5db199SXin Li        exists.
420*9c5db199SXin Li        """
421*9c5db199SXin Li        return int(self._try_bl_cmd('--get_max_brightness'))
422*9c5db199SXin Li
423*9c5db199SXin Li    def get_percent(self):
424*9c5db199SXin Li        """Get backlight percent currently.
425*9c5db199SXin Li
426*9c5db199SXin Li        Returns float of current backlight percent or zero if no backlight
427*9c5db199SXin Li        exists
428*9c5db199SXin Li        """
429*9c5db199SXin Li        return float(self._try_bl_cmd('--get_brightness_percent'))
430*9c5db199SXin Li
431*9c5db199SXin Li    def linear_to_nonlinear(self, linear):
432*9c5db199SXin Li        """Convert supplied linear brightness percent to nonlinear.
433*9c5db199SXin Li
434*9c5db199SXin Li        Returns float of supplied linear brightness percent converted to
435*9c5db199SXin Li        nonlinear percent.
436*9c5db199SXin Li        """
437*9c5db199SXin Li        return float(self._try_bl_cmd('--linear_to_nonlinear=%f' % linear))
438*9c5db199SXin Li
439*9c5db199SXin Li    def nonlinear_to_linear(self, nonlinear):
440*9c5db199SXin Li        """Convert supplied nonlinear brightness percent to linear.
441*9c5db199SXin Li
442*9c5db199SXin Li        Returns float of supplied nonlinear brightness percent converted to
443*9c5db199SXin Li        linear percent.
444*9c5db199SXin Li        """
445*9c5db199SXin Li        return float(self._try_bl_cmd('--nonlinear_to_linear=%f' % nonlinear))
446*9c5db199SXin Li
447*9c5db199SXin Li    def restore(self):
448*9c5db199SXin Li        """Restore backlight to initial level when instance created."""
449*9c5db199SXin Li        if self._init_level is not None:
450*9c5db199SXin Li            self.set_level(self._init_level)
451*9c5db199SXin Li
452*9c5db199SXin Li
453*9c5db199SXin Liclass KbdBacklightException(Exception):
454*9c5db199SXin Li    """Class for KbdBacklight exceptions."""
455*9c5db199SXin Li
456*9c5db199SXin Li
457*9c5db199SXin Liclass KbdBacklight(object):
458*9c5db199SXin Li    """Class for control of keyboard backlight.
459*9c5db199SXin Li
460*9c5db199SXin Li    Example code:
461*9c5db199SXin Li        kblight = power_utils.KbdBacklight()
462*9c5db199SXin Li        kblight.set(10)
463*9c5db199SXin Li        print "kblight % is %.f" % kblight.get_percent()
464*9c5db199SXin Li
465*9c5db199SXin Li    Public methods:
466*9c5db199SXin Li        set_percent: Sets the keyboard backlight to a percent.
467*9c5db199SXin Li        get_percent: Get current keyboard backlight percentage.
468*9c5db199SXin Li        set_level: Sets the keyboard backlight to a level.
469*9c5db199SXin Li        get_default_level: Get default keyboard backlight brightness level
470*9c5db199SXin Li
471*9c5db199SXin Li    Private attributes:
472*9c5db199SXin Li        _default_backlight_level: keboard backlight level set by default
473*9c5db199SXin Li
474*9c5db199SXin Li    """
475*9c5db199SXin Li
476*9c5db199SXin Li    def __init__(self):
477*9c5db199SXin Li        cmd = 'check_powerd_config --keyboard_backlight'
478*9c5db199SXin Li        result = utils.run(cmd, ignore_status=True)
479*9c5db199SXin Li        if result.exit_status:
480*9c5db199SXin Li            raise KbdBacklightException('Keyboard backlight support' +
481*9c5db199SXin Li                                        'is not enabled')
482*9c5db199SXin Li        try:
483*9c5db199SXin Li            cmd = ("backlight_tool --keyboard --get_initial_brightness "
484*9c5db199SXin Li                   "--lux=0 2>/dev/null")
485*9c5db199SXin Li            self._default_backlight_level = int(
486*9c5db199SXin Li                utils.system_output(cmd).rstrip())
487*9c5db199SXin Li            logging.info("Default keyboard backlight brightness level = %d",
488*9c5db199SXin Li                         self._default_backlight_level)
489*9c5db199SXin Li        except Exception:
490*9c5db199SXin Li            raise KbdBacklightException('Keyboard backlight is malfunctioning')
491*9c5db199SXin Li
492*9c5db199SXin Li    def get_percent(self):
493*9c5db199SXin Li        """Get current keyboard brightness setting percentage.
494*9c5db199SXin Li
495*9c5db199SXin Li        Returns:
496*9c5db199SXin Li            float, percentage of keyboard brightness in the range [0.0, 100.0].
497*9c5db199SXin Li        """
498*9c5db199SXin Li        cmd = 'backlight_tool --keyboard --get_brightness_percent'
499*9c5db199SXin Li        return float(utils.system_output(cmd).strip())
500*9c5db199SXin Li
501*9c5db199SXin Li    def get_default_level(self):
502*9c5db199SXin Li        """
503*9c5db199SXin Li        Returns the default backlight level.
504*9c5db199SXin Li
505*9c5db199SXin Li        Returns:
506*9c5db199SXin Li            The default keyboard backlight level.
507*9c5db199SXin Li        """
508*9c5db199SXin Li        return self._default_backlight_level
509*9c5db199SXin Li
510*9c5db199SXin Li    def set_percent(self, percent):
511*9c5db199SXin Li        """Set keyboard backlight percent.
512*9c5db199SXin Li
513*9c5db199SXin Li        Args:
514*9c5db199SXin Li        @param percent: float value in the range [0.0, 100.0]
515*9c5db199SXin Li                        to set keyboard backlight to.
516*9c5db199SXin Li        """
517*9c5db199SXin Li        cmd = 'backlight_tool --keyboard --set_brightness_percent=%f' % percent
518*9c5db199SXin Li        utils.system(cmd)
519*9c5db199SXin Li
520*9c5db199SXin Li    def set_level(self, level):
521*9c5db199SXin Li        """
522*9c5db199SXin Li        Set keyboard backlight to given level.
523*9c5db199SXin Li        Args:
524*9c5db199SXin Li        @param level: level to set keyboard backlight to.
525*9c5db199SXin Li        """
526*9c5db199SXin Li        cmd = 'backlight_tool --keyboard --set_brightness=%d' % level
527*9c5db199SXin Li        utils.system(cmd)
528*9c5db199SXin Li
529*9c5db199SXin Li
530*9c5db199SXin Liclass BacklightController(object):
531*9c5db199SXin Li    """Class to simulate control of backlight via keyboard or Chrome UI.
532*9c5db199SXin Li
533*9c5db199SXin Li    Public methods:
534*9c5db199SXin Li      increase_brightness: Increase backlight by one adjustment step.
535*9c5db199SXin Li      decrease_brightness: Decrease backlight by one adjustment step.
536*9c5db199SXin Li      set_brightness_to_max: Increase backlight to max by calling
537*9c5db199SXin Li          increase_brightness()
538*9c5db199SXin Li      set_brightness_to_min: Decrease backlight to min or zero by calling
539*9c5db199SXin Li          decrease_brightness()
540*9c5db199SXin Li
541*9c5db199SXin Li    Private attributes:
542*9c5db199SXin Li      _max_num_steps: maximum number of backlight adjustment steps between 0 and
543*9c5db199SXin Li                      max brightness.
544*9c5db199SXin Li    """
545*9c5db199SXin Li
546*9c5db199SXin Li    def __init__(self):
547*9c5db199SXin Li        self._max_num_steps = 16
548*9c5db199SXin Li
549*9c5db199SXin Li    def decrease_brightness(self, allow_off=False):
550*9c5db199SXin Li        """
551*9c5db199SXin Li        Decrease brightness by one step, as if the user pressed the brightness
552*9c5db199SXin Li        down key or button.
553*9c5db199SXin Li
554*9c5db199SXin Li        Arguments
555*9c5db199SXin Li        @param allow_off: Boolean flag indicating whether the brightness can be
556*9c5db199SXin Li                     reduced to zero.
557*9c5db199SXin Li                     Set to true to simulate brightness down key.
558*9c5db199SXin Li                     set to false to simulate Chrome UI brightness down button.
559*9c5db199SXin Li        """
560*9c5db199SXin Li        call_powerd_dbus_method('DecreaseScreenBrightness',
561*9c5db199SXin Li                                'boolean:%s' %
562*9c5db199SXin Li                                ('true' if allow_off else 'false'))
563*9c5db199SXin Li
564*9c5db199SXin Li    def increase_brightness(self):
565*9c5db199SXin Li        """
566*9c5db199SXin Li        Increase brightness by one step, as if the user pressed the brightness
567*9c5db199SXin Li        up key or button.
568*9c5db199SXin Li        """
569*9c5db199SXin Li        call_powerd_dbus_method('IncreaseScreenBrightness')
570*9c5db199SXin Li
571*9c5db199SXin Li    def set_brightness_to_max(self):
572*9c5db199SXin Li        """
573*9c5db199SXin Li        Increases the brightness using powerd until the brightness reaches the
574*9c5db199SXin Li        maximum value. Returns when it reaches the maximum number of brightness
575*9c5db199SXin Li        adjustments
576*9c5db199SXin Li        """
577*9c5db199SXin Li        num_steps_taken = 0
578*9c5db199SXin Li        while num_steps_taken < self._max_num_steps:
579*9c5db199SXin Li            self.increase_brightness()
580*9c5db199SXin Li            time.sleep(0.05)
581*9c5db199SXin Li            num_steps_taken += 1
582*9c5db199SXin Li
583*9c5db199SXin Li    def set_brightness_to_min(self, allow_off=False):
584*9c5db199SXin Li        """
585*9c5db199SXin Li        Decreases the brightness using powerd until the brightness reaches the
586*9c5db199SXin Li        minimum value (zero or the minimum nonzero value). Returns when it
587*9c5db199SXin Li        reaches the maximum number of brightness adjustments.
588*9c5db199SXin Li
589*9c5db199SXin Li        Arguments
590*9c5db199SXin Li        @param allow_off: Boolean flag indicating whether the brightness can be
591*9c5db199SXin Li                     reduced to zero.
592*9c5db199SXin Li                     Set to true to simulate brightness down key.
593*9c5db199SXin Li                     set to false to simulate Chrome UI brightness down button.
594*9c5db199SXin Li        """
595*9c5db199SXin Li        num_steps_taken = 0
596*9c5db199SXin Li        while num_steps_taken < self._max_num_steps:
597*9c5db199SXin Li            self.decrease_brightness(allow_off)
598*9c5db199SXin Li            time.sleep(0.05)
599*9c5db199SXin Li            num_steps_taken += 1
600*9c5db199SXin Li
601*9c5db199SXin Li
602*9c5db199SXin Liclass DisplayException(Exception):
603*9c5db199SXin Li    """Class for Display exceptions."""
604*9c5db199SXin Li
605*9c5db199SXin Li
606*9c5db199SXin Lidef set_display_power(power_val):
607*9c5db199SXin Li    """Function to control screens via Chrome.
608*9c5db199SXin Li
609*9c5db199SXin Li    Possible arguments:
610*9c5db199SXin Li      DISPLAY_POWER_ALL_ON,
611*9c5db199SXin Li      DISPLAY_POWER_ALL_OFF,
612*9c5db199SXin Li      DISPLAY_POWER_INTERNAL_OFF_EXTERNAL_ON,
613*9c5db199SXin Li      DISPLAY_POWER_INTERNAL_ON_EXTENRAL_OFF
614*9c5db199SXin Li    """
615*9c5db199SXin Li    if (not isinstance(power_val, int)
616*9c5db199SXin Li            or power_val < DISPLAY_POWER_ALL_ON
617*9c5db199SXin Li            or power_val >= DISPLAY_POWER_MAX):
618*9c5db199SXin Li        raise DisplayException('Invalid display power setting: %d' % power_val)
619*9c5db199SXin Li    _call_dbus_method(destination='org.chromium.DisplayService',
620*9c5db199SXin Li                      path='/org/chromium/DisplayService',
621*9c5db199SXin Li                      interface='org.chromium.DisplayServiceInterface',
622*9c5db199SXin Li                      method_name='SetPower',
623*9c5db199SXin Li                      args='int32:%d' % power_val)
624*9c5db199SXin Li
625*9c5db199SXin Li
626*9c5db199SXin Liclass PowerPrefChanger(object):
627*9c5db199SXin Li    """
628*9c5db199SXin Li    Class to temporarily change powerd prefs. Construct with a dict of
629*9c5db199SXin Li    pref_name/value pairs (e.g. {'disable_idle_suspend':0}). Destructor (or
630*9c5db199SXin Li    reboot) will restore old prefs automatically."""
631*9c5db199SXin Li
632*9c5db199SXin Li    _PREFDIR = '/var/lib/power_manager'
633*9c5db199SXin Li    _TEMPDIR = '/tmp/autotest_powerd_prefs'
634*9c5db199SXin Li
635*9c5db199SXin Li    def __init__(self, prefs):
636*9c5db199SXin Li        shutil.copytree(self._PREFDIR, self._TEMPDIR)
637*9c5db199SXin Li        for name, value in prefs.items():
638*9c5db199SXin Li            utils.write_one_line('%s/%s' % (self._TEMPDIR, name), value)
639*9c5db199SXin Li        utils.system('mount --bind %s %s' % (self._TEMPDIR, self._PREFDIR))
640*9c5db199SXin Li        upstart.restart_job('powerd')
641*9c5db199SXin Li
642*9c5db199SXin Li    def finalize(self):
643*9c5db199SXin Li        """finalize"""
644*9c5db199SXin Li        if os.path.exists(self._TEMPDIR):
645*9c5db199SXin Li            utils.system('umount %s' % self._PREFDIR, ignore_status=True)
646*9c5db199SXin Li            shutil.rmtree(self._TEMPDIR)
647*9c5db199SXin Li            upstart.restart_job('powerd')
648*9c5db199SXin Li
649*9c5db199SXin Li    def __del__(self):
650*9c5db199SXin Li        self.finalize()
651*9c5db199SXin Li
652*9c5db199SXin Li
653*9c5db199SXin Liclass Registers(object):
654*9c5db199SXin Li    """Class to examine PCI and MSR registers."""
655*9c5db199SXin Li
656*9c5db199SXin Li    def __init__(self):
657*9c5db199SXin Li        self._cpu_id = 0
658*9c5db199SXin Li        self._rdmsr_cmd = 'iotools rdmsr'
659*9c5db199SXin Li        self._mmio_read32_cmd = 'iotools mmio_read32'
660*9c5db199SXin Li        self._rcba = 0xfed1c000
661*9c5db199SXin Li
662*9c5db199SXin Li        self._pci_read32_cmd = 'iotools pci_read32'
663*9c5db199SXin Li        self._mch_bar = None
664*9c5db199SXin Li        self._dmi_bar = None
665*9c5db199SXin Li
666*9c5db199SXin Li    def _init_mch_bar(self):
667*9c5db199SXin Li        if self._mch_bar != None:
668*9c5db199SXin Li            return
669*9c5db199SXin Li        # MCHBAR is at offset 0x48 of B/D/F 0/0/0
670*9c5db199SXin Li        cmd = '%s 0 0 0 0x48' % (self._pci_read32_cmd)
671*9c5db199SXin Li        self._mch_bar = int(utils.system_output(cmd), 16) & 0xfffffffe
672*9c5db199SXin Li        logging.debug('MCH BAR is %s', hex(self._mch_bar))
673*9c5db199SXin Li
674*9c5db199SXin Li    def _init_dmi_bar(self):
675*9c5db199SXin Li        if self._dmi_bar != None:
676*9c5db199SXin Li            return
677*9c5db199SXin Li        # DMIBAR is at offset 0x68 of B/D/F 0/0/0
678*9c5db199SXin Li        cmd = '%s 0 0 0 0x68' % (self._pci_read32_cmd)
679*9c5db199SXin Li        self._dmi_bar = int(utils.system_output(cmd), 16) & 0xfffffffe
680*9c5db199SXin Li        logging.debug('DMI BAR is %s', hex(self._dmi_bar))
681*9c5db199SXin Li
682*9c5db199SXin Li    def _read_msr(self, register):
683*9c5db199SXin Li        cmd = '%s %d %s' % (self._rdmsr_cmd, self._cpu_id, register)
684*9c5db199SXin Li        return int(utils.system_output(cmd), 16)
685*9c5db199SXin Li
686*9c5db199SXin Li    def _read_mmio_read32(self, address):
687*9c5db199SXin Li        cmd = '%s 0x%x' % (self._mmio_read32_cmd, address)
688*9c5db199SXin Li        return int(utils.system_output(cmd), 16)
689*9c5db199SXin Li
690*9c5db199SXin Li    def _read_dmi_bar(self, offset):
691*9c5db199SXin Li        self._init_dmi_bar()
692*9c5db199SXin Li        return self._read_mmio_read32(self._dmi_bar + int(offset, 16))
693*9c5db199SXin Li
694*9c5db199SXin Li    def _read_mch_bar(self, offset):
695*9c5db199SXin Li        self._init_mch_bar()
696*9c5db199SXin Li        return self._read_mmio_read32(self._mch_bar + int(offset, 16))
697*9c5db199SXin Li
698*9c5db199SXin Li    def _read_rcba(self, offset):
699*9c5db199SXin Li        return self._read_mmio_read32(self._rcba + int(offset, 16))
700*9c5db199SXin Li
701*9c5db199SXin Li    def _shift_mask_match(self, reg_name, value, match):
702*9c5db199SXin Li        expr = match[1]
703*9c5db199SXin Li        bits = match[0].split(':')
704*9c5db199SXin Li        operator = match[2] if len(match) == 3 else '=='
705*9c5db199SXin Li        hi_bit = int(bits[0])
706*9c5db199SXin Li        if len(bits) == 2:
707*9c5db199SXin Li            lo_bit = int(bits[1])
708*9c5db199SXin Li        else:
709*9c5db199SXin Li            lo_bit = int(bits[0])
710*9c5db199SXin Li
711*9c5db199SXin Li        value >>= lo_bit
712*9c5db199SXin Li        mask = (1 << (hi_bit - lo_bit + 1)) - 1
713*9c5db199SXin Li        value &= mask
714*9c5db199SXin Li
715*9c5db199SXin Li        good = eval("%d %s %d" % (value, operator, expr))
716*9c5db199SXin Li        if not good:
717*9c5db199SXin Li            logging.error('FAILED: %s bits: %s value: %s mask: %s expr: %s ' +
718*9c5db199SXin Li                          'operator: %s', reg_name, bits, hex(value), mask,
719*9c5db199SXin Li                          expr, operator)
720*9c5db199SXin Li        return good
721*9c5db199SXin Li
722*9c5db199SXin Li    def _verify_registers(self, reg_name, read_fn, match_list):
723*9c5db199SXin Li        errors = 0
724*9c5db199SXin Li        for k, v in match_list.items():
725*9c5db199SXin Li            r = read_fn(k)
726*9c5db199SXin Li            for item in v:
727*9c5db199SXin Li                good = self._shift_mask_match(reg_name, r, item)
728*9c5db199SXin Li                if not good:
729*9c5db199SXin Li                    errors += 1
730*9c5db199SXin Li                    logging.error('Error(%d), %s: reg = %s val = %s match = %s',
731*9c5db199SXin Li                                  errors, reg_name, k, hex(r), v)
732*9c5db199SXin Li                else:
733*9c5db199SXin Li                    logging.debug('ok, %s: reg = %s val = %s match = %s',
734*9c5db199SXin Li                                  reg_name, k, hex(r), v)
735*9c5db199SXin Li        return errors
736*9c5db199SXin Li
737*9c5db199SXin Li    def verify_msr(self, match_list):
738*9c5db199SXin Li        """
739*9c5db199SXin Li        Verify MSR
740*9c5db199SXin Li
741*9c5db199SXin Li        @param match_list: match list
742*9c5db199SXin Li        """
743*9c5db199SXin Li        errors = 0
744*9c5db199SXin Li        for cpu_id in range(0, max(utils.count_cpus(), 1)):
745*9c5db199SXin Li            self._cpu_id = cpu_id
746*9c5db199SXin Li            errors += self._verify_registers('msr', self._read_msr, match_list)
747*9c5db199SXin Li        return errors
748*9c5db199SXin Li
749*9c5db199SXin Li    def verify_dmi(self, match_list):
750*9c5db199SXin Li        """
751*9c5db199SXin Li        Verify DMI
752*9c5db199SXin Li
753*9c5db199SXin Li        @param match_list: match list
754*9c5db199SXin Li        """
755*9c5db199SXin Li        return self._verify_registers('dmi', self._read_dmi_bar, match_list)
756*9c5db199SXin Li
757*9c5db199SXin Li    def verify_mch(self, match_list):
758*9c5db199SXin Li        """
759*9c5db199SXin Li        Verify MCH
760*9c5db199SXin Li
761*9c5db199SXin Li        @param match_list: match list
762*9c5db199SXin Li        """
763*9c5db199SXin Li        return self._verify_registers('mch', self._read_mch_bar, match_list)
764*9c5db199SXin Li
765*9c5db199SXin Li    def verify_rcba(self, match_list):
766*9c5db199SXin Li        """
767*9c5db199SXin Li        Verify RCBA
768*9c5db199SXin Li
769*9c5db199SXin Li        @param match_list: match list
770*9c5db199SXin Li        """
771*9c5db199SXin Li        return self._verify_registers('rcba', self._read_rcba, match_list)
772*9c5db199SXin Li
773*9c5db199SXin Li
774*9c5db199SXin Liclass USBDevicePower(object):
775*9c5db199SXin Li    """Class for USB device related power information.
776*9c5db199SXin Li
777*9c5db199SXin Li    Public Methods:
778*9c5db199SXin Li        autosuspend: Return boolean whether USB autosuspend is enabled or False
779*9c5db199SXin Li                     if not or unable to determine
780*9c5db199SXin Li
781*9c5db199SXin Li    Public attributes:
782*9c5db199SXin Li        vid: string of USB Vendor ID
783*9c5db199SXin Li        pid: string of USB Product ID
784*9c5db199SXin Li        allowlisted: Boolean if USB device is allowlisted for USB auto-suspend
785*9c5db199SXin Li
786*9c5db199SXin Li    Private attributes:
787*9c5db199SXin Li       path: string to path of the USB devices in sysfs ( /sys/bus/usb/... )
788*9c5db199SXin Li
789*9c5db199SXin Li    TODO(tbroch): consider converting to use of pyusb although not clear its
790*9c5db199SXin Li    beneficial if it doesn't parse power/control
791*9c5db199SXin Li    """
792*9c5db199SXin Li
793*9c5db199SXin Li    def __init__(self, vid, pid, allowlisted, path):
794*9c5db199SXin Li        self.vid = vid
795*9c5db199SXin Li        self.pid = pid
796*9c5db199SXin Li        self.allowlisted = allowlisted
797*9c5db199SXin Li        self._path = path
798*9c5db199SXin Li
799*9c5db199SXin Li    def autosuspend(self):
800*9c5db199SXin Li        """Determine current value of USB autosuspend for device."""
801*9c5db199SXin Li        control_file = os.path.join(self._path, 'control')
802*9c5db199SXin Li        if not os.path.exists(control_file):
803*9c5db199SXin Li            logging.info('USB: power control file not found for %s', dir)
804*9c5db199SXin Li            return False
805*9c5db199SXin Li
806*9c5db199SXin Li        out = utils.read_one_line(control_file)
807*9c5db199SXin Li        logging.debug('USB: control set to %s for %s', out, control_file)
808*9c5db199SXin Li        return (out == 'auto')
809*9c5db199SXin Li
810*9c5db199SXin Li
811*9c5db199SXin Liclass USBPower(object):
812*9c5db199SXin Li    """Class to expose USB related power functionality.
813*9c5db199SXin Li
814*9c5db199SXin Li    Initially that includes the policy around USB auto-suspend and our
815*9c5db199SXin Li    allowlisting of devices that are internal to CrOS system.
816*9c5db199SXin Li
817*9c5db199SXin Li    Example code:
818*9c5db199SXin Li       usbdev_power = power_utils.USBPower()
819*9c5db199SXin Li       for device in usbdev_power.devices
820*9c5db199SXin Li           if device.is_allowlisted()
821*9c5db199SXin Li               ...
822*9c5db199SXin Li
823*9c5db199SXin Li    Public attributes:
824*9c5db199SXin Li        devices: list of USBDevicePower instances
825*9c5db199SXin Li
826*9c5db199SXin Li    Private functions:
827*9c5db199SXin Li        _is_allowlisted: Returns Boolean if USB device is allowlisted for USB
828*9c5db199SXin Li                         auto-suspend
829*9c5db199SXin Li        _load_allowlist: Reads allowlist and stores int _allowlist attribute
830*9c5db199SXin Li
831*9c5db199SXin Li    Private attributes:
832*9c5db199SXin Li        _alist_file: path to laptop-mode-tools (LMT) USB autosuspend
833*9c5db199SXin Li                         conf file.
834*9c5db199SXin Li        _alist_vname: string name of LMT USB autosuspend allowlist
835*9c5db199SXin Li                          variable
836*9c5db199SXin Li        _allowlisted: list of USB device vid:pid that are allowlisted.
837*9c5db199SXin Li                        May be regular expressions.  See LMT for details.
838*9c5db199SXin Li    """
839*9c5db199SXin Li
840*9c5db199SXin Li    def __init__(self):
841*9c5db199SXin Li        self._alist_file = \
842*9c5db199SXin Li            '/etc/laptop-mode/conf.d/board-specific/usb-autosuspend.conf'
843*9c5db199SXin Li        # TODO b:169251326 terms below are set outside of this codebase
844*9c5db199SXin Li        # and should be updated when possible. ("WHITELIST" -> "ALLOWLIST") # nocheck
845*9c5db199SXin Li        self._alist_vname = '$AUTOSUSPEND_USBID_WHITELIST' # nocheck
846*9c5db199SXin Li        self._allowlisted = None
847*9c5db199SXin Li        self.devices = []
848*9c5db199SXin Li
849*9c5db199SXin Li    def _load_allowlist(self):
850*9c5db199SXin Li        """Load USB device allowlist for enabling USB autosuspend
851*9c5db199SXin Li
852*9c5db199SXin Li        CrOS allowlist only internal USB devices to enter USB auto-suspend mode
853*9c5db199SXin Li        via laptop-mode tools.
854*9c5db199SXin Li        """
855*9c5db199SXin Li        cmd = "source %s && echo %s" % (self._alist_file,
856*9c5db199SXin Li                                        self._alist_vname)
857*9c5db199SXin Li        out = utils.system_output(cmd, ignore_status=True)
858*9c5db199SXin Li        logging.debug('USB allowlist = %s', out)
859*9c5db199SXin Li        self._allowlisted = out.split()
860*9c5db199SXin Li
861*9c5db199SXin Li    def _is_allowlisted(self, vid, pid):
862*9c5db199SXin Li        """Check to see if USB device vid:pid is allowlisted.
863*9c5db199SXin Li
864*9c5db199SXin Li        Args:
865*9c5db199SXin Li          vid: string of USB vendor ID
866*9c5db199SXin Li          pid: string of USB product ID
867*9c5db199SXin Li
868*9c5db199SXin Li        Returns:
869*9c5db199SXin Li          True if vid:pid in allowlist file else False
870*9c5db199SXin Li        """
871*9c5db199SXin Li        if self._allowlisted is None:
872*9c5db199SXin Li            self._load_allowlist()
873*9c5db199SXin Li
874*9c5db199SXin Li        match_str = "%s:%s" % (vid, pid)
875*9c5db199SXin Li        for re_str in self._allowlisted:
876*9c5db199SXin Li            if re.match(re_str, match_str):
877*9c5db199SXin Li                return True
878*9c5db199SXin Li        return False
879*9c5db199SXin Li
880*9c5db199SXin Li    def query_devices(self):
881*9c5db199SXin Li        """."""
882*9c5db199SXin Li        dirs_path = '/sys/bus/usb/devices/*/power'
883*9c5db199SXin Li        dirs = glob.glob(dirs_path)
884*9c5db199SXin Li        if not dirs:
885*9c5db199SXin Li            logging.info('USB power path not found')
886*9c5db199SXin Li            return 1
887*9c5db199SXin Li
888*9c5db199SXin Li        for dirpath in dirs:
889*9c5db199SXin Li            vid_path = os.path.join(dirpath, '..', 'idVendor')
890*9c5db199SXin Li            pid_path = os.path.join(dirpath, '..', 'idProduct')
891*9c5db199SXin Li            if not os.path.exists(vid_path):
892*9c5db199SXin Li                logging.debug("No vid for USB @ %s", vid_path)
893*9c5db199SXin Li                continue
894*9c5db199SXin Li            vid = utils.read_one_line(vid_path)
895*9c5db199SXin Li            pid = utils.read_one_line(pid_path)
896*9c5db199SXin Li            allowlisted = self._is_allowlisted(vid, pid)
897*9c5db199SXin Li            self.devices.append(USBDevicePower(vid, pid, allowlisted, dirpath))
898*9c5db199SXin Li
899*9c5db199SXin Li
900*9c5db199SXin Liclass DisplayPanelSelfRefresh(object):
901*9c5db199SXin Li    """Class for control and monitoring of display's PSR."""
902*9c5db199SXin Li    _PSR_STATUS_FILE_X86 = '/sys/kernel/debug/dri/0/i915_edp_psr_status'
903*9c5db199SXin Li    _PSR_STATUS_FILE_ARM = '/sys/kernel/debug/dri/*/psr_active_ms'
904*9c5db199SXin Li
905*9c5db199SXin Li    def __init__(self, init_time=time.time()):
906*9c5db199SXin Li        """Initializer.
907*9c5db199SXin Li
908*9c5db199SXin Li        @Public attributes:
909*9c5db199SXin Li            supported: Boolean of whether PSR is supported or not
910*9c5db199SXin Li
911*9c5db199SXin Li        @Private attributes:
912*9c5db199SXin Li            _init_time: time when PSR class was instantiated.
913*9c5db199SXin Li            _init_counter: integer of initial value of residency counter.
914*9c5db199SXin Li            _keyvals: dictionary of keyvals
915*9c5db199SXin Li        """
916*9c5db199SXin Li        self._psr_path = ''
917*9c5db199SXin Li        if os.path.exists(self._PSR_STATUS_FILE_X86):
918*9c5db199SXin Li            self._psr_path = self._PSR_STATUS_FILE_X86
919*9c5db199SXin Li            self._psr_parse_prefix = 'Performance_Counter:'
920*9c5db199SXin Li        else:
921*9c5db199SXin Li            paths = glob.glob(self._PSR_STATUS_FILE_ARM)
922*9c5db199SXin Li            if paths:
923*9c5db199SXin Li                # Should be only one PSR file
924*9c5db199SXin Li                self._psr_path = paths[0]
925*9c5db199SXin Li                self._psr_parse_prefix = ''
926*9c5db199SXin Li
927*9c5db199SXin Li        self._init_time = init_time
928*9c5db199SXin Li        self._init_counter = self._get_counter()
929*9c5db199SXin Li        self._keyvals = {}
930*9c5db199SXin Li        self.supported = (self._init_counter != None)
931*9c5db199SXin Li
932*9c5db199SXin Li    def _get_counter(self):
933*9c5db199SXin Li        """Get the current value of the system PSR counter.
934*9c5db199SXin Li
935*9c5db199SXin Li        This counts the number of milliseconds the system has resided in PSR.
936*9c5db199SXin Li
937*9c5db199SXin Li        @returns: amount of time PSR has been active since boot in ms, or None if
938*9c5db199SXin Li        the performance counter can't be read.
939*9c5db199SXin Li        """
940*9c5db199SXin Li        try:
941*9c5db199SXin Li            count = utils.get_field(utils.read_file(self._psr_path),
942*9c5db199SXin Li                                    0, linestart=self._psr_parse_prefix)
943*9c5db199SXin Li        except IOError:
944*9c5db199SXin Li            logging.info("Can't find or read PSR status file")
945*9c5db199SXin Li            return None
946*9c5db199SXin Li
947*9c5db199SXin Li        logging.debug("PSR performance counter: %s", count)
948*9c5db199SXin Li        return int(count) if count else None
949*9c5db199SXin Li
950*9c5db199SXin Li    def _calc_residency(self):
951*9c5db199SXin Li        """Calculate the PSR residency.
952*9c5db199SXin Li
953*9c5db199SXin Li        @returns: PSR residency in percent or -1 if not able to calculate.
954*9c5db199SXin Li        """
955*9c5db199SXin Li        if not self.supported:
956*9c5db199SXin Li            return -1
957*9c5db199SXin Li
958*9c5db199SXin Li        tdelta = time.time() - self._init_time
959*9c5db199SXin Li        cdelta = self._get_counter() - self._init_counter
960*9c5db199SXin Li        return cdelta / (10 * tdelta)
961*9c5db199SXin Li
962*9c5db199SXin Li    def refresh(self):
963*9c5db199SXin Li        """Refresh PSR related data."""
964*9c5db199SXin Li        self._keyvals['percent_psr_residency'] = self._calc_residency()
965*9c5db199SXin Li
966*9c5db199SXin Li    def get_keyvals(self):
967*9c5db199SXin Li        """Get keyvals associated with PSR data.
968*9c5db199SXin Li
969*9c5db199SXin Li        @returns dictionary of keyvals
970*9c5db199SXin Li        """
971*9c5db199SXin Li        return self._keyvals
972*9c5db199SXin Li
973*9c5db199SXin Li
974*9c5db199SXin Liclass BaseActivityException(Exception):
975*9c5db199SXin Li    """Class for base activity simulation exceptions."""
976*9c5db199SXin Li
977*9c5db199SXin Li
978*9c5db199SXin Liclass BaseActivitySimulator(object):
979*9c5db199SXin Li    """Class to simulate wake activity on the normally autosuspended base."""
980*9c5db199SXin Li
981*9c5db199SXin Li    # Note on naming: throughout this class, the word base is used to mean the
982*9c5db199SXin Li    # base of a detachable (keyboard, touchpad, etc).
983*9c5db199SXin Li
984*9c5db199SXin Li    # file defines where to look for detachable base.
985*9c5db199SXin Li    # TODO(coconutruben): check when next wave of detachables come out if this
986*9c5db199SXin Li    # structure still holds, or if we need to replace it by querying input
987*9c5db199SXin Li    # devices.
988*9c5db199SXin Li    _BASE_INIT_CMD = 'cros_config /detachable-base usb-path'
989*9c5db199SXin Li    _BASE_INIT_FILE = '/etc/init/hammerd.override'
990*9c5db199SXin Li    _BASE_WAKE_TIME_MS = 10000
991*9c5db199SXin Li
992*9c5db199SXin Li    def __init__(self):
993*9c5db199SXin Li        """Initializer
994*9c5db199SXin Li
995*9c5db199SXin Li        Let the BaseActivitySimulator bootstrap itself by detecting if
996*9c5db199SXin Li        the board is a detachable, and ensuring the base path exists.
997*9c5db199SXin Li        Sets the base to autosuspend, and the autosuspend delay to be
998*9c5db199SXin Li        at most _BASE_WAKE_TIME_MS.
999*9c5db199SXin Li
1000*9c5db199SXin Li        """
1001*9c5db199SXin Li        self._should_run = False
1002*9c5db199SXin Li
1003*9c5db199SXin Li        if os.path.exists(self._BASE_INIT_FILE):
1004*9c5db199SXin Li            # Try hammerd.override first.
1005*9c5db199SXin Li            init_file_content = utils.read_file(self._BASE_INIT_FILE)
1006*9c5db199SXin Li            try:
1007*9c5db199SXin Li                # The string can be like: env USB_PATH="1-1.1"
1008*9c5db199SXin Li                path = re.search(r'env USB_PATH=\"?([0-9.-]+)\"?',
1009*9c5db199SXin Li                                 init_file_content).group(1)
1010*9c5db199SXin Li            except AttributeError:
1011*9c5db199SXin Li                logging.warning('Failed to read USB path from hammerd file.')
1012*9c5db199SXin Li            else:
1013*9c5db199SXin Li                self._should_run = self._set_base_power_path(path)
1014*9c5db199SXin Li                if not self._should_run:
1015*9c5db199SXin Li                    logging.warning('Device has hammerd file, but base USB'
1016*9c5db199SXin Li                                    ' device not found.')
1017*9c5db199SXin Li
1018*9c5db199SXin Li        if not self._should_run:
1019*9c5db199SXin Li            # Try cros_config.
1020*9c5db199SXin Li            result = utils.run(self._BASE_INIT_CMD, ignore_status=True)
1021*9c5db199SXin Li            if result.exit_status:
1022*9c5db199SXin Li                logging.warning('Command failed: %s', self._BASE_INIT_CMD)
1023*9c5db199SXin Li            else:
1024*9c5db199SXin Li                self._should_run = self._set_base_power_path(result.stdout)
1025*9c5db199SXin Li                if not self._should_run:
1026*9c5db199SXin Li                    logging.warning('cros_config has base info, but base USB'
1027*9c5db199SXin Li                                    ' device not found.')
1028*9c5db199SXin Li
1029*9c5db199SXin Li        if self._should_run:
1030*9c5db199SXin Li            self._base_control_path = os.path.join(self._base_power_path,
1031*9c5db199SXin Li                                                   'control')
1032*9c5db199SXin Li            self._autosuspend_delay_path = os.path.join(self._base_power_path,
1033*9c5db199SXin Li                                                        'autosuspend_delay_ms')
1034*9c5db199SXin Li            logging.debug("base activity simulator will be running.")
1035*9c5db199SXin Li            with open(self._base_control_path, 'r+') as f:
1036*9c5db199SXin Li                self._default_control = f.read()
1037*9c5db199SXin Li                if self._default_control != 'auto':
1038*9c5db199SXin Li                    logging.debug("Putting the base into autosuspend.")
1039*9c5db199SXin Li                    f.write('auto')
1040*9c5db199SXin Li
1041*9c5db199SXin Li            with open(self._autosuspend_delay_path, 'r+') as f:
1042*9c5db199SXin Li                self._default_autosuspend_delay_ms = f.read().rstrip('\n')
1043*9c5db199SXin Li                f.write(str(self._BASE_WAKE_TIME_MS))
1044*9c5db199SXin Li        else:
1045*9c5db199SXin Li            logging.info('No base USB device found, base activity simulator'
1046*9c5db199SXin Li                         ' will NOT be running.')
1047*9c5db199SXin Li
1048*9c5db199SXin Li    def _set_base_power_path(self, usb_path):
1049*9c5db199SXin Li        """Set base power path and check if it exists.
1050*9c5db199SXin Li
1051*9c5db199SXin Li        Args:
1052*9c5db199SXin Li          usb_path: the USB device path under /sys/bus/usb/devices/.
1053*9c5db199SXin Li
1054*9c5db199SXin Li        Returns:
1055*9c5db199SXin Li          True if the base power path exists, or False otherwise.
1056*9c5db199SXin Li        """
1057*9c5db199SXin Li        self._base_power_path = '/sys/bus/usb/devices/%s/power/' % usb_path
1058*9c5db199SXin Li        if not os.path.exists(self._base_power_path):
1059*9c5db199SXin Li            logging.warning('Path not found: %s', self._base_power_path)
1060*9c5db199SXin Li        return os.path.exists(self._base_power_path)
1061*9c5db199SXin Li
1062*9c5db199SXin Li    def wake_base(self, wake_time_ms=_BASE_WAKE_TIME_MS):
1063*9c5db199SXin Li        """Wake up the base to simulate user activity.
1064*9c5db199SXin Li
1065*9c5db199SXin Li        Args:
1066*9c5db199SXin Li          wake_time_ms: time the base should be turned on
1067*9c5db199SXin Li                        (taken out of autosuspend) in milliseconds.
1068*9c5db199SXin Li        """
1069*9c5db199SXin Li        if self._should_run:
1070*9c5db199SXin Li            logging.debug("Taking base out of runtime suspend for %d seconds",
1071*9c5db199SXin Li                          wake_time_ms/1000)
1072*9c5db199SXin Li            with open(self._autosuspend_delay_path, 'r+') as f:
1073*9c5db199SXin Li                f.write(str(wake_time_ms))
1074*9c5db199SXin Li            # Toggling the control will keep the base awake for
1075*9c5db199SXin Li            # the duration specified in the autosuspend_delay_ms file.
1076*9c5db199SXin Li            with open(self._base_control_path, 'w') as f:
1077*9c5db199SXin Li                f.write('on')
1078*9c5db199SXin Li            with open(self._base_control_path, 'w') as f:
1079*9c5db199SXin Li                f.write('auto')
1080*9c5db199SXin Li
1081*9c5db199SXin Li    def restore(self):
1082*9c5db199SXin Li        """Restore the original control and autosuspend delay."""
1083*9c5db199SXin Li        if self._should_run:
1084*9c5db199SXin Li            with open(self._base_control_path, 'w') as f:
1085*9c5db199SXin Li                f.write(self._default_control)
1086*9c5db199SXin Li
1087*9c5db199SXin Li            with open(self._autosuspend_delay_path, 'w') as f:
1088*9c5db199SXin Li                f.write(self._default_autosuspend_delay_ms)
1089