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. 4 import glob 5 import logging 6 import os 7 import re 8 import shutil 9 import time 10 from autotest_lib.client.bin import utils 11 from autotest_lib.client.common_lib import error 12 from autotest_lib.client.cros import upstart 13 14 15 # Possible display power settings. Copied from chromeos::DisplayPowerState 16 # in Chrome's dbus service constants. 17 DISPLAY_POWER_ALL_ON = 0 18 DISPLAY_POWER_ALL_OFF = 1 19 DISPLAY_POWER_INTERNAL_OFF_EXTERNAL_ON = 2 20 DISPLAY_POWER_INTERNAL_ON_EXTERNAL_OFF = 3 21 # for bounds checking 22 DISPLAY_POWER_MAX = 4 23 24 # Retry times for ectool chargecontrol 25 ECTOOL_CHARGECONTROL_RETRY_TIMES = 3 26 ECTOOL_CHARGECONTROL_TIMEOUT_SECS = 3 27 28 29 def 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.*A6-92[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 67 def 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"]) 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 89 def 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 98 def _call_dbus_method(destination, path, interface, method_name, args): 99 """Performs a generic dbus method call.""" 100 command = ('dbus-send --type=method_call --system ' 101 '--dest=%s %s %s.%s %s') % (destination, path, interface, 102 method_name, args) 103 utils.system_output(command) 104 105 106 def call_powerd_dbus_method(method_name, args=''): 107 """ 108 Calls a dbus method exposed by powerd. 109 110 Arguments: 111 @param method_name: name of the dbus method. 112 @param args: string containing args to dbus method call. 113 """ 114 _call_dbus_method(destination='org.chromium.PowerManager', 115 path='/org/chromium/PowerManager', 116 interface='org.chromium.PowerManager', 117 method_name=method_name, args=args) 118 119 120 def get_power_supply(): 121 """ 122 Determine what type of power supply the host has. 123 124 Copied from server/host/cros_hosts.py 125 126 @returns a string representing this host's power supply. 127 'power:battery' when the device has a battery intended for 128 extended use 129 'power:AC_primary' when the device has a battery not intended 130 for extended use (for moving the machine, etc) 131 'power:AC_only' when the device has no battery at all. 132 """ 133 try: 134 psu = utils.system_output('mosys psu type') 135 except Exception: 136 # The psu command for mosys is not included for all platforms. The 137 # assumption is that the device will have a battery if the command 138 # is not found. 139 return 'power:battery' 140 141 psu_str = psu.strip() 142 if psu_str == 'unknown': 143 return None 144 145 return 'power:%s' % psu_str 146 147 148 def get_sleep_state(): 149 """ 150 Returns the current powerd configuration of the sleep state. 151 Can be "freeze" or "mem". 152 """ 153 cmd = 'check_powerd_config --suspend_to_idle' 154 result = utils.run(cmd, ignore_status=True) 155 return 'freeze' if result.exit_status == 0 else 'mem' 156 157 158 def has_battery(): 159 """Determine if DUT has a battery. 160 161 Returns: 162 Boolean, False if known not to have battery, True otherwise. 163 """ 164 rv = True 165 power_supply = get_power_supply() 166 if power_supply == 'power:battery': 167 # TODO(tbroch) if/when 'power:battery' param is reliable 168 # remove board type logic. Also remove verbose mosys call. 169 _NO_BATTERY_BOARD_TYPE = ['CHROMEBOX', 'CHROMEBIT', 'CHROMEBASE'] 170 board_type = utils.get_board_type() 171 if board_type in _NO_BATTERY_BOARD_TYPE: 172 logging.warn('Do NOT believe type %s has battery. ' 173 'See debug for mosys details', board_type) 174 psu = utils.system_output('mosys -vvvv psu type', 175 ignore_status=True) 176 logging.debug(psu) 177 rv = False 178 elif power_supply == 'power:AC_only': 179 rv = False 180 181 return rv 182 183 184 def get_low_battery_shutdown_percent(): 185 """Get the percent-based low-battery shutdown threshold. 186 187 Returns: 188 Float, percent-based low-battery shutdown threshold. 0 if error. 189 """ 190 ret = 0.0 191 try: 192 command = 'check_powerd_config --low_battery_shutdown_percent' 193 ret = float(utils.run(command).stdout) 194 except error.CmdError: 195 logging.debug("Can't run %s", command) 196 except ValueError: 197 logging.debug("Didn't get number from %s", command) 198 199 return ret 200 201 202 def _charge_control_by_ectool(is_charge): 203 """execute ectool command. 204 205 Args: 206 is_charge: Boolean, True for charging, False for discharging. 207 208 Returns: 209 Boolean, True if the command success, False otherwise. 210 """ 211 ec_cmd_discharge = 'ectool chargecontrol discharge' 212 ec_cmd_normal = 'ectool chargecontrol normal' 213 try: 214 if is_charge: 215 utils.run(ec_cmd_normal) 216 else: 217 utils.run(ec_cmd_discharge) 218 except error.CmdError as e: 219 logging.warning('Unable to use ectool: %s', e) 220 return False 221 222 success = utils.wait_for_value(lambda: ( 223 is_charge != bool(re.search(r'Flags.*DISCHARGING', 224 utils.run('ectool battery', 225 ignore_status=True).stdout, 226 re.MULTILINE))), 227 expected_value=True, timeout_sec=ECTOOL_CHARGECONTROL_TIMEOUT_SECS) 228 return success 229 230 231 def charge_control_by_ectool(is_charge): 232 """Force the battery behavior by the is_charge paremeter. 233 234 Args: 235 is_charge: Boolean, True for charging, False for discharging. 236 237 Returns: 238 Boolean, True if the command success, False otherwise. 239 """ 240 for i in xrange(ECTOOL_CHARGECONTROL_RETRY_TIMES): 241 if _charge_control_by_ectool(is_charge): 242 return True 243 244 return False 245 246 247 class BacklightException(Exception): 248 """Class for Backlight exceptions.""" 249 250 251 class Backlight(object): 252 """Class for control of built-in panel backlight. 253 254 Public methods: 255 set_level: Set backlight level to the given brightness. 256 set_percent: Set backlight level to the given brightness percent. 257 set_resume_level: Set backlight level on resume to the given brightness. 258 set_resume_percent: Set backlight level on resume to the given brightness 259 percent. 260 set_default: Set backlight to CrOS default. 261 262 get_level: Get backlight level currently. 263 get_max_level: Get maximum backight level. 264 get_percent: Get backlight percent currently. 265 restore: Restore backlight to initial level when instance created. 266 267 Public attributes: 268 default_brightness_percent: float of default brightness 269 270 Private methods: 271 _try_bl_cmd: run a backlight command. 272 273 Private attributes: 274 _init_level: integer of backlight level when object instantiated. 275 _can_control_bl: boolean determining whether backlight can be controlled 276 or queried 277 """ 278 # Default brightness is based on expected average use case. 279 # See http://www.chromium.org/chromium-os/testing/power-testing for more 280 # details. 281 282 def __init__(self, default_brightness_percent=0): 283 """Constructor. 284 285 attributes: 286 """ 287 cmd = "mosys psu type" 288 result = utils.system_output(cmd, ignore_status=True).strip() 289 self._can_control_bl = not result == "AC_only" 290 291 self._init_level = self.get_level() 292 self.default_brightness_percent = default_brightness_percent 293 294 logging.debug("device can_control_bl: %s", self._can_control_bl) 295 if not self._can_control_bl: 296 return 297 298 if not self.default_brightness_percent: 299 cmd = \ 300 "backlight_tool --get_initial_brightness --lux=150 2>/dev/null" 301 try: 302 level = float(utils.system_output(cmd).rstrip()) 303 self.default_brightness_percent = \ 304 (level / self.get_max_level()) * 100 305 logging.info("Default backlight brightness percent = %f", 306 self.default_brightness_percent) 307 except error.CmdError: 308 self.default_brightness_percent = 40.0 309 logging.warning("Unable to determine default backlight " 310 "brightness percent. Setting to %f", 311 self.default_brightness_percent) 312 313 def _try_bl_cmd(self, arg_str): 314 """Perform backlight command. 315 316 Args: 317 arg_str: String of additional arguments to backlight command. 318 319 Returns: 320 String output of the backlight command. 321 322 Raises: 323 error.TestFail: if 'cmd' returns non-zero exit status. 324 """ 325 if not self._can_control_bl: 326 return 0 327 cmd = 'backlight_tool %s' % (arg_str) 328 logging.debug("backlight_cmd: %s", cmd) 329 try: 330 return utils.system_output(cmd).rstrip() 331 except error.CmdError: 332 raise error.TestFail(cmd) 333 334 def set_level(self, level): 335 """Set backlight level to the given brightness. 336 337 Args: 338 level: integer of brightness to set 339 """ 340 self._try_bl_cmd('--set_brightness=%d' % (level)) 341 342 def set_percent(self, percent): 343 """Set backlight level to the given brightness percent. 344 345 Args: 346 percent: float between 0 and 100 347 """ 348 self._try_bl_cmd('--set_brightness_percent=%f' % (percent)) 349 350 def set_resume_level(self, level): 351 """Set backlight level on resume to the given brightness. 352 353 Args: 354 level: integer of brightness to set 355 """ 356 self._try_bl_cmd('--set_resume_brightness=%d' % (level)) 357 358 def set_resume_percent(self, percent): 359 """Set backlight level on resume to the given brightness percent. 360 361 Args: 362 percent: float between 0 and 100 363 """ 364 self._try_bl_cmd('--set_resume_brightness_percent=%f' % (percent)) 365 366 def set_default(self): 367 """Set backlight to CrOS default. 368 """ 369 self.set_percent(self.default_brightness_percent) 370 371 def get_level(self): 372 """Get backlight level currently. 373 374 Returns integer of current backlight level or zero if no backlight 375 exists. 376 """ 377 return int(self._try_bl_cmd('--get_brightness')) 378 379 def get_max_level(self): 380 """Get maximum backight level. 381 382 Returns integer of maximum backlight level or zero if no backlight 383 exists. 384 """ 385 return int(self._try_bl_cmd('--get_max_brightness')) 386 387 def get_percent(self): 388 """Get backlight percent currently. 389 390 Returns float of current backlight percent or zero if no backlight 391 exists 392 """ 393 return float(self._try_bl_cmd('--get_brightness_percent')) 394 395 def restore(self): 396 """Restore backlight to initial level when instance created.""" 397 self.set_level(self._init_level) 398 399 400 class KbdBacklightException(Exception): 401 """Class for KbdBacklight exceptions.""" 402 403 404 class KbdBacklight(object): 405 """Class for control of keyboard backlight. 406 407 Example code: 408 kblight = power_utils.KbdBacklight() 409 kblight.set(10) 410 print "kblight % is %.f" % kblight.get_percent() 411 412 Public methods: 413 set_percent: Sets the keyboard backlight to a percent. 414 get_percent: Get current keyboard backlight percentage. 415 set_level: Sets the keyboard backlight to a level. 416 get_default_level: Get default keyboard backlight brightness level 417 418 Private attributes: 419 _default_backlight_level: keboard backlight level set by default 420 421 """ 422 423 def __init__(self): 424 cmd = 'check_powerd_config --keyboard_backlight' 425 result = utils.run(cmd, ignore_status=True) 426 if result.exit_status: 427 raise KbdBacklightException('Keyboard backlight support' + 428 'is not enabled') 429 try: 430 cmd = \ 431 "backlight_tool --keyboard --get_initial_brightness 2>/dev/null" 432 self._default_backlight_level = int( 433 utils.system_output(cmd).rstrip()) 434 logging.info("Default keyboard backlight brightness level = %d", 435 self._default_backlight_level) 436 except Exception: 437 raise KbdBacklightException('Keyboard backlight is malfunctioning') 438 439 def get_percent(self): 440 """Get current keyboard brightness setting percentage. 441 442 Returns: 443 float, percentage of keyboard brightness in the range [0.0, 100.0]. 444 """ 445 cmd = 'backlight_tool --keyboard --get_brightness_percent' 446 return float(utils.system_output(cmd).strip()) 447 448 def get_default_level(self): 449 """ 450 Returns the default backlight level. 451 452 Returns: 453 The default keyboard backlight level. 454 """ 455 return self._default_backlight_level 456 457 def set_percent(self, percent): 458 """Set keyboard backlight percent. 459 460 Args: 461 @param percent: float value in the range [0.0, 100.0] 462 to set keyboard backlight to. 463 """ 464 cmd = ('backlight_tool --keyboard --set_brightness_percent=' + 465 str(percent)) 466 utils.system(cmd) 467 468 def set_level(self, level): 469 """ 470 Set keyboard backlight to given level. 471 Args: 472 @param level: level to set keyboard backlight to. 473 """ 474 cmd = 'backlight_tool --keyboard --set_brightness=' + str(level) 475 utils.system(cmd) 476 477 478 class BacklightController(object): 479 """Class to simulate control of backlight via keyboard or Chrome UI. 480 481 Public methods: 482 increase_brightness: Increase backlight by one adjustment step. 483 decrease_brightness: Decrease backlight by one adjustment step. 484 set_brightness_to_max: Increase backlight to max by calling 485 increase_brightness() 486 set_brightness_to_min: Decrease backlight to min or zero by calling 487 decrease_brightness() 488 489 Private attributes: 490 _max_num_steps: maximum number of backlight adjustment steps between 0 and 491 max brightness. 492 """ 493 494 def __init__(self): 495 self._max_num_steps = 16 496 497 def decrease_brightness(self, allow_off=False): 498 """ 499 Decrease brightness by one step, as if the user pressed the brightness 500 down key or button. 501 502 Arguments 503 @param allow_off: Boolean flag indicating whether the brightness can be 504 reduced to zero. 505 Set to true to simulate brightness down key. 506 set to false to simulate Chrome UI brightness down button. 507 """ 508 call_powerd_dbus_method('DecreaseScreenBrightness', 509 'boolean:%s' % 510 ('true' if allow_off else 'false')) 511 512 def increase_brightness(self): 513 """ 514 Increase brightness by one step, as if the user pressed the brightness 515 up key or button. 516 """ 517 call_powerd_dbus_method('IncreaseScreenBrightness') 518 519 def set_brightness_to_max(self): 520 """ 521 Increases the brightness using powerd until the brightness reaches the 522 maximum value. Returns when it reaches the maximum number of brightness 523 adjustments 524 """ 525 num_steps_taken = 0 526 while num_steps_taken < self._max_num_steps: 527 self.increase_brightness() 528 num_steps_taken += 1 529 530 def set_brightness_to_min(self, allow_off=False): 531 """ 532 Decreases the brightness using powerd until the brightness reaches the 533 minimum value (zero or the minimum nonzero value). Returns when it 534 reaches the maximum number of brightness adjustments. 535 536 Arguments 537 @param allow_off: Boolean flag indicating whether the brightness can be 538 reduced to zero. 539 Set to true to simulate brightness down key. 540 set to false to simulate Chrome UI brightness down button. 541 """ 542 num_steps_taken = 0 543 while num_steps_taken < self._max_num_steps: 544 self.decrease_brightness(allow_off) 545 num_steps_taken += 1 546 547 548 class DisplayException(Exception): 549 """Class for Display exceptions.""" 550 551 552 def set_display_power(power_val): 553 """Function to control screens via Chrome. 554 555 Possible arguments: 556 DISPLAY_POWER_ALL_ON, 557 DISPLAY_POWER_ALL_OFF, 558 DISPLAY_POWER_INTERNAL_OFF_EXTERNAL_ON, 559 DISPLAY_POWER_INTERNAL_ON_EXTENRAL_OFF 560 """ 561 if (not isinstance(power_val, int) 562 or power_val < DISPLAY_POWER_ALL_ON 563 or power_val >= DISPLAY_POWER_MAX): 564 raise DisplayException('Invalid display power setting: %d' % power_val) 565 _call_dbus_method(destination='org.chromium.DisplayService', 566 path='/org/chromium/DisplayService', 567 interface='org.chomium.DisplayServiceInterface', 568 method_name='SetPower', 569 args='int32:%d' % power_val) 570 571 572 class PowerPrefChanger(object): 573 """ 574 Class to temporarily change powerd prefs. Construct with a dict of 575 pref_name/value pairs (e.g. {'disable_idle_suspend':0}). Destructor (or 576 reboot) will restore old prefs automatically.""" 577 578 _PREFDIR = '/var/lib/power_manager' 579 _TEMPDIR = '/tmp/autotest_powerd_prefs' 580 581 def __init__(self, prefs): 582 shutil.copytree(self._PREFDIR, self._TEMPDIR) 583 for name, value in prefs.iteritems(): 584 utils.write_one_line('%s/%s' % (self._TEMPDIR, name), value) 585 utils.system('mount --bind %s %s' % (self._TEMPDIR, self._PREFDIR)) 586 upstart.restart_job('powerd') 587 588 def finalize(self): 589 """finalize""" 590 if os.path.exists(self._TEMPDIR): 591 utils.system('umount %s' % self._PREFDIR, ignore_status=True) 592 shutil.rmtree(self._TEMPDIR) 593 upstart.restart_job('powerd') 594 595 def __del__(self): 596 self.finalize() 597 598 599 class Registers(object): 600 """Class to examine PCI and MSR registers.""" 601 602 def __init__(self): 603 self._cpu_id = 0 604 self._rdmsr_cmd = 'iotools rdmsr' 605 self._mmio_read32_cmd = 'iotools mmio_read32' 606 self._rcba = 0xfed1c000 607 608 self._pci_read32_cmd = 'iotools pci_read32' 609 self._mch_bar = None 610 self._dmi_bar = None 611 612 def _init_mch_bar(self): 613 if self._mch_bar != None: 614 return 615 # MCHBAR is at offset 0x48 of B/D/F 0/0/0 616 cmd = '%s 0 0 0 0x48' % (self._pci_read32_cmd) 617 self._mch_bar = int(utils.system_output(cmd), 16) & 0xfffffffe 618 logging.debug('MCH BAR is %s', hex(self._mch_bar)) 619 620 def _init_dmi_bar(self): 621 if self._dmi_bar != None: 622 return 623 # DMIBAR is at offset 0x68 of B/D/F 0/0/0 624 cmd = '%s 0 0 0 0x68' % (self._pci_read32_cmd) 625 self._dmi_bar = int(utils.system_output(cmd), 16) & 0xfffffffe 626 logging.debug('DMI BAR is %s', hex(self._dmi_bar)) 627 628 def _read_msr(self, register): 629 cmd = '%s %d %s' % (self._rdmsr_cmd, self._cpu_id, register) 630 return int(utils.system_output(cmd), 16) 631 632 def _read_mmio_read32(self, address): 633 cmd = '%s 0x%x' % (self._mmio_read32_cmd, address) 634 return int(utils.system_output(cmd), 16) 635 636 def _read_dmi_bar(self, offset): 637 self._init_dmi_bar() 638 return self._read_mmio_read32(self._dmi_bar + int(offset, 16)) 639 640 def _read_mch_bar(self, offset): 641 self._init_mch_bar() 642 return self._read_mmio_read32(self._mch_bar + int(offset, 16)) 643 644 def _read_rcba(self, offset): 645 return self._read_mmio_read32(self._rcba + int(offset, 16)) 646 647 def _shift_mask_match(self, reg_name, value, match): 648 expr = match[1] 649 bits = match[0].split(':') 650 operator = match[2] if len(match) == 3 else '==' 651 hi_bit = int(bits[0]) 652 if len(bits) == 2: 653 lo_bit = int(bits[1]) 654 else: 655 lo_bit = int(bits[0]) 656 657 value >>= lo_bit 658 mask = (1 << (hi_bit - lo_bit + 1)) - 1 659 value &= mask 660 661 good = eval("%d %s %d" % (value, operator, expr)) 662 if not good: 663 logging.error('FAILED: %s bits: %s value: %s mask: %s expr: %s ' + 664 'operator: %s', reg_name, bits, hex(value), mask, 665 expr, operator) 666 return good 667 668 def _verify_registers(self, reg_name, read_fn, match_list): 669 errors = 0 670 for k, v in match_list.iteritems(): 671 r = read_fn(k) 672 for item in v: 673 good = self._shift_mask_match(reg_name, r, item) 674 if not good: 675 errors += 1 676 logging.error('Error(%d), %s: reg = %s val = %s match = %s', 677 errors, reg_name, k, hex(r), v) 678 else: 679 logging.debug('ok, %s: reg = %s val = %s match = %s', 680 reg_name, k, hex(r), v) 681 return errors 682 683 def verify_msr(self, match_list): 684 """ 685 Verify MSR 686 687 @param match_list: match list 688 """ 689 errors = 0 690 for cpu_id in xrange(0, max(utils.count_cpus(), 1)): 691 self._cpu_id = cpu_id 692 errors += self._verify_registers('msr', self._read_msr, match_list) 693 return errors 694 695 def verify_dmi(self, match_list): 696 """ 697 Verify DMI 698 699 @param match_list: match list 700 """ 701 return self._verify_registers('dmi', self._read_dmi_bar, match_list) 702 703 def verify_mch(self, match_list): 704 """ 705 Verify MCH 706 707 @param match_list: match list 708 """ 709 return self._verify_registers('mch', self._read_mch_bar, match_list) 710 711 def verify_rcba(self, match_list): 712 """ 713 Verify RCBA 714 715 @param match_list: match list 716 """ 717 return self._verify_registers('rcba', self._read_rcba, match_list) 718 719 720 class USBDevicePower(object): 721 """Class for USB device related power information. 722 723 Public Methods: 724 autosuspend: Return boolean whether USB autosuspend is enabled or False 725 if not or unable to determine 726 727 Public attributes: 728 vid: string of USB Vendor ID 729 pid: string of USB Product ID 730 whitelisted: Boolean if USB device is whitelisted for USB auto-suspend 731 732 Private attributes: 733 path: string to path of the USB devices in sysfs ( /sys/bus/usb/... ) 734 735 TODO(tbroch): consider converting to use of pyusb although not clear its 736 beneficial if it doesn't parse power/control 737 """ 738 739 def __init__(self, vid, pid, whitelisted, path): 740 self.vid = vid 741 self.pid = pid 742 self.whitelisted = whitelisted 743 self._path = path 744 745 def autosuspend(self): 746 """Determine current value of USB autosuspend for device.""" 747 control_file = os.path.join(self._path, 'control') 748 if not os.path.exists(control_file): 749 logging.info('USB: power control file not found for %s', dir) 750 return False 751 752 out = utils.read_one_line(control_file) 753 logging.debug('USB: control set to %s for %s', out, control_file) 754 return (out == 'auto') 755 756 757 class USBPower(object): 758 """Class to expose USB related power functionality. 759 760 Initially that includes the policy around USB auto-suspend and our 761 whitelisting of devices that are internal to CrOS system. 762 763 Example code: 764 usbdev_power = power_utils.USBPower() 765 for device in usbdev_power.devices 766 if device.is_whitelisted() 767 ... 768 769 Public attributes: 770 devices: list of USBDevicePower instances 771 772 Private functions: 773 _is_whitelisted: Returns Boolean if USB device is whitelisted for USB 774 auto-suspend 775 _load_whitelist: Reads whitelist and stores int _whitelist attribute 776 777 Private attributes: 778 _wlist_file: path to laptop-mode-tools (LMT) USB autosuspend 779 conf file. 780 _wlist_vname: string name of LMT USB autosuspend whitelist 781 variable 782 _whitelisted: list of USB device vid:pid that are whitelisted. 783 May be regular expressions. See LMT for details. 784 """ 785 786 def __init__(self): 787 self._wlist_file = \ 788 '/etc/laptop-mode/conf.d/board-specific/usb-autosuspend.conf' 789 self._wlist_vname = '$AUTOSUSPEND_USBID_WHITELIST' 790 self._whitelisted = None 791 self.devices = [] 792 793 def _load_whitelist(self): 794 """Load USB device whitelist for enabling USB autosuspend 795 796 CrOS whitelists only internal USB devices to enter USB auto-suspend mode 797 via laptop-mode tools. 798 """ 799 cmd = "source %s && echo %s" % (self._wlist_file, 800 self._wlist_vname) 801 out = utils.system_output(cmd, ignore_status=True) 802 logging.debug('USB whitelist = %s', out) 803 self._whitelisted = out.split() 804 805 def _is_whitelisted(self, vid, pid): 806 """Check to see if USB device vid:pid is whitelisted. 807 808 Args: 809 vid: string of USB vendor ID 810 pid: string of USB product ID 811 812 Returns: 813 True if vid:pid in whitelist file else False 814 """ 815 if self._whitelisted is None: 816 self._load_whitelist() 817 818 match_str = "%s:%s" % (vid, pid) 819 for re_str in self._whitelisted: 820 if re.match(re_str, match_str): 821 return True 822 return False 823 824 def query_devices(self): 825 """.""" 826 dirs_path = '/sys/bus/usb/devices/*/power' 827 dirs = glob.glob(dirs_path) 828 if not dirs: 829 logging.info('USB power path not found') 830 return 1 831 832 for dirpath in dirs: 833 vid_path = os.path.join(dirpath, '..', 'idVendor') 834 pid_path = os.path.join(dirpath, '..', 'idProduct') 835 if not os.path.exists(vid_path): 836 logging.debug("No vid for USB @ %s", vid_path) 837 continue 838 vid = utils.read_one_line(vid_path) 839 pid = utils.read_one_line(pid_path) 840 whitelisted = self._is_whitelisted(vid, pid) 841 self.devices.append(USBDevicePower(vid, pid, whitelisted, dirpath)) 842 843 844 class DisplayPanelSelfRefresh(object): 845 """Class for control and monitoring of display's PSR.""" 846 _PSR_STATUS_FILE_X86 = '/sys/kernel/debug/dri/0/i915_edp_psr_status' 847 _PSR_STATUS_FILE_ARM = '/sys/kernel/debug/dri/*/psr_active_ms' 848 849 def __init__(self, init_time=time.time()): 850 """Initializer. 851 852 @Public attributes: 853 supported: Boolean of whether PSR is supported or not 854 855 @Private attributes: 856 _init_time: time when PSR class was instantiated. 857 _init_counter: integer of initial value of residency counter. 858 _keyvals: dictionary of keyvals 859 """ 860 self._psr_path = '' 861 if os.path.exists(self._PSR_STATUS_FILE_X86): 862 self._psr_path = self._PSR_STATUS_FILE_X86 863 self._psr_parse_prefix = 'Performance_Counter:' 864 else: 865 paths = glob.glob(self._PSR_STATUS_FILE_ARM) 866 if paths: 867 # Should be only one PSR file 868 self._psr_path = paths[0] 869 self._psr_parse_prefix = '' 870 871 self._init_time = init_time 872 self._init_counter = self._get_counter() 873 self._keyvals = {} 874 self.supported = (self._init_counter != None) 875 876 def _get_counter(self): 877 """Get the current value of the system PSR counter. 878 879 This counts the number of milliseconds the system has resided in PSR. 880 881 @returns: amount of time PSR has been active since boot in ms, or None if 882 the performance counter can't be read. 883 """ 884 try: 885 count = utils.get_field(utils.read_file(self._psr_path), 886 0, linestart=self._psr_parse_prefix) 887 except IOError: 888 logging.info("Can't find or read PSR status file") 889 return None 890 891 logging.debug("PSR performance counter: %s", count) 892 return int(count) if count else None 893 894 def _calc_residency(self): 895 """Calculate the PSR residency.""" 896 if not self.supported: 897 return 0 898 899 tdelta = time.time() - self._init_time 900 cdelta = self._get_counter() - self._init_counter 901 return cdelta / (10 * tdelta) 902 903 def refresh(self): 904 """Refresh PSR related data.""" 905 self._keyvals['percent_psr_residency'] = self._calc_residency() 906 907 def get_keyvals(self): 908 """Get keyvals associated with PSR data. 909 910 @returns dictionary of keyvals 911 """ 912 return self._keyvals 913