blob: 9cfbc30881ddbeed2c6c931507a02ff25a8fe194 [file] [log] [blame] [edit]
# 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])