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. 4 5import commands, glob, logging, os, re, time 6from autotest_lib.client.bin import test, utils 7from autotest_lib.client.common_lib import error 8from autotest_lib.client.cros import power_rapl 9from autotest_lib.client.cros import power_status 10from autotest_lib.client.cros import power_utils 11from autotest_lib.client.cros import cros_logging 12 13 14# Specify registers to check. The format needs to be: 15# register offset : ('bits', 'expression') 16DMI_BAR_CHECKS = { 17 'Atom': { 18 '0x88': [('1:0', 3)], 19 '0x200': [('27:26', 0)], 20 '0x210': [('2:0', 1), ('15:8', 1)], 21 '0xc28': [('5:1', 7)], 22 '0xc2e': [('5', 1)], 23 '0xc30': [('11', 0), ('10:8', 4)], 24 '0xc34': [('9:4', 7), ('0', 1)], 25 }, 26 'Non-Atom': { 27 # http://www.intel.com/content/dam/doc/datasheet/2nd-gen-core-family-mobile-vol-2-datasheet.pdf 28 # PCIE DMI Link Control Register 29 # -- [1:0] : ASPM State 0=Disable, 1=L0s, 2=reserved, 3=L0s&L1 30 '0x88': [('1:0', 3)], 31 }, 32 } 33 34# http://www.intel.com/content/dam/www/public/us/en/documents/datasheets/2nd-gen-core-family-mobile-vol-1-datasheet.pdf 35# PM_PDWN_Config_ 36# -- [12] : Global power-down (GLPDN). 1 == global, 0 == per rank 37# -- [11:8] : Power-down mode. 0->0x7. Higher is lower power 38# -- [7:0] : Power-down idle timer. Lower is better. Minimum 39# recommended is 0xf 40MCH_PM_PDWN_CONFIG = [('12', 0), ('11:8', 0x6, '>='), ('7:0', 0x40, '<='), 41 ('7:0', 0xf, '>=')] 42MCH_BAR_CHECKS = { 43 'Atom': {}, 44 'Non-Atom': { 45 # mmc0 46 '0x40b0': MCH_PM_PDWN_CONFIG, 47 # mmc1 48 '0x44b0': MCH_PM_PDWN_CONFIG, 49 # single mmc 50 '0x4cb0': MCH_PM_PDWN_CONFIG, 51 }, 52 } 53 54MSR_CHECKS = { 55 'Atom': { 56 '0xe2': [('7', 0), ('2:0', 4)], 57 '0x198': [('28:24', 6)], 58 '0x1a0': [('33:32', 3), ('26:25', 3), ('16', 1)], 59 }, 60 'Non-Atom': { 61 # IA32_ENERGY_PERF_BIAS[3:0] -- 0 == hi-perf, 6 balanced, 15 powersave 62 '0x1b0': [('3:0', 6)], 63 }, 64 } 65 66# Give an ASPM exception for these PCI devices. ID is taken from lspci -n. 67ASPM_EXCEPTED_DEVICES = { 68 'Atom': [ 69 # Intel 82801G HDA Controller 70 '8086:27d8' 71 ], 72 'Non-Atom': [ 73 # Intel HDA Controller 74 '8086:1c20', 75 '8086:1e20' 76 ], 77 } 78 79GFX_CHECKS = { 80 'Non-Atom': {'i915_enable_rc6': -1, 'i915_enable_fbc': 1, 'powersave': 1, 81 'semaphores': 1, 'lvds_downclock': 1} 82 } 83 84# max & min are in Watts. Device should presumably be idle. 85RAPL_CHECKS = { 86 'Non-Atom': {'pkg': {'max': 5.0, 'min': 1.0}, 87 'pp0': {'max': 1.2, 'min': 0.001}, 88 'pp1': {'max': 1.0, 'min': 0.000}} 89 } 90 91SUBTESTS = ['dmi', 'mch', 'msr', 'pcie_aspm', 'wifi', 'usb', 'storage', 92 'audio', 'filesystem', 'graphics', 'rapl'] 93 94 95class power_x86Settings(test.test): 96 """Class for power_x86Settings. See 'control' for details.""" 97 version = 1 98 99 100 def initialize(self): 101 self._usbpower = power_utils.USBPower() 102 103 104 def run_once(self): 105 cpu_arch = power_utils.get_x86_cpu_arch() 106 if not cpu_arch: 107 raise error.TestNAError('Unsupported CPU') 108 109 self._cpu_type = 'Atom' 110 if cpu_arch is not 'Atom': 111 self._cpu_type = 'Non-Atom' 112 113 self._registers = power_utils.Registers() 114 115 status = power_status.get_status() 116 if status.on_ac(): 117 logging.info('AC Power is online') 118 self._on_ac = True 119 else: 120 logging.info('AC Power is offline') 121 self._on_ac = False 122 123 failures = '' 124 125 for testname in SUBTESTS: 126 logging.info("SUBTEST = %s", testname) 127 func = getattr(self, "_verify_%s_power_settings" % testname) 128 fail_count = func() 129 if fail_count: 130 failures += '%s_failures(%d) ' % (testname, fail_count) 131 132 if failures: 133 raise error.TestFail(failures) 134 135 136 def _verify_wifi_power_settings(self): 137 if self._on_ac: 138 expected_state = 'off' 139 else: 140 expected_state = 'on' 141 142 iwconfig_out = utils.system_output('iwconfig 2>&1', retain_output=True) 143 match = re.search(r'Power Management:(.*)', iwconfig_out) 144 if match and match.group(1) == expected_state: 145 return 0 146 147 logging.info(iwconfig_out) 148 return 1 149 150 151 def _verify_storage_power_settings(self): 152 if self._on_ac: 153 return 0 154 155 expected_state = 'min_power' 156 157 dirs_path = '/sys/class/scsi_host/host*' 158 dirs = glob.glob(dirs_path) 159 if not dirs: 160 logging.info('scsi_host paths not found') 161 return 1 162 163 for dirpath in dirs: 164 link_policy_file = os.path.join(dirpath, 165 'link_power_management_policy') 166 if not os.path.exists(link_policy_file): 167 logging.debug('path does not exist: %s', link_policy_file) 168 continue 169 170 out = utils.read_one_line(link_policy_file) 171 logging.debug('storage: path set to %s for %s', 172 out, link_policy_file) 173 if out == expected_state: 174 return 0 175 176 return 1 177 178 179 def _verify_usb_power_settings(self): 180 errors = 0 181 self._usbpower.query_devices() 182 for dev in self._usbpower.devices: 183 # whitelist MUST autosuspend 184 autosuspend = dev.autosuspend() 185 logging.debug("USB %s:%s whitelisted:%s autosuspend:%s", 186 dev.vid, dev.pid, dev.whitelisted, autosuspend) 187 if dev.whitelisted and not autosuspend: 188 logging.error("Whitelisted USB %s:%s " 189 "has autosuspend disabled", dev.vid, dev.pid) 190 errors += 1 191 elif not dev.whitelisted: 192 # TODO(crbug.com/242228): Deprecate warnings once we can 193 # definitively identify preferred USB autosuspend settings 194 logging.warning("Non-Whitelisted USB %s:%s present. " 195 "Should it be whitelisted?", dev.vid, dev.pid) 196 197 return errors 198 199 200 def _verify_audio_power_settings(self): 201 path = '/sys/module/snd_hda_intel/parameters/power_save' 202 out = utils.read_one_line(path) 203 logging.debug('Audio: %s = %s', path, out) 204 power_save_timeout = int(out) 205 206 # Make sure that power_save timeout parameter is zero if on AC. 207 if self._on_ac: 208 if power_save_timeout == 0: 209 return 0 210 else: 211 logging.debug('Audio: On AC power but power_save = %d', \ 212 power_save_timeout) 213 return 1 214 215 # Make sure that power_save timeout parameter is non-zero if on battery. 216 elif power_save_timeout > 0: 217 return 0 218 219 logging.debug('Audio: On battery power but power_save = %d', \ 220 power_save_timeout) 221 return 1 222 223 224 def _verify_filesystem_power_settings(self): 225 mount_output = commands.getoutput('mount | fgrep commit=').split('\n') 226 if len(mount_output) == 0: 227 logging.debug('No file system entries with commit intervals found.') 228 return 1 229 230 errors = 0 231 # Parse for 'commit' param 232 for line in mount_output: 233 try: 234 commit = int(re.search(r'(commit=)([0-9]*)', line).group(2)) 235 except: 236 errors += 1 237 logging.error('Error(%d), reading commit value from \'%s\'', 238 errors, line) 239 continue 240 241 # Check for the correct commit interval. 242 if commit != 600: 243 errors += 1 244 logging.error('Error(%d), incorrect commit interval %d', errors, 245 commit) 246 247 return errors 248 249 def _verify_lvds_downclock_mode_added(self): 250 """Checks the kernel log for a message that an LVDS downclock mode has 251 been added. 252 253 This test is specific to alex/lumpy/parrot/stout since they use the i915 254 driver (which has downclocking ability) and use known LCDs. These LCDs 255 are special, in that they support a downclocked refresh rate, but don't 256 advertise it in the EDID. 257 258 To counteract this, I added a quirk in drm to add a downclocked mode to 259 the panel. Unfortunately, upstream doesn't want this patch, and we have 260 to carry it locally. The quirk patch was dropped inadvertently from 261 chromeos-3.4, so this test ensures we don't regress again. 262 263 I plan on writing an upstream friendly patch sometime in the near 264 future, at which point I'll revert my drm hack and this test. 265 266 Returns: 267 0 if no errors, otherwise the number of errors that occurred. 268 """ 269 cmd = 'cat /etc/lsb-release | grep CHROMEOS_RELEASE_BOARD' 270 output = utils.system_output(cmd) 271 if ('lumpy' not in output and 'alex' not in output and 272 'parrot' not in output and 'stout' not in output): 273 return 0 274 275 # Get the downclock message from the logs 276 reader = cros_logging.LogReader() 277 reader.set_start_by_reboot(-1) 278 if not reader.can_find('Adding LVDS downclock mode'): 279 logging.error('Error, LVDS downclock quirk not applied!') 280 return 1 281 282 return 0 283 284 def _verify_graphics_power_settings(self): 285 """Verify that power-saving for graphics are configured properly. 286 287 Returns: 288 0 if no errors, otherwise the number of errors that occurred. 289 """ 290 errors = 0 291 292 if self._cpu_type in GFX_CHECKS: 293 checks = GFX_CHECKS[self._cpu_type] 294 for param_name in checks: 295 param_path = '/sys/module/i915/parameters/%s' % param_name 296 if not os.path.exists(param_path): 297 errors += 1 298 logging.error('Error(%d), %s not found', errors, param_path) 299 else: 300 out = utils.read_one_line(param_path) 301 logging.debug('Graphics: %s = %s', param_path, out) 302 value = int(out) 303 if value != checks[param_name]: 304 errors += 1 305 logging.error('Error(%d), %s = %d but should be %d', 306 errors, param_path, value, 307 checks[param_name]) 308 errors += self._verify_lvds_downclock_mode_added() 309 310 # On systems which support RC6 (non atom), check that we get into rc6; 311 # idle before doing so, and retry every second for 20 seconds. 312 if self._cpu_type == 'Non-Atom': 313 tries = 0 314 found = False 315 while found == False and tries < 20: 316 time.sleep(1) 317 param_path = "/sys/kernel/debug/dri/0/i915_drpc_info" 318 if not os.path.exists(param_path): 319 logging.error('Error(%d), %s not found', errors, param_path) 320 break 321 drpc_info_file = open (param_path, "r") 322 for line in drpc_info_file: 323 match = re.search(r'Current RC state: (.*)', line) 324 if match: 325 found = match.group(1) != 'on' 326 break 327 328 tries += 1 329 drpc_info_file.close() 330 331 if not found: 332 errors += 1 333 logging.error('Error(%d), did not see the GPU in RC6', errors) 334 335 return errors 336 337 338 def _verify_pcie_aspm_power_settings(self): 339 errors = 0 340 out = utils.system_output('lspci -n') 341 for line in out.splitlines(): 342 slot, _, pci_id = line.split()[0:3] 343 slot_out = utils.system_output('lspci -s %s -vv' % slot, 344 retain_output=True) 345 match = re.search(r'LnkCtl:(.*);', slot_out) 346 if match: 347 if pci_id in ASPM_EXCEPTED_DEVICES[self._cpu_type]: 348 continue 349 350 split = match.group(1).split() 351 if split[1] == 'Disabled' or \ 352 (split[2] == 'Enabled' and split[1] != 'L1'): 353 errors += 1 354 logging.info(slot_out) 355 logging.error('Error(%d), %s ASPM off or no L1 support', 356 errors, slot) 357 else: 358 logging.info('PCIe: LnkCtl not found for %s', line) 359 360 return errors 361 362 363 def _verify_dmi_power_settings(self): 364 return self._registers.verify_dmi(DMI_BAR_CHECKS[self._cpu_type]) 365 366 def _verify_mch_power_settings(self): 367 return self._registers.verify_mch(MCH_BAR_CHECKS[self._cpu_type]) 368 369 def _verify_msr_power_settings(self): 370 return self._registers.verify_msr(MSR_CHECKS[self._cpu_type]) 371 372 def _verify_rapl_power_settings(self): 373 errors = 0 374 if self._cpu_type not in RAPL_CHECKS: 375 return errors 376 377 test_domains = RAPL_CHECKS[self._cpu_type].keys() 378 rapls = power_rapl.create_rapl(domains=test_domains) 379 380 time.sleep(2) 381 for rapl in rapls: 382 power = rapl.refresh() 383 domain = rapl.domain 384 test_params = RAPL_CHECKS[self._cpu_type][domain] 385 logging.info('RAPL %s power during 2secs was: %.3fW', 386 domain, power) 387 if power > test_params['max']: 388 errors += 1 389 logging.error('Error(%d), RAPL %s power > %.3fW', 390 errors, domain, test_params['max']) 391 if power < test_params['min']: 392 errors += 1 393 logging.error('Error(%d), RAPL %s power < %.3fW', 394 errors, domain, test_params['min']) 395 return errors 396