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