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