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