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