| # Lint as: python2, python3 |
| # Copyright 2014 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 errno, glob, logging, os, re, struct, sys, time |
| |
| from autotest_lib.client.bin import test |
| from autotest_lib.client.bin import utils |
| from autotest_lib.client.common_lib import error |
| from autotest_lib.client.common_lib import utils as common_utils |
| from autotest_lib.client.common_lib.cros import chrome |
| from autotest_lib.client.cros import cros_logging |
| from autotest_lib.client.cros.graphics import graphics_utils |
| |
| # Kernel 3.8 to 3.14 has cur_delay_info, 3.18+ has frequency_info. |
| CLOCK_PATHS = [ |
| '/sys/kernel/debug/dri/0/i915_frequency_info', |
| '/sys/kernel/debug/dri/0/i915_cur_delayinfo' |
| ] |
| # Kernel 3.8 has i915_fbc, kernel > 3.8 i915_fbc_status. |
| FBC_PATHS = [ |
| '/sys/kernel/debug/dri/0/i915_fbc', |
| '/sys/kernel/debug/dri/0/i915_fbc_status' |
| ] |
| GEM_OBJECTS_PATHS = ['/sys/kernel/debug/dri/0/i915_gem_objects'] |
| GEM_PATHS = ['/sys/kernel/debug/dri/0/i915_gem_active'] |
| PSR_PATHS = ['/sys/kernel/debug/dri/0/i915_edp_psr_status'] |
| # Kernel 5.7+ has DRPC info in gt/ subdirectory |
| RC6_PATHS = [ |
| '/sys/kernel/debug/dri/0/i915_drpc_info', |
| '/sys/kernel/debug/dri/0/gt/drpc' |
| ] |
| |
| |
| class graphics_Idle(graphics_utils.GraphicsTest): |
| """Class for graphics_Idle. See 'control' for details.""" |
| version = 1 |
| _gpu_type = None |
| _cpu_type = None |
| _board = None |
| |
| def run_once(self, arc_mode=None): |
| # If we are in arc_mode, do not report failures to perf dashboard. |
| if arc_mode: |
| self._test_failure_report_enable = False |
| |
| self.add_failures('graphics_Idle') |
| with chrome.Chrome( |
| logged_in=True, |
| arc_mode=arc_mode) as cr: |
| # The New Tab Page contains the Google doodle which can cause |
| # arbitrary side effects. Hide it by going to a neutral page. |
| if not cr.browser.tabs: |
| cr.browser.tabs.New() |
| tab = cr.browser.tabs[0] |
| tab.Navigate('chrome://version') |
| # Try to protect against runaway previous tests. |
| if not utils.wait_for_idle_cpu(60.0, 0.1): |
| logging.warning('Could not get idle CPU before running tests.') |
| self._gpu_type = utils.get_gpu_family() |
| self._cpu_type = utils.get_cpu_soc_family() |
| self._board = utils.get_board() |
| errors = '' |
| errors += self.verify_graphics_dvfs() |
| errors += self.verify_graphics_fbc() |
| errors += self.verify_graphics_psr() |
| errors += self.verify_graphics_gem_idle() |
| errors += self.verify_graphics_i915_min_clock() |
| errors += self.verify_graphics_rc6() |
| errors += self.verify_lvds_downclock() |
| errors += self.verify_short_blanking() |
| if errors: |
| raise error.TestFail('Failed: %s' % errors) |
| self.remove_failures('graphics_Idle') |
| |
| def get_valid_path(self, paths): |
| for path in paths: |
| if os.path.exists(path): |
| return path |
| logging.error('Error: %s not found.', ' '.join(paths)) |
| return None |
| |
| def handle_error(self, message, path=None): |
| logging.error('Error: %s', message) |
| # For debugging show the content of the file. |
| if path is not None: |
| with open(path, 'r') as text_file: |
| logging.info('Content of %s\n%s', path, text_file.read()) |
| # Dump the output of 'top'. |
| utils.log_process_activity() |
| return message |
| |
| def verify_lvds_downclock(self): |
| """On systems which support LVDS downclock, checks the kernel log for |
| a message that an LVDS downclock mode has been added.""" |
| logging.info('Running verify_lvds_downclock') |
| board = utils.get_board() |
| if not (board == 'alex' or board == 'lumpy' or board == 'stout'): |
| return '' |
| # 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'): |
| return self.handle_error('LVDS downclock quirk not applied. ') |
| return '' |
| |
| def verify_short_blanking(self): |
| """On baytrail systems with a known panel, checks the kernel log for a |
| message that a short blanking mode has been added.""" |
| logging.info('Running verify_short_blanking') |
| if self._gpu_type != 'baytrail' or utils.has_no_monitor(): |
| return '' |
| |
| # Open the EDID to find the panel model. |
| param_path = '/sys/class/drm/card0-eDP-1/edid' |
| if not os.path.exists(param_path): |
| logging.error('Error: %s not found.', param_path) |
| return self.handle_error( |
| 'Short blanking not added (no EDID found). ') |
| |
| with open(param_path, 'r') as edp_edid_file: |
| edp_edid_file.seek(8) |
| data = edp_edid_file.read(2) |
| manufacturer = int(struct.unpack('<H', data)[0]) |
| data = edp_edid_file.read(2) |
| product_code = int(struct.unpack('<H', data)[0]) |
| # This is not the panel we are looking for (AUO B116XTN02.2) |
| if manufacturer != 0xaf06 or product_code != 0x225c: |
| return '' |
| # Get the downclock message from the logs. |
| reader = cros_logging.LogReader() |
| reader.set_start_by_reboot(-1) |
| if not reader.can_find('Modified preferred into a short blanking mode'): |
| return self.handle_error('Short blanking not added. ') |
| return '' |
| |
| def verify_graphics_rc6(self): |
| """ On systems which support RC6 (non atom), check that we are able to |
| get into rc6; idle before doing so, and retry every second for 20 |
| seconds.""" |
| logging.info('Running verify_graphics_rc6') |
| # TODO(ihf): Implement on baytrail/braswell using residency counters. |
| # But note the format changed since SNB, so this will be complex. |
| if (utils.get_cpu_soc_family() == 'x86_64' and |
| self._gpu_type != 'pinetrail' and |
| self._gpu_type != 'baytrail' and self._gpu_type != 'braswell'): |
| tries = 0 |
| found = False |
| param_path = self.get_valid_path(RC6_PATHS) |
| if not param_path: |
| return 'RC6_PATHS not found.' |
| while found == False and tries < 20: |
| time.sleep(1) |
| with open(param_path, 'r') as drpc_info_file: |
| for line in drpc_info_file: |
| match = re.search(r'Current RC state: (.*)', line) |
| if match and match.group(1) == 'RC6': |
| found = True |
| break |
| tries += 1 |
| if not found: |
| return self.handle_error('Error: did not see the GPU in RC6.', |
| param_path) |
| return '' |
| |
| def verify_graphics_i915_min_clock(self): |
| """ On i915 systems, check that we get into the lowest clock frequency; |
| idle before doing so, and retry every second for 20 seconds.""" |
| logging.info('Running verify_graphics_i915_min_clock') |
| |
| # TODO(benzh): enable once crbug.com/719040 is fixed. |
| if self._gpu_type == 'baytrail' and utils.count_cpus() == 4: |
| logging.info('Waived min clock check due to crbug.com/719040') |
| return '' |
| |
| if (utils.get_cpu_soc_family() == 'x86_64' and |
| self._gpu_type != 'pinetrail'): |
| tries = 0 |
| found = False |
| param_path = self.get_valid_path(CLOCK_PATHS) |
| if not param_path: |
| return 'CLOCK_PATHS not found.' |
| while not found and tries < 80: |
| time.sleep(0.25) |
| |
| with open(param_path, 'r') as delayinfo_file: |
| for line in delayinfo_file: |
| # This file has a different format depending on the |
| # board, so we parse both. Also, it would be tedious |
| # to add the minimum clock for each board, so instead |
| # we use 650MHz which is the max of the minimum clocks. |
| match = re.search(r'CAGF: (.*)MHz', line) |
| if match and int(match.group(1)) <= 650: |
| found = True |
| break |
| |
| match = re.search(r'current GPU freq: (.*) MHz', line) |
| if match and int(match.group(1)) <= 650: |
| found = True |
| break |
| |
| tries += 1 |
| |
| if not found: |
| return self.handle_error('Did not see the min i915 clock. ', |
| param_path) |
| |
| return '' |
| |
| def get_devfreq_path(self, dev_path): |
| """ Return the path of the devfreq device for a device (if it has one). |
| """ |
| g = glob.glob(dev_path + "/devfreq/*") |
| |
| if len(g) != 1: |
| raise RuntimeError("Device '" + dev_path + "' has no devfreq device") |
| |
| return g[0] |
| |
| def verify_graphics_dvfs(self): |
| """ On systems which support DVFS, check that we get into the lowest |
| clock frequency; idle before doing so, and retry every second for 20 |
| seconds.""" |
| logging.info('Running verify_graphics_dvfs') |
| |
| exynos_node = '/sys/devices/11800000.mali/' |
| rk3288_node = '/sys/devices/ffa30000.gpu/' |
| rk3288_419_node = '/sys/devices/platform/ffa30000.gpu/' |
| rk3399_node = '/sys/devices/platform/ff9a0000.gpu/' |
| mt8173_node = '/sys/devices/soc/13000000.mfgsys-gpu/' |
| mt8173_419_node = '/sys/devices/platform/soc/13000000.mfgsys-gpu/' |
| mt8183_node = '/sys/devices/platform/soc/13040000.mali/' |
| mt8192_node = '/sys/devices/platform/soc/13000000.mali/' |
| |
| if self._cpu_type == 'exynos5': |
| if os.path.isdir(exynos_node): |
| node = exynos_node |
| use_devfreq = False |
| enable_node = 'dvfs' |
| enable_value = 'on' |
| else: |
| logging.error('Error: unknown exynos SoC.') |
| return self.handle_error('Unknown exynos SoC.') |
| elif self._cpu_type.startswith('rockchip'): |
| if os.path.isdir(rk3288_node): |
| node = rk3288_node |
| use_devfreq = False |
| enable_node = 'dvfs_enable' |
| enable_value = '1' |
| elif os.path.isdir(rk3288_419_node): |
| node = rk3288_419_node |
| use_devfreq = True |
| elif os.path.isdir(rk3399_node): |
| node = rk3399_node |
| use_devfreq = True |
| else: |
| logging.error('Error: unknown rockchip SoC.') |
| return self.handle_error('Unknown rockchip SoC.') |
| elif self._cpu_type == 'mediatek': |
| if os.path.isdir(mt8173_node): |
| node = mt8173_node |
| use_devfreq = True |
| elif os.path.isdir(mt8173_419_node): |
| node = mt8173_419_node |
| use_devfreq = True |
| elif os.path.isdir(mt8183_node): |
| node = mt8183_node |
| use_devfreq = True |
| elif os.path.isdir(mt8192_node): |
| node = mt8192_node |
| use_devfreq = True |
| else: |
| logging.error('Error: unknown mediatek SoC.') |
| return self.handle_error('Unknown mediatek SoC.') |
| else: |
| return '' |
| |
| if use_devfreq: |
| node = self.get_devfreq_path(node) |
| governor_path = utils.locate_file('governor', node) |
| clock_path = utils.locate_file('cur_freq', node) |
| |
| governor = utils.read_one_line(governor_path) |
| logging.info('DVFS governor = %s', governor) |
| if not governor == 'simple_ondemand': |
| logging.error('Error: DVFS governor is not simple_ondemand.') |
| return self.handle_error('Governor is wrong.') |
| else: |
| clock_path = utils.locate_file('clock', node) |
| enable_path = utils.locate_file(enable_node, node) |
| |
| enable = utils.read_one_line(enable_path) |
| logging.info('DVFS enable = %s', enable) |
| if not enable == enable_value: |
| return self.handle_error('DVFS is not enabled. ') |
| |
| freqs_path = utils.locate_file('available_frequencies', node) |
| |
| # available_frequencies are always sorted in ascending order |
| # each line may contain one or multiple integers separated by spaces |
| min_freq = int(utils.read_one_line(freqs_path).split()[0]) |
| |
| # daisy_* (exynos5250) boards set idle frequency to 266000000 |
| # See: crbug.com/467401 and crosbug.com/p/19710 |
| if self._board.startswith('daisy'): |
| min_freq = 266000000 |
| |
| logging.info('Expecting idle DVFS clock = %u', min_freq) |
| tries = 0 |
| found = False |
| while not found and tries < 80: |
| time.sleep(0.25) |
| clock = int(utils.read_one_line(clock_path)) |
| if clock <= min_freq: |
| logging.info('Found idle DVFS clock = %u', clock) |
| found = True |
| break |
| |
| tries += 1 |
| if not found: |
| logging.error('Error: DVFS clock (%u) > min (%u)', clock, min_freq) |
| return self.handle_error('Did not see the min DVFS clock. ', |
| clock_path) |
| return '' |
| |
| def verify_graphics_fbc(self): |
| """ On systems which support FBC, check that we can get into FBC; |
| idle before doing so, and retry every second for 20 seconds.""" |
| logging.info('Running verify_graphics_fbc') |
| |
| # Link's FBC is disabled (crbug.com/338588). |
| # TODO(marcheu): remove this when/if we fix this bug. |
| board = utils.get_board() |
| if board == 'link': |
| return '' |
| |
| # Machines which don't have a monitor can't get FBC. |
| if utils.has_no_monitor(): |
| return '' |
| |
| if (self._gpu_type == 'haswell' or self._gpu_type == 'ivybridge' or |
| self._gpu_type == 'sandybridge'): |
| tries = 0 |
| found = False |
| param_path = self.get_valid_path(FBC_PATHS) |
| if not param_path: |
| return 'FBC_PATHS not found.' |
| while not found and tries < 20: |
| time.sleep(1) |
| with open(param_path, 'r') as fbc_info_file: |
| for line in fbc_info_file: |
| if re.search('FBC enabled', line): |
| found = True |
| break |
| |
| tries += 1 |
| if not found: |
| return self.handle_error('Did not see FBC enabled. ', |
| param_path) |
| return '' |
| |
| def verify_graphics_psr(self): |
| """ On systems which support PSR, check that we can get into PSR; |
| idle before doing so, and retry every second for 20 seconds.""" |
| logging.info('Running verify_graphics_psr') |
| |
| if utils.get_cpu_soc_family() != 'x86_64': |
| return '' |
| tries = 0 |
| found = False |
| param_path = self.get_valid_path(PSR_PATHS) |
| if not param_path: |
| logging.warning("PSR_PATHS not found.") |
| return '' |
| kernel_version = utils.get_kernel_version()[0:4].rstrip(".") |
| logging.info('Kernel version: %s', kernel_version) |
| # First check if PSR is enabled on the device so |
| # we can watch for the active values |
| with open(param_path, 'r') as psr_info_file: |
| match = None |
| try: |
| for line in psr_info_file: |
| match = re.search(r'Enabled: yes', line) |
| if match: |
| logging.info('PSR enabled') |
| break |
| if not match: |
| logging.warning('PSR not enabled') |
| return '' |
| except IOError as e: |
| num, strerror = e.args |
| # Newer kernels might report ENODEV when PSR not available. |
| if num == errno.ENODEV: |
| logging.warning('PSR not enabled') |
| return '' |
| else: |
| logging.error('While accessing %s', param_path) |
| logging.error(e) |
| return self.handle_error('Unexpected PSR read failure. ') |
| while not found and tries < 20: |
| time.sleep(1) |
| with open(param_path, 'r') as psr_info_file: |
| for line in psr_info_file: |
| # Kernels 4.4 and up |
| if common_utils.compare_versions(kernel_version, '4.4') != -1: |
| match = re.search(r'PSR status: .* \[SRDENT', line) |
| if match: |
| found = True |
| logging.info('Found active with kernel >= 4.4') |
| break |
| # 3.18 kernel |
| elif kernel_version == '3.18': |
| match = re.search(r'Performance_Counter: 0', line) |
| if match: |
| found = True |
| logging.info('Found active with 3.18 kernel') |
| break |
| # Older kernels (up to 3.14) |
| else: |
| match = re.search(r'Performance_Counter: ([\d])+', line) |
| if match and int(match.group(1)) > 0: |
| found = True |
| logging.info('Found active with kernel <= 3.14') |
| break |
| |
| tries += 1 |
| if not found: |
| return self.handle_error('Did not see PSR activity. ', param_path) |
| return '' |
| |
| def verify_graphics_gem_idle(self): |
| """ On systems which have i915, check that we can get all gem objects |
| to become idle (i.e. the i915_gem_active list or i915_gem_objects |
| client/process gem object counts need to go to 0); |
| idle before doing so, and retry every second for 20 seconds.""" |
| kernel_version = utils.get_kernel_version()[0:4].rstrip(".") |
| # Skip test on kernel 5.10 and above. |
| if common_utils.compare_versions(kernel_version, '5.10') != -1: |
| # The data needed for this test was removed in the 5.10 kernel. |
| # See b/179453336 for details. |
| logging.info('Skipping gem idle check on 5.10 and above') |
| return '' |
| |
| logging.info('Running verify_graphics_gem_idle') |
| if utils.get_cpu_soc_family() == 'x86_64': |
| tries = 0 |
| found = False |
| per_process_check = False |
| |
| gem_path = self.get_valid_path(GEM_PATHS) |
| if not gem_path: |
| gem_path = self.get_valid_path(GEM_OBJECTS_PATHS) |
| if gem_path: |
| per_process_check = True |
| else: |
| return 'GEM_PATHS not found.' |
| |
| # Checks 4.4 and later kernels |
| if per_process_check: |
| while not found and tries < 240: |
| time.sleep(0.25) |
| gem_objects_idle = False |
| gem_active_search = False |
| with open(gem_path, 'r') as gem_file: |
| for line in gem_file: |
| if gem_active_search: |
| if re.search('\(0 active,', line): |
| gem_objects_idle = True |
| else: |
| gem_objects_idle = False |
| break |
| elif line == '\n': |
| gem_active_search = True |
| if gem_objects_idle: |
| found = True |
| tries += 1 |
| |
| # Checks pre 4.4 kernels |
| else: |
| while not found and tries < 240: |
| time.sleep(0.25) |
| with open(gem_path, 'r') as gem_file: |
| for line in gem_file: |
| if re.search('Total 0 objects', line): |
| found = True |
| break |
| tries += 1 |
| if not found: |
| return self.handle_error('Did not reach 0 gem actives. ', |
| gem_path) |
| return '' |