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