Home | History | Annotate | Download | only in power
      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