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