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