blob: 41ef7b0a39714a7d3d47af6000215b99da864eeb [file] [log] [blame]
Todd Broch3e05cf82012-06-07 14:02:29 -07001# 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.
Po-Hsien Wang8afce4f2017-10-23 16:02:32 -07004import glob
5import logging
6import os
7import re
8import shutil
9import time
Allen Li2c32d6b2017-02-03 15:28:10 -080010from autotest_lib.client.bin import utils
Ravi Chandra Sadinenif719f352018-06-16 14:10:02 -070011from autotest_lib.client.bin.input.input_device import InputDevice
Todd Broch8f9bf602012-11-20 14:50:21 -080012from autotest_lib.client.common_lib import error
Brian Norris8a0bd702016-06-03 18:06:55 -070013from autotest_lib.client.cros import upstart
Todd Broch3e05cf82012-06-07 14:02:29 -070014
Eric Caruso9c30bcd2015-03-04 14:52:59 -080015
16# Possible display power settings. Copied from chromeos::DisplayPowerState
17# in Chrome's dbus service constants.
18DISPLAY_POWER_ALL_ON = 0
19DISPLAY_POWER_ALL_OFF = 1
20DISPLAY_POWER_INTERNAL_OFF_EXTERNAL_ON = 2
21DISPLAY_POWER_INTERNAL_ON_EXTERNAL_OFF = 3
22# for bounds checking
23DISPLAY_POWER_MAX = 4
24
Moja Hsu71cf9512018-02-05 17:09:18 +080025# Retry times for ectool chargecontrol
26ECTOOL_CHARGECONTROL_RETRY_TIMES = 3
27ECTOOL_CHARGECONTROL_TIMEOUT_SECS = 3
28
Eric Caruso9c30bcd2015-03-04 14:52:59 -080029
Todd Broch3e05cf82012-06-07 14:02:29 -070030def get_x86_cpu_arch():
31 """Identify CPU architectural type.
32
33 Intel's processor naming conventions is a mine field of inconsistencies.
34 Armed with that, this method simply tries to identify the architecture of
35 systems we care about.
36
37 TODO(tbroch) grow method to cover processors numbers outlined in:
38 http://www.intel.com/content/www/us/en/processors/processor-numbers.html
39 perhaps returning more information ( brand, generation, features )
40
41 Returns:
42 String, explicitly (Atom, Core, Celeron) or None
43 """
44 cpuinfo = utils.read_file('/proc/cpuinfo')
45
Pratik Vishwakarmafc2b1782018-03-12 13:39:50 +053046 if re.search(r'AMD.*[AE][269]-9[0-9][0-9][0-9].*RADEON.*R[245]', cpuinfo):
Pratik Vishwakarmaccae8a22017-09-22 14:35:16 +053047 return 'Stoney'
Todd Broch3e05cf82012-06-07 14:02:29 -070048 if re.search(r'Intel.*Atom.*[NZ][2-6]', cpuinfo):
49 return 'Atom'
Kevin Cheng89712bc2014-01-28 11:55:20 +080050 if re.search(r'Intel.*Celeron.*N2[89][0-9][0-9]', cpuinfo):
51 return 'Celeron N2000'
Eric Caruso36ae1c52015-06-26 15:19:05 -070052 if re.search(r'Intel.*Celeron.*N3[0-9][0-9][0-9]', cpuinfo):
53 return 'Celeron N3000'
Kees Cook6929c352013-09-12 14:55:16 -070054 if re.search(r'Intel.*Celeron.*[0-9]{3,4}', cpuinfo):
Todd Broch3e05cf82012-06-07 14:02:29 -070055 return 'Celeron'
Jorge Lucangeli Obes9f3bae72018-02-23 11:29:04 -050056 # https://ark.intel.com/products/series/94028/5th-Generation-Intel-Core-M-Processors
57 # https://ark.intel.com/products/series/94025/6th-Generation-Intel-Core-m-Processors
58 # https://ark.intel.com/products/series/95542/7th-Generation-Intel-Core-m-Processors
59 if re.search(r'Intel.*Core.*[mM][357]-[567][Y0-9][0-9][0-9]', cpuinfo):
60 return 'Core M'
Sameer Nandaf69be6b2013-05-17 12:51:27 -070061 if re.search(r'Intel.*Core.*i[357]-[234][0-9][0-9][0-9]', cpuinfo):
Todd Broch3e05cf82012-06-07 14:02:29 -070062 return 'Core'
63
64 logging.info(cpuinfo)
65 return None
Todd Broch0e53e632012-06-08 11:10:26 -070066
67
68def has_rapl_support():
Harry Pan40f7c7a2017-07-31 20:51:54 +080069 """Identify if CPU microarchitecture supports RAPL energy profile.
70
71 TODO(harry.pan): Since Sandy Bridge, all microarchitectures have RAPL
72 in various power domains. With that said, the Silvermont and Airmont
73 support RAPL as well, while the ESU (Energy Status Unit of MSR 606H)
74 are in different multipiler against others, hense not list by far.
Todd Broch0e53e632012-06-08 11:10:26 -070075
76 Returns:
77 Boolean, True if RAPL supported, False otherwise.
78 """
Po-Hsien Wang8afce4f2017-10-23 16:02:32 -070079 rapl_set = set(["Haswell", "Haswell-E", "Broadwell", "Skylake", "Goldmont",
80 "Kaby Lake"])
Harry Pan40f7c7a2017-07-31 20:51:54 +080081 cpu_uarch = utils.get_intel_cpu_uarch()
82 if (cpu_uarch in rapl_set):
Todd Broch0e53e632012-06-08 11:10:26 -070083 return True
Harry Pan40f7c7a2017-07-31 20:51:54 +080084 else:
85 # The cpu_uarch here is either unlisted uarch, or family_model.
86 logging.debug("%s is not in RAPL support collection", cpu_uarch)
Todd Broch0e53e632012-06-08 11:10:26 -070087 return False
Todd Broch8848c0c2012-10-11 16:40:06 -070088
89
Harry Pan71423de2017-07-27 03:36:35 +080090def has_powercap_support():
91 """Identify if OS supports powercap sysfs.
92
93 Returns:
94 Boolean, True if powercap supported, False otherwise.
95 """
96 return os.path.isdir('/sys/devices/virtual/powercap/intel-rapl/')
97
98
Ravi Chandra Sadinenif719f352018-06-16 14:10:02 -070099def has_lid():
100 """
101 Checks whether the device has lid.
102
103 @return: Returns True if the device has a lid, False otherwise.
104 """
105 INPUT_DEVICE_LIST = "/dev/input/event*"
106
107 return any(InputDevice(node).is_lid() for node in
108 glob.glob(INPUT_DEVICE_LIST))
109
110
Eric Caruso9c30bcd2015-03-04 14:52:59 -0800111def _call_dbus_method(destination, path, interface, method_name, args):
112 """Performs a generic dbus method call."""
113 command = ('dbus-send --type=method_call --system '
114 '--dest=%s %s %s.%s %s') % (destination, path, interface,
Po-Hsien Wang8afce4f2017-10-23 16:02:32 -0700115 method_name, args)
Eric Caruso9c30bcd2015-03-04 14:52:59 -0800116 utils.system_output(command)
117
118
Simon Que73db85f2013-01-16 11:09:02 -0800119def call_powerd_dbus_method(method_name, args=''):
120 """
121 Calls a dbus method exposed by powerd.
122
123 Arguments:
Kevin Cheng89712bc2014-01-28 11:55:20 +0800124 @param method_name: name of the dbus method.
125 @param args: string containing args to dbus method call.
Simon Que73db85f2013-01-16 11:09:02 -0800126 """
Eric Caruso9c30bcd2015-03-04 14:52:59 -0800127 _call_dbus_method(destination='org.chromium.PowerManager',
128 path='/org/chromium/PowerManager',
129 interface='org.chromium.PowerManager',
130 method_name=method_name, args=args)
131
Po-Hsien Wang8afce4f2017-10-23 16:02:32 -0700132
Todd Broch830ada42015-09-23 08:44:01 -0700133def get_power_supply():
134 """
135 Determine what type of power supply the host has.
136
137 Copied from server/host/cros_hosts.py
138
139 @returns a string representing this host's power supply.
140 'power:battery' when the device has a battery intended for
141 extended use
142 'power:AC_primary' when the device has a battery not intended
143 for extended use (for moving the machine, etc)
144 'power:AC_only' when the device has no battery at all.
145 """
146 try:
147 psu = utils.system_output('mosys psu type')
148 except Exception:
149 # The psu command for mosys is not included for all platforms. The
150 # assumption is that the device will have a battery if the command
151 # is not found.
152 return 'power:battery'
153
154 psu_str = psu.strip()
155 if psu_str == 'unknown':
156 return None
157
158 return 'power:%s' % psu_str
159
Po-Hsien Wang8afce4f2017-10-23 16:02:32 -0700160
Ravi Chandra Sadineni03fef842017-02-08 14:36:33 -0800161def get_sleep_state():
162 """
163 Returns the current powerd configuration of the sleep state.
164 Can be "freeze" or "mem".
165 """
166 cmd = 'check_powerd_config --suspend_to_idle'
Allen Lidee56ff2017-04-25 12:45:17 -0700167 result = utils.run(cmd, ignore_status=True)
Ravi Chandra Sadineni03fef842017-02-08 14:36:33 -0800168 return 'freeze' if result.exit_status == 0 else 'mem'
169
Po-Hsien Wang8afce4f2017-10-23 16:02:32 -0700170
Todd Broch830ada42015-09-23 08:44:01 -0700171def has_battery():
172 """Determine if DUT has a battery.
173
174 Returns:
175 Boolean, False if known not to have battery, True otherwise.
176 """
177 rv = True
178 power_supply = get_power_supply()
179 if power_supply == 'power:battery':
180 # TODO(tbroch) if/when 'power:battery' param is reliable
181 # remove board type logic. Also remove verbose mosys call.
Todd Brochb71f3672016-07-11 18:12:18 -0700182 _NO_BATTERY_BOARD_TYPE = ['CHROMEBOX', 'CHROMEBIT', 'CHROMEBASE']
Allen Li2c32d6b2017-02-03 15:28:10 -0800183 board_type = utils.get_board_type()
Todd Broch830ada42015-09-23 08:44:01 -0700184 if board_type in _NO_BATTERY_BOARD_TYPE:
185 logging.warn('Do NOT believe type %s has battery. '
186 'See debug for mosys details', board_type)
187 psu = utils.system_output('mosys -vvvv psu type',
188 ignore_status=True)
189 logging.debug(psu)
190 rv = False
191 elif power_supply == 'power:AC_only':
192 rv = False
193
194 return rv
195
Simon Que73db85f2013-01-16 11:09:02 -0800196
Puthikorn Voravootivatb506d6e2018-02-16 10:56:55 -0800197def get_low_battery_shutdown_percent():
198 """Get the percent-based low-battery shutdown threshold.
199
200 Returns:
201 Float, percent-based low-battery shutdown threshold. 0 if error.
202 """
203 ret = 0.0
204 try:
205 command = 'check_powerd_config --low_battery_shutdown_percent'
206 ret = float(utils.run(command).stdout)
207 except error.CmdError:
208 logging.debug("Can't run %s", command)
209 except ValueError:
210 logging.debug("Didn't get number from %s", command)
211
212 return ret
213
214
Puthikorn Voravootivat09c83d72018-08-10 15:58:32 -0700215def has_hammer():
216 """Check whether DUT has hammer device or not.
217
218 Returns:
219 boolean whether device has hammer or not
220 """
221 command = 'grep Hammer /sys/bus/usb/devices/*/product'
222 return utils.run(command, ignore_status=True).exit_status == 0
223
224
Moja Hsu71cf9512018-02-05 17:09:18 +0800225def _charge_control_by_ectool(is_charge):
226 """execute ectool command.
Moja Hsu2434bb22017-11-02 17:52:15 +0800227
228 Args:
229 is_charge: Boolean, True for charging, False for discharging.
230
231 Returns:
232 Boolean, True if the command success, False otherwise.
233 """
234 ec_cmd_discharge = 'ectool chargecontrol discharge'
235 ec_cmd_normal = 'ectool chargecontrol normal'
Moja Hsu71cf9512018-02-05 17:09:18 +0800236 try:
237 if is_charge:
238 utils.run(ec_cmd_normal)
239 else:
240 utils.run(ec_cmd_discharge)
241 except error.CmdError as e:
242 logging.warning('Unable to use ectool: %s', e)
243 return False
Moja Hsu2434bb22017-11-02 17:52:15 +0800244
245 success = utils.wait_for_value(lambda: (
246 is_charge != bool(re.search(r'Flags.*DISCHARGING',
247 utils.run('ectool battery',
248 ignore_status=True).stdout,
249 re.MULTILINE))),
Moja Hsu71cf9512018-02-05 17:09:18 +0800250 expected_value=True, timeout_sec=ECTOOL_CHARGECONTROL_TIMEOUT_SECS)
Moja Hsu2434bb22017-11-02 17:52:15 +0800251 return success
252
253
Moja Hsu71cf9512018-02-05 17:09:18 +0800254def charge_control_by_ectool(is_charge):
255 """Force the battery behavior by the is_charge paremeter.
256
257 Args:
258 is_charge: Boolean, True for charging, False for discharging.
259
260 Returns:
261 Boolean, True if the command success, False otherwise.
262 """
263 for i in xrange(ECTOOL_CHARGECONTROL_RETRY_TIMES):
264 if _charge_control_by_ectool(is_charge):
265 return True
266
267 return False
268
269
Todd Broch8f9bf602012-11-20 14:50:21 -0800270class BacklightException(Exception):
271 """Class for Backlight exceptions."""
272
273
274class Backlight(object):
Todd Broch0fd769b2014-04-10 08:59:59 -0700275 """Class for control of built-in panel backlight.
Todd Broch8f9bf602012-11-20 14:50:21 -0800276
Todd Broch0fd769b2014-04-10 08:59:59 -0700277 Public methods:
278 set_level: Set backlight level to the given brightness.
279 set_percent: Set backlight level to the given brightness percent.
280 set_resume_level: Set backlight level on resume to the given brightness.
281 set_resume_percent: Set backlight level on resume to the given brightness
282 percent.
283 set_default: Set backlight to CrOS default.
284
285 get_level: Get backlight level currently.
286 get_max_level: Get maximum backight level.
287 get_percent: Get backlight percent currently.
288 restore: Restore backlight to initial level when instance created.
289
290 Public attributes:
291 default_brightness_percent: float of default brightness
292
293 Private methods:
294 _try_bl_cmd: run a backlight command.
295
296 Private attributes:
297 _init_level: integer of backlight level when object instantiated.
298 _can_control_bl: boolean determining whether backlight can be controlled
299 or queried
300 """
Todd Broch8f9bf602012-11-20 14:50:21 -0800301 # Default brightness is based on expected average use case.
302 # See http://www.chromium.org/chromium-os/testing/power-testing for more
303 # details.
Po-Hsien Wang8afce4f2017-10-23 16:02:32 -0700304
Todd Broch0fd769b2014-04-10 08:59:59 -0700305 def __init__(self, default_brightness_percent=0):
Todd Broch8f9bf602012-11-20 14:50:21 -0800306 """Constructor.
307
308 attributes:
Todd Broch8f9bf602012-11-20 14:50:21 -0800309 """
Todd Brochde3c9792018-09-05 19:59:01 -0700310 self._init_level = None
Todd Broch02ec5bc2013-09-10 17:42:20 -0700311 self.default_brightness_percent = default_brightness_percent
Todd Broch0fd769b2014-04-10 08:59:59 -0700312
Todd Brochde3c9792018-09-05 19:59:01 -0700313 self._can_control_bl = True
314 try:
315 self._init_level = self.get_level()
316 except error.TestFail:
317 self._can_control_bl = False
318
Todd Broch0fd769b2014-04-10 08:59:59 -0700319 logging.debug("device can_control_bl: %s", self._can_control_bl)
320 if not self._can_control_bl:
321 return
322
Todd Broch02ec5bc2013-09-10 17:42:20 -0700323 if not self.default_brightness_percent:
Puthikorn Voravootivat97302c62017-09-27 10:40:17 -0700324 cmd = \
325 "backlight_tool --get_initial_brightness --lux=150 2>/dev/null"
Todd Broch02ec5bc2013-09-10 17:42:20 -0700326 try:
Todd Broch0fd769b2014-04-10 08:59:59 -0700327 level = float(utils.system_output(cmd).rstrip())
328 self.default_brightness_percent = \
329 (level / self.get_max_level()) * 100
Todd Broch02ec5bc2013-09-10 17:42:20 -0700330 logging.info("Default backlight brightness percent = %f",
331 self.default_brightness_percent)
332 except error.CmdError:
333 self.default_brightness_percent = 40.0
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700334 logging.warning("Unable to determine default backlight "
Ravi Chandra Sadineni85dad302016-04-26 17:17:16 -0700335 "brightness percent. Setting to %f",
336 self.default_brightness_percent)
Todd Broch8f9bf602012-11-20 14:50:21 -0800337
Todd Broch0fd769b2014-04-10 08:59:59 -0700338 def _try_bl_cmd(self, arg_str):
339 """Perform backlight command.
340
341 Args:
342 arg_str: String of additional arguments to backlight command.
343
344 Returns:
345 String output of the backlight command.
346
347 Raises:
348 error.TestFail: if 'cmd' returns non-zero exit status.
349 """
350 if not self._can_control_bl:
351 return 0
352 cmd = 'backlight_tool %s' % (arg_str)
353 logging.debug("backlight_cmd: %s", cmd)
354 try:
355 return utils.system_output(cmd).rstrip()
356 except error.CmdError:
357 raise error.TestFail(cmd)
358
Todd Broch8f9bf602012-11-20 14:50:21 -0800359 def set_level(self, level):
360 """Set backlight level to the given brightness.
Todd Broch0fd769b2014-04-10 08:59:59 -0700361
Todd Broch8f9bf602012-11-20 14:50:21 -0800362 Args:
363 level: integer of brightness to set
364 """
Steve Fung9297d902014-10-08 00:50:01 -0700365 self._try_bl_cmd('--set_brightness=%d' % (level))
Todd Broch8f9bf602012-11-20 14:50:21 -0800366
Todd Broch8f9bf602012-11-20 14:50:21 -0800367 def set_percent(self, percent):
368 """Set backlight level to the given brightness percent.
369
370 Args:
371 percent: float between 0 and 100
372 """
Steve Fung9297d902014-10-08 00:50:01 -0700373 self._try_bl_cmd('--set_brightness_percent=%f' % (percent))
Todd Broch8f9bf602012-11-20 14:50:21 -0800374
Simon Que73db85f2013-01-16 11:09:02 -0800375 def set_resume_level(self, level):
376 """Set backlight level on resume to the given brightness.
Todd Broch0fd769b2014-04-10 08:59:59 -0700377
Simon Que73db85f2013-01-16 11:09:02 -0800378 Args:
379 level: integer of brightness to set
Simon Que73db85f2013-01-16 11:09:02 -0800380 """
Steve Fung9297d902014-10-08 00:50:01 -0700381 self._try_bl_cmd('--set_resume_brightness=%d' % (level))
Simon Que73db85f2013-01-16 11:09:02 -0800382
Simon Que73db85f2013-01-16 11:09:02 -0800383 def set_resume_percent(self, percent):
384 """Set backlight level on resume to the given brightness percent.
385
386 Args:
387 percent: float between 0 and 100
Simon Que73db85f2013-01-16 11:09:02 -0800388 """
Steve Fung9297d902014-10-08 00:50:01 -0700389 self._try_bl_cmd('--set_resume_brightness_percent=%f' % (percent))
Simon Que73db85f2013-01-16 11:09:02 -0800390
Todd Broch8f9bf602012-11-20 14:50:21 -0800391 def set_default(self):
392 """Set backlight to CrOS default.
393 """
Todd Brochaf5de982012-11-27 18:27:03 -0800394 self.set_percent(self.default_brightness_percent)
Todd Broch8f9bf602012-11-20 14:50:21 -0800395
Todd Broch8f9bf602012-11-20 14:50:21 -0800396 def get_level(self):
397 """Get backlight level currently.
398
Todd Broch0fd769b2014-04-10 08:59:59 -0700399 Returns integer of current backlight level or zero if no backlight
400 exists.
Todd Broch8f9bf602012-11-20 14:50:21 -0800401 """
Todd Broch0fd769b2014-04-10 08:59:59 -0700402 return int(self._try_bl_cmd('--get_brightness'))
Todd Broch8f9bf602012-11-20 14:50:21 -0800403
Todd Broch8f9bf602012-11-20 14:50:21 -0800404 def get_max_level(self):
405 """Get maximum backight level.
406
Todd Broch0fd769b2014-04-10 08:59:59 -0700407 Returns integer of maximum backlight level or zero if no backlight
408 exists.
Todd Broch8f9bf602012-11-20 14:50:21 -0800409 """
Todd Broch0fd769b2014-04-10 08:59:59 -0700410 return int(self._try_bl_cmd('--get_max_brightness'))
411
Todd Broch0fd769b2014-04-10 08:59:59 -0700412 def get_percent(self):
413 """Get backlight percent currently.
414
415 Returns float of current backlight percent or zero if no backlight
416 exists
417 """
418 return float(self._try_bl_cmd('--get_brightness_percent'))
Todd Broch8f9bf602012-11-20 14:50:21 -0800419
Todd Broch8f9bf602012-11-20 14:50:21 -0800420 def restore(self):
421 """Restore backlight to initial level when instance created."""
Todd Brochaf966ae2018-09-12 15:55:07 -0700422 if self._init_level is not None:
423 self.set_level(self._init_level)
Todd Broch8f9bf602012-11-20 14:50:21 -0800424
425
Todd Broch8848c0c2012-10-11 16:40:06 -0700426class KbdBacklightException(Exception):
427 """Class for KbdBacklight exceptions."""
428
429
430class KbdBacklight(object):
431 """Class for control of keyboard backlight.
432
433 Example code:
434 kblight = power_utils.KbdBacklight()
435 kblight.set(10)
Ravi Chandra Sadineni85dad302016-04-26 17:17:16 -0700436 print "kblight % is %.f" % kblight.get_percent()
Todd Broch8848c0c2012-10-11 16:40:06 -0700437
438 Public methods:
Ravi Chandra Sadineni85dad302016-04-26 17:17:16 -0700439 set_percent: Sets the keyboard backlight to a percent.
440 get_percent: Get current keyboard backlight percentage.
441 set_level: Sets the keyboard backlight to a level.
442 get_default_level: Get default keyboard backlight brightness level
Todd Broch8848c0c2012-10-11 16:40:06 -0700443
444 Private attributes:
Ravi Chandra Sadineni85dad302016-04-26 17:17:16 -0700445 _default_backlight_level: keboard backlight level set by default
Todd Broch8848c0c2012-10-11 16:40:06 -0700446
Todd Broch8848c0c2012-10-11 16:40:06 -0700447 """
Todd Broch8848c0c2012-10-11 16:40:06 -0700448
Ravi Chandra Sadineni85dad302016-04-26 17:17:16 -0700449 def __init__(self):
450 cmd = 'check_powerd_config --keyboard_backlight'
Allen Li5ed7e632017-02-03 16:31:33 -0800451 result = utils.run(cmd, ignore_status=True)
Ravi Chandra Sadineni85dad302016-04-26 17:17:16 -0700452 if result.exit_status:
453 raise KbdBacklightException('Keyboard backlight support' +
454 'is not enabled')
Harry Pan85d39682017-09-14 00:25:56 +0800455 try:
Daniel Erat3aa91e62017-09-23 07:45:30 -0700456 cmd = \
457 "backlight_tool --keyboard --get_initial_brightness 2>/dev/null"
Harry Pan85d39682017-09-14 00:25:56 +0800458 self._default_backlight_level = int(
459 utils.system_output(cmd).rstrip())
460 logging.info("Default keyboard backlight brightness level = %d",
461 self._default_backlight_level)
462 except Exception:
463 raise KbdBacklightException('Keyboard backlight is malfunctioning')
Todd Broch8848c0c2012-10-11 16:40:06 -0700464
Ravi Chandra Sadineni85dad302016-04-26 17:17:16 -0700465 def get_percent(self):
466 """Get current keyboard brightness setting percentage.
Todd Broch8848c0c2012-10-11 16:40:06 -0700467
468 Returns:
Ravi Chandra Sadineni85dad302016-04-26 17:17:16 -0700469 float, percentage of keyboard brightness in the range [0.0, 100.0].
Todd Broch8848c0c2012-10-11 16:40:06 -0700470 """
Ravi Chandra Sadineni85dad302016-04-26 17:17:16 -0700471 cmd = 'backlight_tool --keyboard --get_brightness_percent'
472 return float(utils.system_output(cmd).strip())
Todd Broch8848c0c2012-10-11 16:40:06 -0700473
Ravi Chandra Sadineni85dad302016-04-26 17:17:16 -0700474 def get_default_level(self):
475 """
476 Returns the default backlight level.
Todd Broch8848c0c2012-10-11 16:40:06 -0700477
478 Returns:
Ravi Chandra Sadineni85dad302016-04-26 17:17:16 -0700479 The default keyboard backlight level.
Todd Broch8848c0c2012-10-11 16:40:06 -0700480 """
Ravi Chandra Sadineni85dad302016-04-26 17:17:16 -0700481 return self._default_backlight_level
Todd Broch8848c0c2012-10-11 16:40:06 -0700482
Ravi Chandra Sadineni85dad302016-04-26 17:17:16 -0700483 def set_percent(self, percent):
Todd Broch8848c0c2012-10-11 16:40:06 -0700484 """Set keyboard backlight percent.
485
486 Args:
Ravi Chandra Sadineni85dad302016-04-26 17:17:16 -0700487 @param percent: float value in the range [0.0, 100.0]
488 to set keyboard backlight to.
Todd Broch8848c0c2012-10-11 16:40:06 -0700489 """
Ravi Chandra Sadineni85dad302016-04-26 17:17:16 -0700490 cmd = ('backlight_tool --keyboard --set_brightness_percent=' +
Po-Hsien Wang8afce4f2017-10-23 16:02:32 -0700491 str(percent))
Ravi Chandra Sadineni85dad302016-04-26 17:17:16 -0700492 utils.system(cmd)
493
Ravi Chandra Sadineni85dad302016-04-26 17:17:16 -0700494 def set_level(self, level):
495 """
496 Set keyboard backlight to given level.
497 Args:
498 @param level: level to set keyboard backlight to.
499 """
500 cmd = 'backlight_tool --keyboard --set_brightness=' + str(level)
Todd Broch8848c0c2012-10-11 16:40:06 -0700501 utils.system(cmd)
Simon Que73db85f2013-01-16 11:09:02 -0800502
503
504class BacklightController(object):
505 """Class to simulate control of backlight via keyboard or Chrome UI.
506
507 Public methods:
508 increase_brightness: Increase backlight by one adjustment step.
509 decrease_brightness: Decrease backlight by one adjustment step.
510 set_brightness_to_max: Increase backlight to max by calling
511 increase_brightness()
512 set_brightness_to_min: Decrease backlight to min or zero by calling
513 decrease_brightness()
514
515 Private attributes:
516 _max_num_steps: maximum number of backlight adjustment steps between 0 and
517 max brightness.
Simon Que73db85f2013-01-16 11:09:02 -0800518 """
519
520 def __init__(self):
521 self._max_num_steps = 16
522
Simon Que73db85f2013-01-16 11:09:02 -0800523 def decrease_brightness(self, allow_off=False):
524 """
525 Decrease brightness by one step, as if the user pressed the brightness
526 down key or button.
527
528 Arguments
Kevin Cheng89712bc2014-01-28 11:55:20 +0800529 @param allow_off: Boolean flag indicating whether the brightness can be
Simon Que73db85f2013-01-16 11:09:02 -0800530 reduced to zero.
531 Set to true to simulate brightness down key.
532 set to false to simulate Chrome UI brightness down button.
533 """
534 call_powerd_dbus_method('DecreaseScreenBrightness',
Po-Hsien Wang8afce4f2017-10-23 16:02:32 -0700535 'boolean:%s' %
536 ('true' if allow_off else 'false'))
Simon Que73db85f2013-01-16 11:09:02 -0800537
538 def increase_brightness(self):
539 """
540 Increase brightness by one step, as if the user pressed the brightness
541 up key or button.
542 """
543 call_powerd_dbus_method('IncreaseScreenBrightness')
544
Simon Que73db85f2013-01-16 11:09:02 -0800545 def set_brightness_to_max(self):
546 """
547 Increases the brightness using powerd until the brightness reaches the
548 maximum value. Returns when it reaches the maximum number of brightness
549 adjustments
550 """
551 num_steps_taken = 0
552 while num_steps_taken < self._max_num_steps:
553 self.increase_brightness()
554 num_steps_taken += 1
555
Simon Que73db85f2013-01-16 11:09:02 -0800556 def set_brightness_to_min(self, allow_off=False):
557 """
558 Decreases the brightness using powerd until the brightness reaches the
559 minimum value (zero or the minimum nonzero value). Returns when it
560 reaches the maximum number of brightness adjustments.
561
562 Arguments
Kevin Cheng89712bc2014-01-28 11:55:20 +0800563 @param allow_off: Boolean flag indicating whether the brightness can be
Simon Que73db85f2013-01-16 11:09:02 -0800564 reduced to zero.
565 Set to true to simulate brightness down key.
566 set to false to simulate Chrome UI brightness down button.
567 """
568 num_steps_taken = 0
569 while num_steps_taken < self._max_num_steps:
570 self.decrease_brightness(allow_off)
571 num_steps_taken += 1
Kees Cook6e7ec542013-01-28 16:18:40 -0800572
573
Eric Caruso9c30bcd2015-03-04 14:52:59 -0800574class DisplayException(Exception):
575 """Class for Display exceptions."""
576
577
578def set_display_power(power_val):
579 """Function to control screens via Chrome.
580
581 Possible arguments:
582 DISPLAY_POWER_ALL_ON,
583 DISPLAY_POWER_ALL_OFF,
584 DISPLAY_POWER_INTERNAL_OFF_EXTERNAL_ON,
585 DISPLAY_POWER_INTERNAL_ON_EXTENRAL_OFF
586 """
587 if (not isinstance(power_val, int)
588 or power_val < DISPLAY_POWER_ALL_ON
589 or power_val >= DISPLAY_POWER_MAX):
590 raise DisplayException('Invalid display power setting: %d' % power_val)
Lann Martin54c47bf2017-07-24 16:54:53 -0600591 _call_dbus_method(destination='org.chromium.DisplayService',
592 path='/org/chromium/DisplayService',
Todd Broch9bc12352018-08-21 18:08:01 -0700593 interface='org.chromium.DisplayServiceInterface',
Lann Martin54c47bf2017-07-24 16:54:53 -0600594 method_name='SetPower',
595 args='int32:%d' % power_val)
Eric Caruso9c30bcd2015-03-04 14:52:59 -0800596
597
Julius Werner4cf56292013-02-06 15:55:20 -0800598class PowerPrefChanger(object):
599 """
600 Class to temporarily change powerd prefs. Construct with a dict of
601 pref_name/value pairs (e.g. {'disable_idle_suspend':0}). Destructor (or
602 reboot) will restore old prefs automatically."""
603
604 _PREFDIR = '/var/lib/power_manager'
605 _TEMPDIR = '/tmp/autotest_powerd_prefs'
606
607 def __init__(self, prefs):
608 shutil.copytree(self._PREFDIR, self._TEMPDIR)
609 for name, value in prefs.iteritems():
610 utils.write_one_line('%s/%s' % (self._TEMPDIR, name), value)
611 utils.system('mount --bind %s %s' % (self._TEMPDIR, self._PREFDIR))
Brian Norris8a0bd702016-06-03 18:06:55 -0700612 upstart.restart_job('powerd')
Julius Werner4cf56292013-02-06 15:55:20 -0800613
Julius Wernera1f68342013-02-04 13:27:51 -0800614 def finalize(self):
Kevin Cheng89712bc2014-01-28 11:55:20 +0800615 """finalize"""
Julius Wernera1f68342013-02-04 13:27:51 -0800616 if os.path.exists(self._TEMPDIR):
617 utils.system('umount %s' % self._PREFDIR, ignore_status=True)
618 shutil.rmtree(self._TEMPDIR)
Brian Norris8a0bd702016-06-03 18:06:55 -0700619 upstart.restart_job('powerd')
Julius Wernera1f68342013-02-04 13:27:51 -0800620
Julius Werner4cf56292013-02-06 15:55:20 -0800621 def __del__(self):
Julius Wernera1f68342013-02-04 13:27:51 -0800622 self.finalize()
Julius Werner4cf56292013-02-06 15:55:20 -0800623
624
Kees Cook6e7ec542013-01-28 16:18:40 -0800625class Registers(object):
626 """Class to examine PCI and MSR registers."""
627
628 def __init__(self):
629 self._cpu_id = 0
630 self._rdmsr_cmd = 'iotools rdmsr'
631 self._mmio_read32_cmd = 'iotools mmio_read32'
Kees Cookcc9ea492013-02-04 17:36:45 -0800632 self._rcba = 0xfed1c000
Kees Cook6e7ec542013-01-28 16:18:40 -0800633
634 self._pci_read32_cmd = 'iotools pci_read32'
635 self._mch_bar = None
636 self._dmi_bar = None
637
638 def _init_mch_bar(self):
639 if self._mch_bar != None:
640 return
641 # MCHBAR is at offset 0x48 of B/D/F 0/0/0
642 cmd = '%s 0 0 0 0x48' % (self._pci_read32_cmd)
643 self._mch_bar = int(utils.system_output(cmd), 16) & 0xfffffffe
644 logging.debug('MCH BAR is %s', hex(self._mch_bar))
645
646 def _init_dmi_bar(self):
647 if self._dmi_bar != None:
648 return
649 # DMIBAR is at offset 0x68 of B/D/F 0/0/0
650 cmd = '%s 0 0 0 0x68' % (self._pci_read32_cmd)
651 self._dmi_bar = int(utils.system_output(cmd), 16) & 0xfffffffe
652 logging.debug('DMI BAR is %s', hex(self._dmi_bar))
653
654 def _read_msr(self, register):
655 cmd = '%s %d %s' % (self._rdmsr_cmd, self._cpu_id, register)
656 return int(utils.system_output(cmd), 16)
657
658 def _read_mmio_read32(self, address):
Kees Cookcc9ea492013-02-04 17:36:45 -0800659 cmd = '%s 0x%x' % (self._mmio_read32_cmd, address)
Kees Cook6e7ec542013-01-28 16:18:40 -0800660 return int(utils.system_output(cmd), 16)
661
662 def _read_dmi_bar(self, offset):
663 self._init_dmi_bar()
664 return self._read_mmio_read32(self._dmi_bar + int(offset, 16))
665
666 def _read_mch_bar(self, offset):
667 self._init_mch_bar()
668 return self._read_mmio_read32(self._mch_bar + int(offset, 16))
669
Kees Cookcc9ea492013-02-04 17:36:45 -0800670 def _read_rcba(self, offset):
671 return self._read_mmio_read32(self._rcba + int(offset, 16))
672
Kees Cook6e7ec542013-01-28 16:18:40 -0800673 def _shift_mask_match(self, reg_name, value, match):
674 expr = match[1]
675 bits = match[0].split(':')
676 operator = match[2] if len(match) == 3 else '=='
677 hi_bit = int(bits[0])
678 if len(bits) == 2:
679 lo_bit = int(bits[1])
680 else:
681 lo_bit = int(bits[0])
682
683 value >>= lo_bit
684 mask = (1 << (hi_bit - lo_bit + 1)) - 1
685 value &= mask
686
687 good = eval("%d %s %d" % (value, operator, expr))
688 if not good:
689 logging.error('FAILED: %s bits: %s value: %s mask: %s expr: %s ' +
690 'operator: %s', reg_name, bits, hex(value), mask,
691 expr, operator)
692 return good
693
694 def _verify_registers(self, reg_name, read_fn, match_list):
695 errors = 0
696 for k, v in match_list.iteritems():
697 r = read_fn(k)
698 for item in v:
699 good = self._shift_mask_match(reg_name, r, item)
700 if not good:
701 errors += 1
702 logging.error('Error(%d), %s: reg = %s val = %s match = %s',
703 errors, reg_name, k, hex(r), v)
Kees Cookcc9ea492013-02-04 17:36:45 -0800704 else:
705 logging.debug('ok, %s: reg = %s val = %s match = %s',
706 reg_name, k, hex(r), v)
Kees Cook6e7ec542013-01-28 16:18:40 -0800707 return errors
708
709 def verify_msr(self, match_list):
Kevin Cheng89712bc2014-01-28 11:55:20 +0800710 """
711 Verify MSR
712
713 @param match_list: match list
714 """
Kees Cook6e7ec542013-01-28 16:18:40 -0800715 errors = 0
716 for cpu_id in xrange(0, max(utils.count_cpus(), 1)):
717 self._cpu_id = cpu_id
718 errors += self._verify_registers('msr', self._read_msr, match_list)
719 return errors
720
721 def verify_dmi(self, match_list):
Kevin Cheng89712bc2014-01-28 11:55:20 +0800722 """
723 Verify DMI
724
725 @param match_list: match list
726 """
Kees Cook6e7ec542013-01-28 16:18:40 -0800727 return self._verify_registers('dmi', self._read_dmi_bar, match_list)
728
729 def verify_mch(self, match_list):
Kevin Cheng89712bc2014-01-28 11:55:20 +0800730 """
731 Verify MCH
732
733 @param match_list: match list
734 """
Kees Cook6e7ec542013-01-28 16:18:40 -0800735 return self._verify_registers('mch', self._read_mch_bar, match_list)
Kees Cookcc9ea492013-02-04 17:36:45 -0800736
737 def verify_rcba(self, match_list):
Kevin Cheng89712bc2014-01-28 11:55:20 +0800738 """
739 Verify RCBA
740
741 @param match_list: match list
742 """
Kees Cookcc9ea492013-02-04 17:36:45 -0800743 return self._verify_registers('rcba', self._read_rcba, match_list)
Todd Broch22802962013-05-17 15:17:00 -0700744
745
746class USBDevicePower(object):
747 """Class for USB device related power information.
748
749 Public Methods:
750 autosuspend: Return boolean whether USB autosuspend is enabled or False
751 if not or unable to determine
752
753 Public attributes:
754 vid: string of USB Vendor ID
755 pid: string of USB Product ID
756 whitelisted: Boolean if USB device is whitelisted for USB auto-suspend
757
758 Private attributes:
759 path: string to path of the USB devices in sysfs ( /sys/bus/usb/... )
760
761 TODO(tbroch): consider converting to use of pyusb although not clear its
762 beneficial if it doesn't parse power/control
763 """
Po-Hsien Wang8afce4f2017-10-23 16:02:32 -0700764
Todd Broch22802962013-05-17 15:17:00 -0700765 def __init__(self, vid, pid, whitelisted, path):
766 self.vid = vid
767 self.pid = pid
768 self.whitelisted = whitelisted
769 self._path = path
770
Todd Broch22802962013-05-17 15:17:00 -0700771 def autosuspend(self):
772 """Determine current value of USB autosuspend for device."""
773 control_file = os.path.join(self._path, 'control')
774 if not os.path.exists(control_file):
775 logging.info('USB: power control file not found for %s', dir)
776 return False
777
778 out = utils.read_one_line(control_file)
779 logging.debug('USB: control set to %s for %s', out, control_file)
780 return (out == 'auto')
781
782
783class USBPower(object):
784 """Class to expose USB related power functionality.
785
786 Initially that includes the policy around USB auto-suspend and our
787 whitelisting of devices that are internal to CrOS system.
788
789 Example code:
790 usbdev_power = power_utils.USBPower()
791 for device in usbdev_power.devices
792 if device.is_whitelisted()
793 ...
794
795 Public attributes:
796 devices: list of USBDevicePower instances
797
798 Private functions:
799 _is_whitelisted: Returns Boolean if USB device is whitelisted for USB
800 auto-suspend
801 _load_whitelist: Reads whitelist and stores int _whitelist attribute
802
803 Private attributes:
804 _wlist_file: path to laptop-mode-tools (LMT) USB autosuspend
805 conf file.
806 _wlist_vname: string name of LMT USB autosuspend whitelist
807 variable
808 _whitelisted: list of USB device vid:pid that are whitelisted.
809 May be regular expressions. See LMT for details.
810 """
Po-Hsien Wang8afce4f2017-10-23 16:02:32 -0700811
Todd Broch22802962013-05-17 15:17:00 -0700812 def __init__(self):
813 self._wlist_file = \
814 '/etc/laptop-mode/conf.d/board-specific/usb-autosuspend.conf'
815 self._wlist_vname = '$AUTOSUSPEND_USBID_WHITELIST'
816 self._whitelisted = None
817 self.devices = []
818
Todd Broch22802962013-05-17 15:17:00 -0700819 def _load_whitelist(self):
820 """Load USB device whitelist for enabling USB autosuspend
821
822 CrOS whitelists only internal USB devices to enter USB auto-suspend mode
823 via laptop-mode tools.
824 """
825 cmd = "source %s && echo %s" % (self._wlist_file,
826 self._wlist_vname)
827 out = utils.system_output(cmd, ignore_status=True)
828 logging.debug('USB whitelist = %s', out)
829 self._whitelisted = out.split()
830
Todd Broch22802962013-05-17 15:17:00 -0700831 def _is_whitelisted(self, vid, pid):
832 """Check to see if USB device vid:pid is whitelisted.
833
834 Args:
835 vid: string of USB vendor ID
836 pid: string of USB product ID
837
838 Returns:
839 True if vid:pid in whitelist file else False
840 """
841 if self._whitelisted is None:
842 self._load_whitelist()
843
844 match_str = "%s:%s" % (vid, pid)
845 for re_str in self._whitelisted:
846 if re.match(re_str, match_str):
847 return True
848 return False
849
Todd Broch22802962013-05-17 15:17:00 -0700850 def query_devices(self):
851 """."""
852 dirs_path = '/sys/bus/usb/devices/*/power'
853 dirs = glob.glob(dirs_path)
854 if not dirs:
855 logging.info('USB power path not found')
856 return 1
857
858 for dirpath in dirs:
859 vid_path = os.path.join(dirpath, '..', 'idVendor')
860 pid_path = os.path.join(dirpath, '..', 'idProduct')
861 if not os.path.exists(vid_path):
862 logging.debug("No vid for USB @ %s", vid_path)
863 continue
864 vid = utils.read_one_line(vid_path)
865 pid = utils.read_one_line(pid_path)
866 whitelisted = self._is_whitelisted(vid, pid)
867 self.devices.append(USBDevicePower(vid, pid, whitelisted, dirpath))
Todd Broch67cdc802016-06-02 19:10:41 -0700868
869
870class DisplayPanelSelfRefresh(object):
Derek Basehore20f0a592017-03-14 23:22:39 -0700871 """Class for control and monitoring of display's PSR."""
872 _PSR_STATUS_FILE_X86 = '/sys/kernel/debug/dri/0/i915_edp_psr_status'
873 _PSR_STATUS_FILE_ARM = '/sys/kernel/debug/dri/*/psr_active_ms'
Todd Broch67cdc802016-06-02 19:10:41 -0700874
875 def __init__(self, init_time=time.time()):
876 """Initializer.
877
878 @Public attributes:
879 supported: Boolean of whether PSR is supported or not
880
881 @Private attributes:
882 _init_time: time when PSR class was instantiated.
883 _init_counter: integer of initial value of residency counter.
884 _keyvals: dictionary of keyvals
885 """
Derek Basehore7a1030b2017-04-02 23:47:30 -0700886 self._psr_path = ''
Derek Basehore20f0a592017-03-14 23:22:39 -0700887 if os.path.exists(self._PSR_STATUS_FILE_X86):
888 self._psr_path = self._PSR_STATUS_FILE_X86
889 self._psr_parse_prefix = 'Performance_Counter:'
890 else:
891 paths = glob.glob(self._PSR_STATUS_FILE_ARM)
892 if paths:
893 # Should be only one PSR file
894 self._psr_path = paths[0]
895 self._psr_parse_prefix = ''
896
Todd Broch67cdc802016-06-02 19:10:41 -0700897 self._init_time = init_time
898 self._init_counter = self._get_counter()
899 self._keyvals = {}
900 self.supported = (self._init_counter != None)
901
902 def _get_counter(self):
903 """Get the current value of the system PSR counter.
904
905 This counts the number of milliseconds the system has resided in PSR.
906
907 @returns: amount of time PSR has been active since boot in ms, or None if
908 the performance counter can't be read.
909 """
910 try:
Derek Basehore20f0a592017-03-14 23:22:39 -0700911 count = utils.get_field(utils.read_file(self._psr_path),
912 0, linestart=self._psr_parse_prefix)
Todd Broch67cdc802016-06-02 19:10:41 -0700913 except IOError:
914 logging.info("Can't find or read PSR status file")
915 return None
916
917 logging.debug("PSR performance counter: %s", count)
918 return int(count) if count else None
919
920 def _calc_residency(self):
921 """Calculate the PSR residency."""
922 if not self.supported:
923 return 0
924
925 tdelta = time.time() - self._init_time
926 cdelta = self._get_counter() - self._init_counter
927 return cdelta / (10 * tdelta)
928
929 def refresh(self):
930 """Refresh PSR related data."""
931 self._keyvals['percent_psr_residency'] = self._calc_residency()
932
933 def get_keyvals(self):
934 """Get keyvals associated with PSR data.
935
936 @returns dictionary of keyvals
937 """
938 return self._keyvals
Ruben Rodriguez Buchillon7d68bc02017-09-26 17:34:48 -0700939
940
941class BaseActivityException(Exception):
942 """Class for base activity simulation exceptions."""
943
944
945class BaseActivitySimulator(object):
946 """Class to simulate wake activity on the normally autosuspended base."""
947
948 # Note on naming: throughout this class, the word base is used to mean the
949 # base of a detachable (keyboard, touchpad, etc).
950
951 # file defines where to look for detachable base.
952 # TODO(coconutruben): check when next wave of detachables come out if this
953 # structure still holds, or if we need to replace it by querying input
954 # devices.
955 _BASE_INIT_FILE = '/etc/init/hammerd.override'
956 _BASE_WAKE_TIME_MS = 10000
957
958 def __init__(self):
959 """Initializer
960
961 Let the BaseActivitySimulator bootstrap itself by detecting if
962 the board is a detachable, and ensuring the base path exists.
963 Sets the base to autosuspend, and the autosuspend delay to be
964 at most _BASE_WAKE_TIME_MS.
965
966 """
967 self._should_run = os.path.exists(self._BASE_INIT_FILE)
968 base_power_path = ''
969 if self._should_run:
970 with open(self._BASE_INIT_FILE, 'r') as init_file:
971 init_file_content = init_file.read()
972 try:
973 bus = re.search('env USB_BUS=([0-9]+)',
974 init_file_content).group(1)
975 port = re.search('env USB_PORT=([0-9]+)',
976 init_file_content).group(1)
977 except AttributeError:
978 raise BaseActivityException("Failed to read usb bus "
979 "or port from hammerd file.")
980 base_power_path = ('/sys/bus/usb/devices/%s-%s/power/'
981 % (bus, port))
982 if not os.path.exists(base_power_path):
983 logging.warn("Device has hammerd file, but base usb device"
984 " not found.")
985 self._should_run = False
986 if self._should_run:
987 self._base_control_path = os.path.join(base_power_path,
988 'control')
989 self._autosuspend_delay_path = os.path.join(base_power_path,
990 'autosuspend_delay_ms')
991 logging.debug("base activity simulator will be running.")
992 with open(self._base_control_path, 'r+') as f:
993 self._default_control = f.read()
994 if self._default_control != 'auto':
995 logging.debug("Putting the base into autosuspend.")
996 f.write('auto')
997
998 with open(self._autosuspend_delay_path, 'r+') as f:
999 self._default_autosuspend_delay_ms = f.read().rstrip('\n')
1000 f.write(str(self._BASE_WAKE_TIME_MS))
1001
1002 def wake_base(self, wake_time_ms=_BASE_WAKE_TIME_MS):
1003 """Wake up the base to simulate user activity.
1004
1005 Args:
1006 wake_time_ms: time the base should be turned on
1007 (taken out of autosuspend) in milliseconds.
1008 """
1009 if self._should_run:
1010 logging.debug("Taking base out of runtime suspend for %d seconds",
1011 wake_time_ms/1000)
1012 with open(self._autosuspend_delay_path, 'r+') as f:
1013 f.write(str(wake_time_ms))
1014 # Toggling the control will keep the base awake for
1015 # the duration specified in the autosuspend_delay_ms file.
1016 with open(self._base_control_path, 'w') as f:
1017 f.write('on')
1018 with open(self._base_control_path, 'w') as f:
1019 f.write('auto')
1020
1021 def restore(self):
1022 """Restore the original control and autosuspend delay."""
1023 if self._should_run:
1024 with open(self._base_control_path, 'w') as f:
1025 f.write(self._default_control)
1026
1027 with open(self._autosuspend_delay_path, 'w') as f:
1028 f.write(self._default_autosuspend_delay_ms)