Add graphics_Idle test

This adds a new graphics_Idle test which checks different things when the
GPU is idle:
- panel downclock is in use
- short blanking is enabled
- rc6 is in use
- dvfs goes to the lowest clock
- fbc works
- all gem objects are idle

BUG=chromium:361897, chromium:363747
TEST=run the test on lumpy, daisy, glimmer, rambi, falco

Change-Id: I93e860c6f2ed2adc2905a82e4885cd99bfdff112
Signed-off-by: Stéphane Marchesin <marcheu@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/197167
Reviewed-by: Ilja Friedel <ihf@chromium.org>
Tested-by: Ilja Friedel <ihf@chromium.org>
diff --git a/client/bin/site_utils.py b/client/bin/site_utils.py
index feb35fd..20a4918 100644
--- a/client/bin/site_utils.py
+++ b/client/bin/site_utils.py
@@ -7,6 +7,7 @@
 import logging, os, platform, re, signal, tempfile, time, uuid
 from autotest_lib.client.common_lib import error
 from autotest_lib.client.common_lib import utils
+from autotest_lib.client.bin import base_utils
 
 class TimeoutError(error.TestError):
     """Error raised when we time out when waiting on a condition."""
@@ -800,3 +801,30 @@
     for (path, value) in path_value_list:
         cmd = 'sudo echo %s > %s' % (value, path)
         utils.system(cmd)
+
+
+def get_gpu_family():
+    """Return the GPU family name"""
+    cpuarch = base_utils.get_cpu_soc_family()
+    if cpuarch == 'exynos5':
+        return 'mali'
+    if cpuarch == 'tegra':
+        return 'tegra'
+
+    pci_path = '/sys/bus/pci/devices/0000:00:02.0/device'
+
+    if not os.path.exists(pci_path):
+        raise error.TestError('PCI device 0000:00:02.0 not found')
+
+    device_id = int(utils.read_one_line(pci_path), 16)
+    intel_architecture = {
+        0xa011: 'pinetrail',
+        0x0106: 'sandybridge',
+        0x0126: 'sandybridge',
+        0x0156: 'ivybridge',
+        0x0166: 'ivybridge',
+        0x0a06: 'haswell',
+        0x0f31: 'baytrail',
+    }
+
+    return intel_architecture[device_id]
diff --git a/client/site_tests/graphics_Idle/control b/client/site_tests/graphics_Idle/control
new file mode 100644
index 0000000..8828091
--- /dev/null
+++ b/client/site_tests/graphics_Idle/control
@@ -0,0 +1,23 @@
+# 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.
+
+AUTHOR = 'chromeos-gfx'
+NAME = "graphics_Idle"
+SUITE = "graphics"
+PURPOSE = "Verify that graphics behaves as expected on idle."
+CRITERIA = """
+This test will fail if we don't see the appropriate GPU idle states.
+It depends on the browser not using graphics continuously in guest/kiosk mode.
+"""
+TIME = "SHORT"
+TEST_CATEGORY = "Functional"
+TEST_CLASS = "graphics"
+TEST_TYPE = "client"
+
+DOC = """
+This test checks that the GPU is in the proper state when idle (RC6, panel,
+clocks...)
+"""
+
+job.run_test('graphics_Idle')
diff --git a/client/site_tests/graphics_Idle/graphics_Idle.py b/client/site_tests/graphics_Idle/graphics_Idle.py
new file mode 100755
index 0000000..bbfacb3
--- /dev/null
+++ b/client/site_tests/graphics_Idle/graphics_Idle.py
@@ -0,0 +1,207 @@
+# 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 logging, os, re, 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.cros import cros_logging
+from autotest_lib.client.common_lib.cros import chrome
+
+
+class graphics_Idle(test.test):
+    """Class for graphics_Idle.  See 'control' for details."""
+    version = 1
+
+
+    def run_once(self):
+        # We use kiosk mode to make sure Chrome is idle.
+        with chrome.Chrome(logged_in=False, extra_browser_args=['--kiosk']):
+            self._gpu_type = utils.get_gpu_family()
+            errors = ''
+            errors += self.verify_lvds_downclock()
+            errors += self.verify_short_blanking()
+            errors += self.verify_graphics_rc6()
+            errors += self.verify_graphics_dvfs()
+            errors += self.verify_graphics_fbc()
+            errors += self.verify_graphics_gem_idle()
+            if errors:
+                raise error.TestFail(errors)
+
+
+    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 == 'parrot' or board == 'parrot_ivb' 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'):
+            logging.error('Error: LVDS downclock quirk not applied.')
+            return 'LVDS downclock quirk not applied. '
+
+        return ''
+
+
+    def verify_short_blanking(self):
+        """On some baytrail systems, checks the kernel log for a message that a
+        short blanking mode has been added."""
+        logging.info('Running verify_short_blanking')
+        board = utils.get_board()
+        # TODO(marcheu): add more BYT machines
+        if (board != 'rambi' and board != 'squawks'):
+            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'):
+            logging.error('Error: short blanking not added.')
+            return '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')
+        if (self._gpu_type == 'haswell' or self._gpu_type == 'ivybridge' or
+            self._gpu_type == 'sandybridge'):
+            tries = 0
+            found = False
+            while found == False and tries < 20:
+                time.sleep(1)
+                param_path = '/sys/kernel/debug/dri/0/i915_drpc_info'
+                if not os.path.exists(param_path):
+                    logging.error('Error: %s not found.', param_path)
+                    break
+                drpc_info_file = open (param_path, "r")
+                for line in drpc_info_file:
+                    match = re.search(r'Current RC state: (.*)', line)
+                    if match:
+                        found = match.group(1) != 'on'
+                        break
+
+                tries += 1
+                drpc_info_file.close()
+
+            if not found:
+                logging.error('Error: did not see the GPU in RC6.')
+                return 'Did not see the GPU in RC6. '
+
+        return ''
+
+
+    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')
+        if self._gpu_type == 'mali':
+            tries = 0
+            found = False
+            while found == False and tries < 20:
+                time.sleep(1)
+                param_path = '/sys/devices/11800000.mali/clock'
+                if not os.path.exists(param_path):
+                    logging.error('Error: %s not found.', param_path)
+                    break
+                clock = utils.read_file(param_path)
+                if int(clock) <= 266000000:
+                    found = 1
+                    break
+
+                tries += 1
+
+            if not found:
+                logging.error('Error: did not see the min DVFS clock')
+                return 'Did not see the min DVFS clock. '
+
+        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 ''
+
+        if (self._gpu_type == 'haswell' or self._gpu_type == 'ivybridge' or
+            self._gpu_type == 'sandybridge'):
+            tries = 0
+            found = False
+            while found == False and tries < 20:
+                time.sleep(1)
+                # Kernel 3.4 has i915_fbc, kernel 3.8+ has i915_fbc_status,
+                # so we check for both.
+                param_path = '/sys/kernel/debug/dri/0/i915_fbc_status'
+                if not os.path.exists(param_path):
+                    param_path = '/sys/kernel/debug/dri/0/i915_fbc'
+                if not os.path.exists(param_path):
+                    logging.error('Error: %s not found.', param_path)
+                    break
+                fbc_info_file = open (param_path, "r")
+                for line in fbc_info_file:
+                    match = re.search('FBC enabled', line)
+                    if match:
+                        found = 1
+                        break
+
+                tries += 1
+                fbc_info_file.close()
+
+            if not found:
+                logging.error('Error: did not see FBC enabled.')
+                return 'Did not see FBC enabled. '
+
+
+        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 need to go to 0);
+        idle before doing so, and retry every second for 20 seconds."""
+        logging.info('Running verify_graphics_gem_idle')
+        if (self._gpu_type == 'baytrail' or self._gpu_type == 'haswell' or
+            self._gpu_type == 'ivybridge' or self._gpu_type == 'pinetrail' or
+            self._gpu_type == 'sandybridge'):
+            tries = 0
+            found = False
+            while found == False and tries < 20:
+                time.sleep(1)
+                gem_path = '/sys/kernel/debug/dri/0/i915_gem_active'
+                if not os.path.exists(gem_path):
+                    logging.error('Error: %s not found.', gem_path)
+                    break
+                gem_file = open(gem_path, "r")
+                for line in gem_file:
+                    found = re.search('Total 0 objects', line)
+                    if found:
+                        break
+
+                tries += 1
+                gem_file.close()
+
+            if not found:
+                logging.error('Error: did not reach 0 gem actives.')
+                return 'Did not reach 0 gem actives. '
+
+        return ''
+
+