# Copyright 2016 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 from autotest_lib.client.bin import test, utils from autotest_lib.client.common_lib import error from autotest_lib.client.cros import sys_power class kernel_CheckArmErrata(test.test): """ Test for the presence of ARM errata fixes. See control file for more details. """ version = 1 SECS_TO_SUSPEND = 15 @staticmethod def _parse_cpu_info(cpuinfo_str): """ Parse the contents of /proc/cpuinfo This will return a dict of dicts with info about all the CPUs. :param cpuinfo_str: The contents of /proc/cpuinfo as a string. :return: A dictionary of dictionaries; top key is processor ID and secondary key is info from cpuinfo about that processor. >>> cpuinfo = kernel_CheckArmErrata._parse_cpu_info( ... '''processor : 0 ... model name : ARMv7 Processor rev 1 (v7l) ... Features : swp half thumb fastmult vfp edsp thumbee neon ... ... CPU implementer : 0x41 ... CPU architecture: 7 ... CPU variant : 0x0 ... CPU part : 0xc0d ... CPU revision : 1 ... ... processor : 1 ... model name : ARMv7 Processor rev 1 (v7l) ... Features : swp half thumb fastmult vfp edsp thumbee neon ... ... CPU implementer : 0x41 ... CPU architecture: 7 ... CPU variant : 0x0 ... CPU part : 0xc0d ... CPU revision : 1 ... ... Hardware : Rockchip (Device Tree) ... Revision : 0000 ... Serial : 0000000000000000''') >>> cpuinfo == { ... 0: {"CPU architecture": 7, ... "CPU implementer": 65, ... "CPU part": 3085, ... "CPU revision": 1, ... "CPU variant": 0, ... "Features": "swp half thumb fastmult vfp edsp thumbee neon ...", ... "model name": "ARMv7 Processor rev 1 (v7l)", ... "processor": 0}, ... 1: {"CPU architecture": 7, ... "CPU implementer": 65, ... "CPU part": 3085, ... "CPU revision": 1, ... "CPU variant": 0, ... "Features": "swp half thumb fastmult vfp edsp thumbee neon ...", ... "model name": "ARMv7 Processor rev 1 (v7l)", ... "processor": 1} ... } True """ cpuinfo = {} processor = None for info_line in cpuinfo_str.splitlines(): # Processors are separated by blank lines if not info_line: processor = None continue key, _, val = info_line.partition(':') key = key.strip() val = val.strip() # Try to convert to int... try: val = int(val, 0) except ValueError: pass # Handle processor special. if key == "processor": processor = int(val) cpuinfo[processor] = { "processor": processor } continue # Skip over any info we have no processor for if processor is None: continue cpuinfo[processor][key] = val return cpuinfo @staticmethod def _get_regid_to_val(cpu_id): """ Parse the contents of /sys/kernel/debug/arm_coprocessor_debug This will read /sys/kernel/debug/arm_coprocessor_debug and create a dictionary mapping register IDs, which look like: "(p15, 0, c15, c0, 1)" ...to values (as integers). :return: A dictionary mapping string register IDs to int value. """ try: arm_debug_info = utils.run( "taskset -c %d cat /sys/kernel/debug/arm_coprocessor_debug" % cpu_id).stdout.strip() except error.CmdError: arm_debug_info = "" regid_to_val = {} for line in arm_debug_info.splitlines(): # Each line is supposed to show the CPU number; confirm it's right if not line.startswith("CPU %d: " % cpu_id): raise error.TestError( "arm_coprocessor_debug error: CPU %d != %s" % (cpu_id, line.split(":")[0])) _, _, regid, val = line.split(":") regid_to_val[regid.strip()] = int(val, 0) return regid_to_val def _check_one_cortex_a12(self, cpuinfo): """ Check the errata for a Cortex-A12. :param cpuinfo: The CPU info for one CPU. See _parse_cpu_info for the format. >>> _testobj._get_regid_to_val = lambda cpu_id: {} >>> try: ... _testobj._check_one_cortex_a12({ ... "processor": 2, ... "model name": "ARMv7 Processor rev 1 (v7l)", ... "CPU implementer": ord("A"), ... "CPU part": 0xc0d, ... "CPU variant": 0, ... "CPU revision": 1}) ... except Exception: ... import traceback ... print traceback.format_exc(), Traceback (most recent call last): ... TestError: Kernel didn't provide register vals >>> _testobj._get_regid_to_val = lambda cpu_id: \ {"(p15, 0, c15, c0, 1)": 0, "(p15, 0, c15, c0, 2)": 0} >>> try: ... _testobj._check_one_cortex_a12({ ... "processor": 2, ... "model name": "ARMv7 Processor rev 1 (v7l)", ... "CPU implementer": ord("A"), ... "CPU part": 0xc0d, ... "CPU variant": 0, ... "CPU revision": 1}) ... except Exception: ... import traceback ... print traceback.format_exc(), Traceback (most recent call last): ... TestError: Missing bit 12 for erratum 818325 / 852422: 0x00000000 >>> _testobj._get_regid_to_val = lambda cpu_id: \ { "(p15, 0, c15, c0, 1)": (1 << 12) | (1 << 24), \ "(p15, 0, c15, c0, 2)": (1 << 1)} >>> _info_io.seek(0); _info_io.truncate() >>> _testobj._check_one_cortex_a12({ ... "processor": 2, ... "model name": "ARMv7 Processor rev 1 (v7l)", ... "CPU implementer": ord("A"), ... "CPU part": 0xc0d, ... "CPU variant": 0, ... "CPU revision": 1}) >>> "good" in _info_io.getvalue() True >>> _testobj._check_one_cortex_a12({ ... "processor": 2, ... "model name": "ARMv7 Processor rev 1 (v7l)", ... "CPU implementer": ord("A"), ... "CPU part": 0xc0d, ... "CPU variant": 0, ... "CPU revision": 2}) Traceback (most recent call last): ... TestError: Unexpected A12 revision: r0p2 """ cpu_id = cpuinfo["processor"] variant = cpuinfo.get("CPU variant", -1) revision = cpuinfo.get("CPU revision", -1) # Handy to express this the way ARM does rev_str = "r%dp%d" % (variant, revision) # We don't ever expect an A12 newer than r0p1. If we ever see one # then maybe the errata was fixed. if rev_str not in ("r0p0", "r0p1"): raise error.TestError("Unexpected A12 revision: %s" % rev_str) regid_to_val = self._get_regid_to_val(cpu_id) # Getting this means we're missing the change to expose debug # registers via arm_coprocessor_debug if not regid_to_val: raise error.TestError("Kernel didn't provide register vals") # Erratum 818325 applies to old A12s and erratum 852422 to newer. # Fix is to set bit 12 in diag register. Confirm that's done. diag_reg = regid_to_val.get("(p15, 0, c15, c0, 1)") if diag_reg is None: raise error.TestError("Kernel didn't provide diag register") elif not (diag_reg & (1 << 12)): raise error.TestError( "Missing bit 12 for erratum 818325 / 852422: %#010x" % diag_reg) logging.info("CPU %d: erratum 818325 / 852422 good", cpu_id) # Erratum 821420 applies to all A12s. Make sure bit 1 of the # internal feature register is set. int_feat_reg = regid_to_val.get("(p15, 0, c15, c0, 2)") if int_feat_reg is None: raise error.TestError("Kernel didn't provide internal feature reg") elif not (int_feat_reg & (1 << 1)): raise error.TestError( "Missing bit 1 for erratum 821420: %#010x" % int_feat_reg) logging.info("CPU %d: erratum 821420 good", cpu_id) # Erratum 825619 applies to all A12s. Need bit 24 in diag reg. diag_reg = regid_to_val.get("(p15, 0, c15, c0, 1)") if diag_reg is None: raise error.TestError("Kernel didn't provide diag register") elif not (diag_reg & (1 << 24)): raise error.TestError( "Missing bit 24 for erratum 825619: %#010x" % diag_reg) logging.info("CPU %d: erratum 825619 good", cpu_id) def _check_one_cortex_a17(self, cpuinfo): """ Check the errata for a Cortex-A17. :param cpuinfo: The CPU info for one CPU. See _parse_cpu_info for the format. >>> _testobj._get_regid_to_val = lambda cpu_id: {} >>> try: ... _testobj._check_one_cortex_a17({ ... "processor": 2, ... "model name": "ARMv7 Processor rev 1 (v7l)", ... "CPU implementer": ord("A"), ... "CPU part": 0xc0e, ... "CPU variant": 1, ... "CPU revision": 1}) ... except Exception: ... import traceback ... print traceback.format_exc(), Traceback (most recent call last): ... TestError: Kernel didn't provide register vals >>> _testobj._get_regid_to_val = lambda cpu_id: \ {"(p15, 0, c15, c0, 1)": 0} >>> try: ... _testobj._check_one_cortex_a17({ ... "processor": 2, ... "model name": "ARMv7 Processor rev 1 (v7l)", ... "CPU implementer": ord("A"), ... "CPU part": 0xc0e, ... "CPU variant": 1, ... "CPU revision": 1}) ... except Exception: ... import traceback ... print traceback.format_exc(), Traceback (most recent call last): ... TestError: Missing bit 24 for erratum 852421: 0x00000000 >>> _testobj._get_regid_to_val = lambda cpu_id: \ {"(p15, 0, c15, c0, 1)": (1 << 12) | (1 << 24)} >>> _info_io.seek(0); _info_io.truncate() >>> _testobj._check_one_cortex_a17({ ... "processor": 2, ... "model name": "ARMv7 Processor rev 1 (v7l)", ... "CPU implementer": ord("A"), ... "CPU part": 0xc0e, ... "CPU variant": 1, ... "CPU revision": 2}) >>> "good" in _info_io.getvalue() True >>> _info_io.seek(0); _info_io.truncate() >>> _testobj._check_one_cortex_a17({ ... "processor": 2, ... "model name": "ARMv7 Processor rev 1 (v7l)", ... "CPU implementer": ord("A"), ... "CPU part": 0xc0e, ... "CPU variant": 2, ... "CPU revision": 0}) >>> print _info_io.getvalue() CPU 2: new A17 (r2p0) = no errata """ cpu_id = cpuinfo["processor"] variant = cpuinfo.get("CPU variant", -1) revision = cpuinfo.get("CPU revision", -1) # Handy to express this the way ARM does rev_str = "r%dp%d" % (variant, revision) regid_to_val = self._get_regid_to_val(cpu_id) # Erratum 852421 and 852423 apply to "r1p0", "r1p1", "r1p2" if rev_str in ("r1p0", "r1p1", "r1p2"): # Getting this means we're missing the change to expose debug # registers via arm_coprocessor_debug if not regid_to_val: raise error.TestError("Kernel didn't provide register vals") diag_reg = regid_to_val.get("(p15, 0, c15, c0, 1)") if diag_reg is None: raise error.TestError("Kernel didn't provide diag register") elif not (diag_reg & (1 << 24)): raise error.TestError( "Missing bit 24 for erratum 852421: %#010x" % diag_reg) logging.info("CPU %d: erratum 852421 good",cpu_id) diag_reg = regid_to_val.get("(p15, 0, c15, c0, 1)") if diag_reg is None: raise error.TestError("Kernel didn't provide diag register") elif not (diag_reg & (1 << 12)): raise error.TestError( "Missing bit 12 for erratum 852423: %#010x" % diag_reg) logging.info("CPU %d: erratum 852423 good",cpu_id) else: logging.info("CPU %d: new A17 (%s) = no errata", cpu_id, rev_str) def _check_one_armv7(self, cpuinfo): """ Check the errata for one ARMv7 CPU. :param cpuinfo: The CPU info for one CPU. See _parse_cpu_info for the format. >>> _info_io.seek(0); _info_io.truncate() >>> _testobj._check_one_cpu({ ... "processor": 2, ... "model name": "ARMv7 Processor rev 1 (v7l)", ... "CPU implementer": ord("B"), ... "CPU part": 0xc99, ... "CPU variant": 7, ... "CPU revision": 9}) >>> print _info_io.getvalue() CPU 2: No errata tested: imp=0x42, part=0xc99 """ # Get things so we don't worry about key errors below cpu_id = cpuinfo["processor"] implementer = cpuinfo.get("CPU implementer", "?") part = cpuinfo.get("CPU part", 0xfff) if implementer == ord("A") and part == 0xc0d: self._check_one_cortex_a12(cpuinfo) elif implementer == ord("A") and part == 0xc0e: self._check_one_cortex_a17(cpuinfo) else: logging.info("CPU %d: No errata tested: imp=%#04x, part=%#05x", cpu_id, implementer, part) def _check_one_cpu(self, cpuinfo): """ Check the errata for one CPU. :param cpuinfo: The CPU info for one CPU. See _parse_cpu_info for the format. >>> _info_io.seek(0); _info_io.truncate() >>> _testobj._check_one_cpu({ ... "processor": 0, ... "model name": "LEGv7 Processor"}) >>> print _info_io.getvalue() CPU 0: Not an ARM, skipping: LEGv7 Processor >>> _info_io.seek(0); _info_io.truncate() >>> _testobj._check_one_cpu({ ... "processor": 1, ... "model name": "ARMv99 Processor"}) >>> print _info_io.getvalue() CPU 1: No errata tested; model: ARMv99 Processor """ if cpuinfo["model name"].startswith("ARMv7"): self._check_one_armv7(cpuinfo) elif cpuinfo["model name"].startswith("ARM"): logging.info("CPU %d: No errata tested; model: %s", cpuinfo["processor"], cpuinfo["model name"]) else: logging.info("CPU %d: Not an ARM, skipping: %s", cpuinfo["processor"], cpuinfo["model name"]) def run_once(self, doctest=False): """ Run the test. :param doctest: If true we will just run our doctests. We'll set these globals to help our tests: - _testobj: An instance of this object. - _info_io: A StringIO that's stuffed into logging.info so we can see what was written there. ... """ if doctest: import doctest, inspect, StringIO global _testobj, _info_io # Keep a backup of _get_regid_to_val() since tests will clobber. old_get_regid_to_val = self._get_regid_to_val # Mock out logging.info to help tests. _info_io = StringIO.StringIO() old_logging_info = logging.info logging.info = lambda fmt, *args: _info_io.write(fmt % args) # Stash an object in a global to help tests _testobj = self try: failure_count, test_count = doctest.testmod( inspect.getmodule(self), optionflags=doctest.ELLIPSIS) finally: logging.info = old_logging_info # Technically don't need to clean this up, but let's be nice. self._get_regid_to_val = old_get_regid_to_val logging.info("Doctest ran %d tests, had %d failures", test_count, failure_count) return cpuinfo = self._parse_cpu_info(utils.read_file('/proc/cpuinfo')) for cpu_id in sorted(cpuinfo.keys()): self._check_one_cpu(cpuinfo[cpu_id]) sys_power.do_suspend(self.SECS_TO_SUSPEND) for cpu_id in sorted(cpuinfo.keys()): self._check_one_cpu(cpuinfo[cpu_id])