xref: /aosp_15_r20/external/autotest/client/site_tests/graphics_Idle/graphics_Idle.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
1# Lint as: python2, python3
2# Copyright 2014 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6import errno, glob, logging, os, re, struct, sys, time
7
8from autotest_lib.client.bin import test
9from autotest_lib.client.bin import utils
10from autotest_lib.client.common_lib import error
11from autotest_lib.client.common_lib import utils as common_utils
12from autotest_lib.client.common_lib.cros import chrome
13from autotest_lib.client.cros import cros_logging
14from autotest_lib.client.cros.graphics import graphics_utils
15
16# Kernel 3.8 to 3.14 has cur_delay_info, 3.18+ has frequency_info.
17CLOCK_PATHS = [
18    '/sys/kernel/debug/dri/0/i915_frequency_info',
19    '/sys/kernel/debug/dri/0/i915_cur_delayinfo'
20]
21# Kernel 3.8 has i915_fbc, kernel > 3.8 i915_fbc_status.
22FBC_PATHS = [
23    '/sys/kernel/debug/dri/0/i915_fbc',
24    '/sys/kernel/debug/dri/0/i915_fbc_status'
25]
26GEM_OBJECTS_PATHS = ['/sys/kernel/debug/dri/0/i915_gem_objects']
27GEM_PATHS = ['/sys/kernel/debug/dri/0/i915_gem_active']
28PSR_PATHS = ['/sys/kernel/debug/dri/0/i915_edp_psr_status']
29# Kernel 5.7+ has DRPC info in gt/ subdirectory
30RC6_PATHS = [
31    '/sys/kernel/debug/dri/0/i915_drpc_info',
32    '/sys/kernel/debug/dri/0/gt/drpc'
33]
34
35
36class graphics_Idle(graphics_utils.GraphicsTest):
37    """Class for graphics_Idle.  See 'control' for details."""
38    version = 1
39    _gpu_type = None
40    _cpu_type = None
41    _board = None
42
43    def run_once(self, arc_mode=None):
44        # If we are in arc_mode, do not report failures to perf dashboard.
45        if arc_mode:
46            self._test_failure_report_enable = False
47
48        self.add_failures('graphics_Idle')
49        with chrome.Chrome(
50                logged_in=True,
51                arc_mode=arc_mode) as cr:
52            # The New Tab Page contains the Google doodle which can cause
53            # arbitrary side effects. Hide it by going to a neutral page.
54            if not cr.browser.tabs:
55                cr.browser.tabs.New()
56            tab = cr.browser.tabs[0]
57            tab.Navigate('chrome://version')
58            # Try to protect against runaway previous tests.
59            if not utils.wait_for_idle_cpu(60.0, 0.1):
60                logging.warning('Could not get idle CPU before running tests.')
61            self._gpu_type = utils.get_gpu_family()
62            self._cpu_type = utils.get_cpu_soc_family()
63            self._board = utils.get_board()
64            errors = ''
65            errors += self.verify_graphics_dvfs()
66            errors += self.verify_graphics_fbc()
67            errors += self.verify_graphics_psr()
68            errors += self.verify_graphics_gem_idle()
69            errors += self.verify_graphics_i915_min_clock()
70            errors += self.verify_graphics_rc6()
71            errors += self.verify_lvds_downclock()
72            errors += self.verify_short_blanking()
73            if errors:
74                raise error.TestFail('Failed: %s' % errors)
75        self.remove_failures('graphics_Idle')
76
77    def get_valid_path(self, paths):
78        for path in paths:
79            if os.path.exists(path):
80                return path
81        logging.error('Error: %s not found.', ' '.join(paths))
82        return None
83
84    def handle_error(self, message, path=None):
85        logging.error('Error: %s', message)
86        # For debugging show the content of the file.
87        if path is not None:
88            with open(path, 'r') as text_file:
89                logging.info('Content of %s\n%s', path, text_file.read())
90        # Dump the output of 'top'.
91        utils.log_process_activity()
92        return message
93
94    def verify_lvds_downclock(self):
95        """On systems which support LVDS downclock, checks the kernel log for
96        a message that an LVDS downclock mode has been added."""
97        logging.info('Running verify_lvds_downclock')
98        board = utils.get_board()
99        if not (board == 'alex' or board == 'lumpy' or board == 'stout'):
100            return ''
101        # Get the downclock message from the logs.
102        reader = cros_logging.LogReader()
103        reader.set_start_by_reboot(-1)
104        if not reader.can_find('Adding LVDS downclock mode'):
105            return self.handle_error('LVDS downclock quirk not applied. ')
106        return ''
107
108    def verify_short_blanking(self):
109        """On baytrail systems with a known panel, checks the kernel log for a
110        message that a short blanking mode has been added."""
111        logging.info('Running verify_short_blanking')
112        if self._gpu_type != 'baytrail' or utils.has_no_monitor():
113            return ''
114
115        # Open the EDID to find the panel model.
116        param_path = '/sys/class/drm/card0-eDP-1/edid'
117        if not os.path.exists(param_path):
118            logging.error('Error: %s not found.', param_path)
119            return self.handle_error(
120                'Short blanking not added (no EDID found). ')
121
122        with open(param_path, 'r') as edp_edid_file:
123            edp_edid_file.seek(8)
124            data = edp_edid_file.read(2)
125            manufacturer = int(struct.unpack('<H', data)[0])
126            data = edp_edid_file.read(2)
127            product_code = int(struct.unpack('<H', data)[0])
128        # This is not the panel we are looking for (AUO B116XTN02.2)
129        if manufacturer != 0xaf06 or product_code != 0x225c:
130            return ''
131        # Get the downclock message from the logs.
132        reader = cros_logging.LogReader()
133        reader.set_start_by_reboot(-1)
134        if not reader.can_find('Modified preferred into a short blanking mode'):
135            return self.handle_error('Short blanking not added. ')
136        return ''
137
138    def verify_graphics_rc6(self):
139        """ On systems which support RC6 (non atom), check that we are able to
140        get into rc6; idle before doing so, and retry every second for 20
141        seconds."""
142        logging.info('Running verify_graphics_rc6')
143        # TODO(ihf): Implement on baytrail/braswell using residency counters.
144        # But note the format changed since SNB, so this will be complex.
145        if (utils.get_cpu_soc_family() == 'x86_64' and
146                self._gpu_type != 'pinetrail' and
147                self._gpu_type != 'baytrail' and self._gpu_type != 'braswell'):
148            tries = 0
149            found = False
150            param_path = self.get_valid_path(RC6_PATHS)
151            if not param_path:
152                return 'RC6_PATHS not found.'
153            while found == False and tries < 20:
154                time.sleep(1)
155                with open(param_path, 'r') as drpc_info_file:
156                    for line in drpc_info_file:
157                        match = re.search(r'Current RC state: (.*)', line)
158                        if match and match.group(1) == 'RC6':
159                            found = True
160                            break
161                tries += 1
162            if not found:
163                return self.handle_error('Error: did not see the GPU in RC6.',
164                                         param_path)
165        return ''
166
167    def verify_graphics_i915_min_clock(self):
168        """ On i915 systems, check that we get into the lowest clock frequency;
169        idle before doing so, and retry every second for 20 seconds."""
170        logging.info('Running verify_graphics_i915_min_clock')
171
172        # TODO(benzh): enable once crbug.com/719040 is fixed.
173        if self._gpu_type == 'baytrail' and utils.count_cpus() == 4:
174            logging.info('Waived min clock check due to crbug.com/719040')
175            return ''
176
177        if (utils.get_cpu_soc_family() == 'x86_64' and
178                self._gpu_type != 'pinetrail'):
179            tries = 0
180            found = False
181            param_path = self.get_valid_path(CLOCK_PATHS)
182            if not param_path:
183                return 'CLOCK_PATHS not found.'
184            while not found and tries < 80:
185                time.sleep(0.25)
186
187                with open(param_path, 'r') as delayinfo_file:
188                    for line in delayinfo_file:
189                        # This file has a different format depending on the
190                        # board, so we parse both. Also, it would be tedious
191                        # to add the minimum clock for each board, so instead
192                        # we use 650MHz which is the max of the minimum clocks.
193                        match = re.search(r'CAGF: (.*)MHz', line)
194                        if match and int(match.group(1)) <= 650:
195                            found = True
196                            break
197
198                        match = re.search(r'current GPU freq: (.*) MHz', line)
199                        if match and int(match.group(1)) <= 650:
200                            found = True
201                            break
202
203                tries += 1
204
205            if not found:
206                return self.handle_error('Did not see the min i915 clock. ',
207                                         param_path)
208
209        return ''
210
211    def get_devfreq_path(self, dev_path):
212        """ Return the path of the devfreq device for a device (if it has one).
213        """
214        g = glob.glob(dev_path + "/devfreq/*")
215
216        if len(g) != 1:
217            raise RuntimeError("Device '" + dev_path + "' has no devfreq device")
218
219        return g[0]
220
221    def verify_graphics_dvfs(self):
222        """ On systems which support DVFS, check that we get into the lowest
223        clock frequency; idle before doing so, and retry every second for 20
224        seconds."""
225        logging.info('Running verify_graphics_dvfs')
226
227        exynos_node = '/sys/devices/11800000.mali/'
228        rk3288_node = '/sys/devices/ffa30000.gpu/'
229        rk3288_419_node = '/sys/devices/platform/ffa30000.gpu/'
230        rk3399_node = '/sys/devices/platform/ff9a0000.gpu/'
231        mt8173_node = '/sys/devices/soc/13000000.mfgsys-gpu/'
232        mt8173_419_node = '/sys/devices/platform/soc/13000000.mfgsys-gpu/'
233        mt8183_node = '/sys/devices/platform/soc/13040000.mali/'
234        mt8192_node = '/sys/devices/platform/soc/13000000.mali/'
235
236        if self._cpu_type == 'exynos5':
237            if os.path.isdir(exynos_node):
238                node = exynos_node
239                use_devfreq = False
240                enable_node = 'dvfs'
241                enable_value = 'on'
242            else:
243                logging.error('Error: unknown exynos SoC.')
244                return self.handle_error('Unknown exynos SoC.')
245        elif self._cpu_type.startswith('rockchip'):
246            if os.path.isdir(rk3288_node):
247                node = rk3288_node
248                use_devfreq = False
249                enable_node = 'dvfs_enable'
250                enable_value = '1'
251            elif os.path.isdir(rk3288_419_node):
252                node = rk3288_419_node
253                use_devfreq = True
254            elif os.path.isdir(rk3399_node):
255                node = rk3399_node
256                use_devfreq = True
257            else:
258                logging.error('Error: unknown rockchip SoC.')
259                return self.handle_error('Unknown rockchip SoC.')
260        elif self._cpu_type == 'mediatek':
261            if os.path.isdir(mt8173_node):
262                node = mt8173_node
263                use_devfreq = True
264            elif os.path.isdir(mt8173_419_node):
265                node = mt8173_419_node
266                use_devfreq = True
267            elif os.path.isdir(mt8183_node):
268                node = mt8183_node
269                use_devfreq = True
270            elif os.path.isdir(mt8192_node):
271                node = mt8192_node
272                use_devfreq = True
273            else:
274                logging.error('Error: unknown mediatek SoC.')
275                return self.handle_error('Unknown mediatek SoC.')
276        else:
277            return ''
278
279        if use_devfreq:
280            node = self.get_devfreq_path(node)
281            governor_path = utils.locate_file('governor', node)
282            clock_path = utils.locate_file('cur_freq', node)
283
284            governor = utils.read_one_line(governor_path)
285            logging.info('DVFS governor = %s', governor)
286            if not governor == 'simple_ondemand':
287                logging.error('Error: DVFS governor is not simple_ondemand.')
288                return self.handle_error('Governor is wrong.')
289        else:
290            clock_path = utils.locate_file('clock', node)
291            enable_path = utils.locate_file(enable_node, node)
292
293            enable = utils.read_one_line(enable_path)
294            logging.info('DVFS enable = %s', enable)
295            if not enable == enable_value:
296                return self.handle_error('DVFS is not enabled. ')
297
298        freqs_path = utils.locate_file('available_frequencies', node)
299
300        # available_frequencies are always sorted in ascending order
301        # each line may contain one or multiple integers separated by spaces
302        min_freq = int(utils.read_one_line(freqs_path).split()[0])
303
304        # daisy_* (exynos5250) boards set idle frequency to 266000000
305        # See: crbug.com/467401 and crosbug.com/p/19710
306        if self._board.startswith('daisy'):
307            min_freq = 266000000
308
309        logging.info('Expecting idle DVFS clock = %u', min_freq)
310        tries = 0
311        found = False
312        while not found and tries < 80:
313            time.sleep(0.25)
314            clock = int(utils.read_one_line(clock_path))
315            if clock <= min_freq:
316                logging.info('Found idle DVFS clock = %u', clock)
317                found = True
318                break
319
320            tries += 1
321        if not found:
322            logging.error('Error: DVFS clock (%u) > min (%u)', clock, min_freq)
323            return self.handle_error('Did not see the min DVFS clock. ',
324                                     clock_path)
325        return ''
326
327    def verify_graphics_fbc(self):
328        """ On systems which support FBC, check that we can get into FBC;
329        idle before doing so, and retry every second for 20 seconds."""
330        logging.info('Running verify_graphics_fbc')
331
332        # Link's FBC is disabled (crbug.com/338588).
333        # TODO(marcheu): remove this when/if we fix this bug.
334        board = utils.get_board()
335        if board == 'link':
336            return ''
337
338        # Machines which don't have a monitor can't get FBC.
339        if utils.has_no_monitor():
340            return ''
341
342        if (self._gpu_type == 'haswell' or self._gpu_type == 'ivybridge' or
343                self._gpu_type == 'sandybridge'):
344            tries = 0
345            found = False
346            param_path = self.get_valid_path(FBC_PATHS)
347            if not param_path:
348                return 'FBC_PATHS not found.'
349            while not found and tries < 20:
350                time.sleep(1)
351                with open(param_path, 'r') as fbc_info_file:
352                    for line in fbc_info_file:
353                        if re.search('FBC enabled', line):
354                            found = True
355                            break
356
357                tries += 1
358            if not found:
359                return self.handle_error('Did not see FBC enabled. ',
360                                         param_path)
361        return ''
362
363    def verify_graphics_psr(self):
364        """ On systems which support PSR, check that we can get into PSR;
365        idle before doing so, and retry every second for 20 seconds."""
366        logging.info('Running verify_graphics_psr')
367
368        if utils.get_cpu_soc_family() != 'x86_64':
369            return ''
370        tries = 0
371        found = False
372        param_path = self.get_valid_path(PSR_PATHS)
373        if not param_path:
374            logging.warning("PSR_PATHS not found.")
375            return ''
376        kernel_version = utils.get_kernel_version()[0:4].rstrip(".")
377        logging.info('Kernel version: %s', kernel_version)
378        # First check if PSR is enabled on the device so
379        # we can watch for the active values
380        with open(param_path, 'r') as psr_info_file:
381            match = None
382            try:
383                for line in psr_info_file:
384                    match = re.search(r'Enabled: yes', line)
385                    if match:
386                        logging.info('PSR enabled')
387                        break
388                if not match:
389                    logging.warning('PSR not enabled')
390                    return ''
391            except IOError as e:
392                num, strerror = e.args
393                # Newer kernels might report ENODEV when PSR not available.
394                if num == errno.ENODEV:
395                    logging.warning('PSR not enabled')
396                    return ''
397                else:
398                    logging.error('While accessing %s', param_path)
399                    logging.error(e)
400                    return self.handle_error('Unexpected PSR read failure. ')
401        while not found and tries < 20:
402            time.sleep(1)
403            with open(param_path, 'r') as psr_info_file:
404                for line in psr_info_file:
405                    # Kernels 4.4 and up
406                    if common_utils.compare_versions(kernel_version, '4.4') != -1:
407                        match = re.search(r'PSR status: .* \[SRDENT', line)
408                        if match:
409                            found = True
410                            logging.info('Found active with kernel >= 4.4')
411                            break
412                    # 3.18 kernel
413                    elif kernel_version == '3.18':
414                        match = re.search(r'Performance_Counter: 0', line)
415                        if match:
416                            found = True
417                            logging.info('Found active with 3.18 kernel')
418                            break
419                    # Older kernels (up to 3.14)
420                    else:
421                        match = re.search(r'Performance_Counter: ([\d])+', line)
422                        if match and int(match.group(1)) > 0:
423                            found = True
424                            logging.info('Found active with kernel <= 3.14')
425                            break
426
427            tries += 1
428        if not found:
429            return self.handle_error('Did not see PSR activity. ', param_path)
430        return ''
431
432    def verify_graphics_gem_idle(self):
433        """ On systems which have i915, check that we can get all gem objects
434        to become idle (i.e. the i915_gem_active list or i915_gem_objects
435        client/process gem object counts need to go to 0);
436        idle before doing so, and retry every second for 20 seconds."""
437        kernel_version = utils.get_kernel_version()[0:4].rstrip(".")
438        # Skip test on kernel 5.10 and above.
439        if common_utils.compare_versions(kernel_version, '5.10') != -1:
440            # The data needed for this test was removed in the 5.10 kernel.
441            # See b/179453336 for details.
442            logging.info('Skipping gem idle check on 5.10 and above')
443            return ''
444
445        logging.info('Running verify_graphics_gem_idle')
446        if utils.get_cpu_soc_family() == 'x86_64':
447            tries = 0
448            found = False
449            per_process_check = False
450
451            gem_path = self.get_valid_path(GEM_PATHS)
452            if not gem_path:
453                gem_path = self.get_valid_path(GEM_OBJECTS_PATHS)
454                if gem_path:
455                    per_process_check = True
456                else:
457                    return 'GEM_PATHS not found.'
458
459            # Checks 4.4 and later kernels
460            if per_process_check:
461                while not found and tries < 240:
462                    time.sleep(0.25)
463                    gem_objects_idle = False
464                    gem_active_search = False
465                    with open(gem_path, 'r') as gem_file:
466                        for line in gem_file:
467                            if gem_active_search:
468                                if re.search('\(0 active,', line):
469                                    gem_objects_idle = True
470                                else:
471                                    gem_objects_idle = False
472                                    break
473                            elif line == '\n':
474                                gem_active_search = True
475                        if gem_objects_idle:
476                            found = True
477                    tries += 1
478
479            # Checks pre 4.4 kernels
480            else:
481                while not found and tries < 240:
482                    time.sleep(0.25)
483                    with open(gem_path, 'r') as gem_file:
484                        for line in gem_file:
485                            if re.search('Total 0 objects', line):
486                                found = True
487                                break
488                    tries += 1
489            if not found:
490                return self.handle_error('Did not reach 0 gem actives. ',
491                                         gem_path)
492        return ''
493