480 lines
20 KiB
Python
Executable File
480 lines
20 KiB
Python
Executable File
# 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']
|
|
RC6_PATHS = ['/sys/kernel/debug/dri/0/i915_drpc_info']
|
|
|
|
|
|
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."""
|
|
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 ''
|