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.bin.input.input_device import InputDevice
     12 from autotest_lib.client.common_lib import error
     13 from autotest_lib.client.cros import upstart
     14 
     15 
     16 # Possible display power settings. Copied from chromeos::DisplayPowerState
     17 # in Chrome's dbus service constants.
     18 DISPLAY_POWER_ALL_ON = 0
     19 DISPLAY_POWER_ALL_OFF = 1
     20 DISPLAY_POWER_INTERNAL_OFF_EXTERNAL_ON = 2
     21 DISPLAY_POWER_INTERNAL_ON_EXTERNAL_OFF = 3
     22 # for bounds checking
     23 DISPLAY_POWER_MAX = 4
     24 
     25 # Retry times for ectool chargecontrol
     26 ECTOOL_CHARGECONTROL_RETRY_TIMES = 3
     27 ECTOOL_CHARGECONTROL_TIMEOUT_SECS = 3
     28 
     29 
     30 def 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 
     68 def 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 
     90 def 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 
     99 def 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 
    111 def _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 
    119 def 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 
    133 def 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 
    161 def 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 
    171 def 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 
    197 def 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 
    215 def 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 
    225 def _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 
    254 def 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 
    270 def 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 
    294 class BacklightException(Exception):
    295     """Class for Backlight exceptions."""
    296 
    297 
    298 class 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 
    466 class KbdBacklightException(Exception):
    467     """Class for KbdBacklight exceptions."""
    468 
    469 
    470 class 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 
    544 class 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 
    614 class DisplayException(Exception):
    615     """Class for Display exceptions."""
    616 
    617 
    618 def 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 
    638 class 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 
    665 class 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 
    786 class 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 
    823 class 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 
    910 class 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 
    981 class BaseActivityException(Exception):
    982     """Class for base activity simulation exceptions."""
    983 
    984 
    985 class 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