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