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