blob: ebff40df832554959a59bf47a4d06ee92012fdc2 [file] [log] [blame]
# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import commands, glob, logging, os, re, time
from autotest_lib.client.bin import test, utils
from autotest_lib.client.common_lib import error
from autotest_lib.client.cros import power_rapl
from autotest_lib.client.cros import power_status
from autotest_lib.client.cros import power_utils
from autotest_lib.client.cros import cros_logging
# Specify registers to check. The format needs to be:
# register offset : ('bits', 'expression')
DMI_BAR_CHECKS = {
'Atom': {
'0x88': [('1:0', 3)],
'0x200': [('27:26', 0)],
'0x210': [('2:0', 1), ('15:8', 1)],
'0xc28': [('5:1', 7)],
'0xc2e': [('5', 1)],
'0xc30': [('11', 0), ('10:8', 4)],
'0xc34': [('9:4', 7), ('0', 1)],
},
'Non-Atom': {
# http://www.intel.com/content/dam/doc/datasheet/2nd-gen-core-family-mobile-vol-2-datasheet.pdf
# PCIE DMI Link Control Register
# -- [1:0] : ASPM State 0=Disable, 1=L0s, 2=reserved, 3=L0s&L1
'0x88': [('1:0', 3)],
},
}
# http://www.intel.com/content/dam/www/public/us/en/documents/datasheets/2nd-gen-core-family-mobile-vol-1-datasheet.pdf
# PM_PDWN_Config_
# -- [12] : Global power-down (GLPDN). 1 == global, 0 == per rank
# -- [11:8] : Power-down mode. 0->0x7. Higher is lower power
# -- [7:0] : Power-down idle timer. Lower is better. Minimum
# recommended is 0xf
MCH_PM_PDWN_CONFIG = [('12', 0), ('11:8', 0x6, '>='), ('7:0', 0x40, '<='),
('7:0', 0xf, '>=')]
MCH_BAR_CHECKS = {
'Atom': {},
'Non-Atom': {
# mmc0
'0x40b0': MCH_PM_PDWN_CONFIG,
# mmc1
'0x44b0': MCH_PM_PDWN_CONFIG,
# single mmc
'0x4cb0': MCH_PM_PDWN_CONFIG,
},
}
MSR_CHECKS = {
'Atom': {
'0xe2': [('7', 0), ('2:0', 4)],
'0x198': [('28:24', 6)],
'0x1a0': [('33:32', 3), ('26:25', 3), ('16', 1)],
},
'Non-Atom': {
# IA32_ENERGY_PERF_BIAS[3:0] -- 0 == hi-perf, 6 balanced, 15 powersave
'0x1b0': [('3:0', 6)],
},
}
# Give an ASPM exception for these PCI devices. ID is taken from lspci -n.
ASPM_EXCEPTED_DEVICES = {
'Atom': [
# Intel 82801G HDA Controller
'8086:27d8'
],
'Non-Atom': [
# Intel HDA Controller
'8086:1c20',
'8086:1e20'
],
}
GFX_CHECKS = {
'Non-Atom': {'i915_enable_rc6': -1, 'i915_enable_fbc': 1, 'powersave': 1,
'semaphores': 1, 'lvds_downclock': 1}
}
# max & min are in Watts. Device should presumably be idle.
RAPL_CHECKS = {
'Non-Atom': {'pkg': {'max': 5.0, 'min': 1.0},
'pp0': {'max': 1.2, 'min': 0.001},
'pp1': {'max': 1.0, 'min': 0.000}}
}
SUBTESTS = ['dmi', 'mch', 'msr', 'pcie_aspm', 'wifi', 'usb', 'storage',
'audio', 'filesystem', 'graphics', 'rapl']
class power_x86Settings(test.test):
version = 1
def initialize(self):
"""
Private attributes:
_usb_wlist_file: path to laptop-mode-tools (LMT) USB autosuspend
conf file.
_usb_wlist_vname: string name of LMT USB autosuspend whitelist
variable
_usb_whitelist: list of USB device vid:pid that are whitelisted.
May be regular expressions. See LMT for details.
"""
self._usb_wlist_file = \
'/etc/laptop-mode/conf.d/board-specific/usb-autosuspend.conf'
self._usb_wlist_vname = '$AUTOSUSPEND_USBID_WHITELIST'
self._usb_whitelist = None
def run_once(self):
cpu_arch = power_utils.get_x86_cpu_arch()
if not cpu_arch:
raise error.TestNAError('Unsupported CPU')
self._cpu_type = 'Atom'
if cpu_arch is not 'Atom':
self._cpu_type = 'Non-Atom'
self._registers = power_utils.Registers()
status = power_status.get_status()
if status.linepower[0].online:
logging.info('AC Power is online')
self._on_ac = True
else:
logging.info('AC Power is offline')
self._on_ac = False
failures = ''
for testname in SUBTESTS:
logging.info("SUBTEST = %s", testname)
func = getattr(self, "_verify_%s_power_settings" % testname)
fail_count = func()
if fail_count:
failures += '%s_failures(%d) ' % (testname, fail_count)
if failures:
raise error.TestFail(failures)
def _verify_wifi_power_settings(self):
if self._on_ac:
expected_state = 'off'
else:
expected_state = 'on'
iwconfig_out = utils.system_output('iwconfig 2>&1', retain_output=True)
match = re.search(r'Power Management:(.*)', iwconfig_out)
if match and match.group(1) == expected_state:
return 0
logging.info(iwconfig_out)
return 1
def _verify_storage_power_settings(self):
if self._on_ac:
return 0
expected_state = 'min_power'
dirs_path = '/sys/class/scsi_host/host*'
dirs = glob.glob(dirs_path)
if not dirs:
logging.info('scsi_host paths not found')
return 1
for dirpath in dirs:
link_policy_file = os.path.join(dirpath,
'link_power_management_policy')
if not os.path.exists(link_policy_file):
logging.debug('path does not exist: %s', link_policy_file)
continue
out = utils.read_one_line(link_policy_file)
logging.debug('storage: path set to %s for %s',
out, link_policy_file)
if out == expected_state:
return 0
return 1
def _load_usb_device_whitelist(self):
"""Load USB device whitelist for enabling USB autosuspend
CrOS whitelists only internal USB devices to enter USB auto-suspend mode
via laptop-mode tools.
"""
cmd = "source %s && echo %s" % (self._usb_wlist_file,
self._usb_wlist_vname)
out = utils.system_output(cmd, ignore_status=True)
logging.debug('USB whitelist = %s', out)
self._usb_whitelist = out.split()
def _usb_device_is_whitelisted(self, vid, pid):
"""Check to see if USB vid:pid is whitelisted.
Args:
vid: string of USB vendor ID
pid: string of USB product ID
Returns:
True if vid:pid in whitelist file else False
"""
if self._usb_whitelist is None:
self._load_usb_device_whitelist()
match_str = "%s:%s" % (vid, pid)
for re_str in self._usb_whitelist:
if re.match(re_str, match_str):
return True
return False
def _verify_usb_power_settings(self):
if self._on_ac:
expected_state = 'on'
else:
expected_state = 'auto'
dirs_path = '/sys/bus/usb/devices/*/power'
dirs = glob.glob(dirs_path)
if not dirs:
logging.info('USB power path not found')
return 1
errors = 0
for dirpath in dirs:
level_file = os.path.join(dirpath, 'level')
if not os.path.exists(level_file):
logging.info('USB: power level file not found for %s', dir)
continue
vid = utils.read_one_line(os.path.join(dirpath, '..', 'idVendor'))
pid = utils.read_one_line(os.path.join(dirpath, '..', 'idProduct'))
whitelisted = self._usb_device_is_whitelisted(vid, pid)
if not whitelisted:
logging.info('USB: %s:%s @ %s is NOT whitelisted. It should '
'be an externally connected device', vid, pid,
dirpath)
out = utils.read_one_line(level_file)
logging.debug('USB: %s:%s path set to %s for %s',
vid, pid, out, level_file)
if out != expected_state and whitelisted and not self._on_ac:
errors += 1
logging.error("Error(%d), %s == %s, but expected %s", errors,
level_file, out, expected_state)
return errors
def _verify_audio_power_settings(self):
path = '/sys/module/snd_hda_intel/parameters/power_save'
out = utils.read_one_line(path)
logging.debug('Audio: %s = %s', path, out)
power_save_timeout = int(out)
# Make sure that power_save timeout parameter is zero if on AC.
if self._on_ac:
if power_save_timeout == 0:
return 0
else:
logging.debug('Audio: On AC power but power_save = %d', \
power_save_timeout)
return 1
# Make sure that power_save timeout parameter is non-zero if on battery.
elif power_save_timeout > 0:
return 0
logging.debug('Audio: On battery power but power_save = %d', \
power_save_timeout)
return 1
def _verify_filesystem_power_settings(self):
mount_output = commands.getoutput('mount | fgrep commit=').split('\n')
if len(mount_output) == 0:
logging.debug('No file system entries with commit intervals found.')
return 1
errors = 0
# Parse for 'commit' param
for line in mount_output:
try:
commit = int(re.search(r'(commit=)([0-9]*)', line).group(2))
except:
errors += 1
logging.error('Error(%d), reading commit value from \'%s\'',
errors, line)
continue
# Check for the correct commit interval.
if commit != 600:
errors += 1
logging.error('Error(%d), incorrect commit interval %d', errors,
commit)
return errors
def _verify_lvds_downclock_mode_added(self):
"""Checks the kernel log for a message that an LVDS downclock mode has
been added.
This test is specific to alex & lumpy, since they use the i915 driver
(which has downclocking ability) and use the same LCD. This LCD is
special, in that it supports a downclocked refresh rate, but doesn't
advertise it in the EDID.
To counteract this, I added a quirk in drm to add a downclocked mode to
the panel. Unfortunately, upstream doesn't want this patch, and we have
to carry it locally. The quirk patch was dropped inadvertently from
chromeos-3.4, so this test ensures we don't regress again.
I plan on writing an upstream friendly patch sometime in the near
future, at which point I'll revert my drm hack and this test.
Returns:
0 if no errors, otherwise the number of errors that occurred.
"""
# Skip all boards except lumpy and alex
cmd = 'cat /etc/lsb-release | grep CHROMEOS_RELEASE_BOARD'
output = utils.system_output(cmd)
if 'lumpy' not in output and 'alex' not in output:
return 0
# Get the downclock message from the logs
reader = cros_logging.LogReader()
reader.set_start_by_reboot(-1)
if not reader.can_find('Adding LVDS downclock mode'):
logging.error('Error, LVDS downclock quirk not applied!')
return 1
return 0
def _verify_graphics_power_settings(self):
"""Verify that power-saving for graphics are configured properly.
Returns:
0 if no errors, otherwise the number of errors that occurred.
"""
errors = 0
if self._cpu_type in GFX_CHECKS:
checks = GFX_CHECKS[self._cpu_type]
for param_name in checks:
param_path = '/sys/module/i915/parameters/%s' % param_name
if not os.path.exists(param_path):
errors += 1
logging.error('Error(%d), %s not found', errors, param_path)
else:
out = utils.read_one_line(param_path)
logging.debug('Graphics: %s = %s', param_path, out)
value = int(out)
if value != checks[param_name]:
errors += 1
logging.error('Error(%d), %s = %d but should be %d',
errors, param_path, value,
checks[param_name])
errors += self._verify_lvds_downclock_mode_added()
return errors
def _verify_pcie_aspm_power_settings(self):
errors = 0
out = utils.system_output('lspci -n')
for line in out.splitlines():
slot, _, pci_id = line.split()[0:3]
slot_out = utils.system_output('lspci -s %s -vv' % slot,
retain_output=True)
match = re.search(r'LnkCtl:(.*);', slot_out)
if match:
if pci_id in ASPM_EXCEPTED_DEVICES[self._cpu_type]:
continue
split = match.group(1).split()
if split[1] == 'Disabled' or \
(split[2] == 'Enabled' and split[1] != 'L1'):
errors += 1
logging.info(slot_out)
logging.error('Error(%d), %s ASPM off or no L1 support',
errors, slot)
else:
logging.info('PCIe: LnkCtl not found for %s', line)
return errors
def _verify_dmi_power_settings(self):
return self._registers.verify_dmi(DMI_BAR_CHECKS[self._cpu_type])
def _verify_mch_power_settings(self):
return self._registers.verify_mch(MCH_BAR_CHECKS[self._cpu_type])
def _verify_msr_power_settings(self):
return self._registers.verify_msr(MSR_CHECKS[self._cpu_type])
def _verify_rapl_power_settings(self):
errors = 0
if self._cpu_type not in RAPL_CHECKS:
return errors
test_domains=RAPL_CHECKS[self._cpu_type].keys()
rapls = power_rapl.create_rapl(domains=test_domains)
time.sleep(2)
for rapl in rapls:
power = rapl.refresh()
domain = rapl.domain
test_params = RAPL_CHECKS[self._cpu_type][domain]
logging.info('RAPL %s power during 2secs was: %.3fW',
domain, power)
if power > test_params['max']:
errors += 1
logging.error('Error(%d), RAPL %s power > %.3fW',
errors, domain, test_params['max'])
if power < test_params['min']:
errors += 1
logging.error('Error(%d), RAPL %s power < %.3fW',
errors, domain, test_params['min'])
return errors