1# Copyright 2016 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 logging, re, time
6
7from autotest_lib.client.common_lib import error
8from autotest_lib.server import test
9from autotest_lib.server.cros.dark_resume_utils import DarkResumeUtils
10from autotest_lib.server.cros.faft.config.config import Config as FAFTConfig
11
12SUSPEND_DURATION = 15
13NUM_DARK_RESUMES = 10
14ERROR_FILE = '/sys/kernel/debug/dri/0/i915_crtc_errors'
15
16
17class power_DarkResumeDisplay(test.test):
18    """ Ensure we don't have display errors after dark resume """
19    version = 1
20    dark_resume_utils = None
21
22
23    def initialize(self, host):
24        self.dark_resume_utils = DarkResumeUtils(host,
25                                                 duration=SUSPEND_DURATION)
26
27
28    def verify_host_supports_test(self, host):
29        """Check if the test works on the given host
30
31        @param host: reference to the host object
32        """
33        platform = host.run_output('mosys platform name')
34        logging.info('Checking platform %s for compatibility with display test',
35                     platform)
36
37        # TODO(seanpaul) Look at backporting i915_crtc_errors accounting to
38        # other kernels.
39        kernel_ver = host.run('uname -r').stdout.rstrip()
40        logging.info('kernel version is %s', kernel_ver)
41        if not kernel_ver.startswith('3.14') and \
42           not kernel_ver.startswith('3.18'):
43            raise error.TestNAError('Test support on 3.14 | 3.18 kernels only')
44
45        client_attr = FAFTConfig(platform)
46        if client_attr.dark_resume_capable == False:
47            raise error.TestNAError('platform is not capable of dark resume')
48
49        cmd = host.run('test -r %s' % ERROR_FILE, ignore_status=True)
50        logging.info("node_exists=%s", str(cmd.exit_status))
51        if cmd.exit_status != 0:
52            raise error.TestError('%s file not found.' % ERROR_FILE)
53
54
55    def get_crtc_error_count(self, host):
56        """Get the current crtc error count for the dut
57
58        @returns: A dict whose key is the crtc id, and whose value is a
59                  dict of {pipe, errors}
60        """
61        output = host.run_output('cat %s' % ERROR_FILE)
62        pattern = 'Crtc ([0-9]+) Pipe ([A-Za-z]+) errors:\t\t([0-9a-fA-F]{8})'
63        regex = re.compile(pattern)
64        counts = {}
65        for line in output.splitlines():
66            match = regex.match(line)
67            if match == None:
68                raise error.TestError('Unexpected error file string: %s' % line)
69
70            counts[int(match.group(1))] = {
71                'pipe': match.group(2),
72                'errors': int(match.group(3), 16),
73            }
74        return counts
75
76
77    def run_once(self, host=None):
78        """Run the test.
79
80           Setup preferences so that a dark resume will happen shortly after
81           suspending the machine.
82
83           store the current crtc error count
84           suspend the machine
85           wait for dark resume
86           wake the machine
87           retrieve the current crtc error count after suspend
88           ensure the error counts did not increase while suspended
89
90        @param host: The machine to run the tests on
91        """
92        self.verify_host_supports_test(host)
93
94        pre_err_count = self.get_crtc_error_count(host)
95
96        # The DUT will perform a dark resume every SUSPEND_DURATION seconds
97        # while it is suspended. Suspend the device and wait for the amount
98        # of time to have performed NUM_DARK_RESUMES, plus half the
99        # SUSPEND_DURATION to ensure the last dark resume has a chance to
100        # complete.
101        wait_time = SUSPEND_DURATION * NUM_DARK_RESUMES + SUSPEND_DURATION / 2
102        logging.info('suspending host, and waiting %ds', wait_time)
103        with self.dark_resume_utils.suspend() as _:
104            time.sleep(wait_time)
105
106        dark_resume_count = self.dark_resume_utils.count_dark_resumes()
107        logging.info('dark resume count = %d', dark_resume_count)
108        if dark_resume_count == 0:
109            raise error.TestError('Device did not enter dark resume!')
110
111        logging.info('retrieving post-suspend error counts')
112        post_err_count = self.get_crtc_error_count(host)
113        for k in pre_err_count:
114            pre = pre_err_count[k]['errors']
115            post = post_err_count[k]['errors']
116            if pre != post:
117                raise error.TestError('Crtc %d Pipe %s err count changed %d/%d'
118                                      % (k, pre_err_count[k]['pipe'], pre,
119                                         post))
120            logging.info('error counts for Crtc %d Pipe %s constant at %d', k,
121                         pre_err_count[k]['pipe'], pre)
122
123
124    def cleanup(self, _):
125        self.dark_resume_utils.teardown()
126
127