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