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