Home | History | Annotate | Download | only in kernel_CheckArmErrata
      1 # Copyright 2016 The Chromium OS Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 import logging
      6 
      7 from autotest_lib.client.bin import test, utils
      8 from autotest_lib.client.common_lib import error
      9 
     10 from autotest_lib.client.cros import sys_power
     11 
     12 
     13 class kernel_CheckArmErrata(test.test):
     14     """
     15     Test for the presence of ARM errata fixes.
     16 
     17     See control file for more details.
     18     """
     19     version = 1
     20     SECS_TO_SUSPEND = 15
     21 
     22     @staticmethod
     23     def _parse_cpu_info(cpuinfo_str):
     24         """
     25         Parse the contents of /proc/cpuinfo
     26 
     27         This will return a dict of dicts with info about all the CPUs.
     28 
     29         :param cpuinfo_str: The contents of /proc/cpuinfo as a string.
     30         :return: A dictionary of dictionaries; top key is processor ID and
     31                  secondary key is info from cpuinfo about that processor.
     32 
     33         >>> cpuinfo = kernel_CheckArmErrata._parse_cpu_info(
     34         ... '''processor       : 0
     35         ... model name      : ARMv7 Processor rev 1 (v7l)
     36         ... Features        : swp half thumb fastmult vfp edsp thumbee neon ...
     37         ... CPU implementer : 0x41
     38         ... CPU architecture: 7
     39         ... CPU variant     : 0x0
     40         ... CPU part        : 0xc0d
     41         ... CPU revision    : 1
     42         ...
     43         ... processor       : 1
     44         ... model name      : ARMv7 Processor rev 1 (v7l)
     45         ... Features        : swp half thumb fastmult vfp edsp thumbee neon ...
     46         ... CPU implementer : 0x41
     47         ... CPU architecture: 7
     48         ... CPU variant     : 0x0
     49         ... CPU part        : 0xc0d
     50         ... CPU revision    : 1
     51         ...
     52         ... Hardware        : Rockchip (Device Tree)
     53         ... Revision        : 0000
     54         ... Serial          : 0000000000000000''')
     55         >>> cpuinfo == {
     56         ... 0: {"CPU architecture": 7,
     57         ...     "CPU implementer": 65,
     58         ...     "CPU part": 3085,
     59         ...     "CPU revision": 1,
     60         ...     "CPU variant": 0,
     61         ...     "Features": "swp half thumb fastmult vfp edsp thumbee neon ...",
     62         ...     "model name": "ARMv7 Processor rev 1 (v7l)",
     63         ...     "processor": 0},
     64         ... 1: {"CPU architecture": 7,
     65         ...     "CPU implementer": 65,
     66         ...     "CPU part": 3085,
     67         ...     "CPU revision": 1,
     68         ...     "CPU variant": 0,
     69         ...     "Features": "swp half thumb fastmult vfp edsp thumbee neon ...",
     70         ...     "model name": "ARMv7 Processor rev 1 (v7l)",
     71         ...     "processor": 1}
     72         ... }
     73         True
     74         """
     75         cpuinfo = {}
     76         processor = None
     77         for info_line in cpuinfo_str.splitlines():
     78             # Processors are separated by blank lines
     79             if not info_line:
     80                 processor = None
     81                 continue
     82 
     83             key, _, val = info_line.partition(':')
     84             key = key.strip()
     85             val = val.strip()
     86 
     87             # Try to convert to int...
     88             try:
     89                 val = int(val, 0)
     90             except ValueError:
     91                 pass
     92 
     93             # Handle processor special.
     94             if key == "processor":
     95                 processor = int(val)
     96                 cpuinfo[processor] = { "processor": processor }
     97                 continue
     98 
     99             # Skip over any info we have no processor for
    100             if processor is None:
    101                 continue
    102 
    103             cpuinfo[processor][key] = val
    104 
    105         return cpuinfo
    106 
    107     @staticmethod
    108     def _get_regid_to_val(cpu_id):
    109         """
    110         Parse the contents of /sys/kernel/debug/arm_coprocessor_debug
    111 
    112         This will read /sys/kernel/debug/arm_coprocessor_debug and create
    113         a dictionary mapping register IDs, which look like:
    114           "(p15, 0, c15, c0, 1)"
    115         ...to values (as integers).
    116 
    117         :return: A dictionary mapping string register IDs to int value.
    118         """
    119         try:
    120             arm_debug_info = utils.run(
    121                 "taskset -c %d cat /sys/kernel/debug/arm_coprocessor_debug" %
    122                 cpu_id).stdout.strip()
    123         except error.CmdError:
    124             arm_debug_info = ""
    125 
    126         regid_to_val = {}
    127         for line in arm_debug_info.splitlines():
    128             # Each line is supposed to show the CPU number; confirm it's right
    129             if not line.startswith("CPU %d: " % cpu_id):
    130                 raise error.TestError(
    131                     "arm_coprocessor_debug error: CPU %d != %s" %
    132                     (cpu_id, line.split(":")[0]))
    133 
    134             _, _, regid, val = line.split(":")
    135             regid_to_val[regid.strip()] = int(val, 0)
    136 
    137         return regid_to_val
    138 
    139     def _check_one_cortex_a12(self, cpuinfo):
    140         """
    141         Check the errata for a Cortex-A12.
    142 
    143         :param cpuinfo: The CPU info for one CPU.  See _parse_cpu_info for
    144                         the format.
    145 
    146         >>> _testobj._get_regid_to_val = lambda cpu_id: {}
    147         >>> try:
    148         ...     _testobj._check_one_cortex_a12({
    149         ...         "processor": 2,
    150         ...         "model name": "ARMv7 Processor rev 1 (v7l)",
    151         ...         "CPU implementer": ord("A"),
    152         ...         "CPU part": 0xc0d,
    153         ...         "CPU variant": 0,
    154         ...         "CPU revision": 1})
    155         ... except Exception:
    156         ...     import traceback
    157         ...     print traceback.format_exc(),
    158         Traceback (most recent call last):
    159         ...
    160         TestError: Kernel didn't provide register vals
    161 
    162         >>> _testobj._get_regid_to_val = lambda cpu_id: \
    163                {"(p15, 0, c15, c0, 1)": 0, "(p15, 0, c15, c0, 2)": 0}
    164         >>> try:
    165         ...     _testobj._check_one_cortex_a12({
    166         ...         "processor": 2,
    167         ...         "model name": "ARMv7 Processor rev 1 (v7l)",
    168         ...         "CPU implementer": ord("A"),
    169         ...         "CPU part": 0xc0d,
    170         ...         "CPU variant": 0,
    171         ...         "CPU revision": 1})
    172         ... except Exception:
    173         ...     import traceback
    174         ...     print traceback.format_exc(),
    175         Traceback (most recent call last):
    176         ...
    177         TestError: Missing bit 12 for erratum 818325 / 852422: 0x00000000
    178 
    179         >>> _testobj._get_regid_to_val = lambda cpu_id: \
    180                { "(p15, 0, c15, c0, 1)": (1 << 12) | (1 << 24), \
    181                  "(p15, 0, c15, c0, 2)": (1 << 1)}
    182         >>> _info_io.seek(0); _info_io.truncate()
    183         >>> _testobj._check_one_cortex_a12({
    184         ...    "processor": 2,
    185         ...     "model name": "ARMv7 Processor rev 1 (v7l)",
    186         ...     "CPU implementer": ord("A"),
    187         ...     "CPU part": 0xc0d,
    188         ...     "CPU variant": 0,
    189         ...     "CPU revision": 1})
    190         >>> "good" in _info_io.getvalue()
    191         True
    192 
    193         >>> _testobj._check_one_cortex_a12({
    194         ...    "processor": 2,
    195         ...     "model name": "ARMv7 Processor rev 1 (v7l)",
    196         ...     "CPU implementer": ord("A"),
    197         ...     "CPU part": 0xc0d,
    198         ...     "CPU variant": 0,
    199         ...     "CPU revision": 2})
    200         Traceback (most recent call last):
    201         ...
    202         TestError: Unexpected A12 revision: r0p2
    203         """
    204         cpu_id = cpuinfo["processor"]
    205         variant = cpuinfo.get("CPU variant", -1)
    206         revision = cpuinfo.get("CPU revision", -1)
    207 
    208         # Handy to express this the way ARM does
    209         rev_str = "r%dp%d" % (variant, revision)
    210 
    211         # We don't ever expect an A12 newer than r0p1.  If we ever see one
    212         # then maybe the errata was fixed.
    213         if rev_str not in ("r0p0", "r0p1"):
    214             raise error.TestError("Unexpected A12 revision: %s" % rev_str)
    215 
    216         regid_to_val = self._get_regid_to_val(cpu_id)
    217 
    218         # Getting this means we're missing the change to expose debug
    219         # registers via arm_coprocessor_debug
    220         if not regid_to_val:
    221             raise error.TestError("Kernel didn't provide register vals")
    222 
    223         # Erratum 818325 applies to old A12s and erratum 852422 to newer.
    224         # Fix is to set bit 12 in diag register.  Confirm that's done.
    225         diag_reg = regid_to_val.get("(p15, 0, c15, c0, 1)")
    226         if diag_reg is None:
    227             raise error.TestError("Kernel didn't provide diag register")
    228         elif not (diag_reg & (1 << 12)):
    229             raise error.TestError(
    230                 "Missing bit 12 for erratum 818325 / 852422: %#010x" %
    231                 diag_reg)
    232         logging.info("CPU %d: erratum 818325 / 852422 good", cpu_id)
    233 
    234         # Erratum 821420 applies to all A12s.  Make sure bit 1 of the
    235         # internal feature register is set.
    236         int_feat_reg = regid_to_val.get("(p15, 0, c15, c0, 2)")
    237         if int_feat_reg is None:
    238             raise error.TestError("Kernel didn't provide internal feature reg")
    239         elif not (int_feat_reg & (1 << 1)):
    240             raise error.TestError(
    241                 "Missing bit 1 for erratum 821420: %#010x" % int_feat_reg)
    242         logging.info("CPU %d: erratum 821420 good", cpu_id)
    243 
    244         # Erratum 825619 applies to all A12s.  Need bit 24 in diag reg.
    245         diag_reg = regid_to_val.get("(p15, 0, c15, c0, 1)")
    246         if diag_reg is None:
    247             raise error.TestError("Kernel didn't provide diag register")
    248         elif not (diag_reg & (1 << 24)):
    249             raise error.TestError(
    250                 "Missing bit 24 for erratum 825619: %#010x" % diag_reg)
    251         logging.info("CPU %d: erratum 825619 good", cpu_id)
    252 
    253     def _check_one_cortex_a17(self, cpuinfo):
    254         """
    255         Check the errata for a Cortex-A17.
    256 
    257         :param cpuinfo: The CPU info for one CPU.  See _parse_cpu_info for
    258                         the format.
    259 
    260         >>> _testobj._get_regid_to_val = lambda cpu_id: {}
    261         >>> try:
    262         ...     _testobj._check_one_cortex_a17({
    263         ...         "processor": 2,
    264         ...         "model name": "ARMv7 Processor rev 1 (v7l)",
    265         ...         "CPU implementer": ord("A"),
    266         ...         "CPU part": 0xc0e,
    267         ...         "CPU variant": 1,
    268         ...         "CPU revision": 1})
    269         ... except Exception:
    270         ...     import traceback
    271         ...     print traceback.format_exc(),
    272         Traceback (most recent call last):
    273         ...
    274         TestError: Kernel didn't provide register vals
    275 
    276         >>> _testobj._get_regid_to_val = lambda cpu_id: \
    277                {"(p15, 0, c15, c0, 1)": 0}
    278         >>> try:
    279         ...     _testobj._check_one_cortex_a17({
    280         ...         "processor": 2,
    281         ...         "model name": "ARMv7 Processor rev 1 (v7l)",
    282         ...         "CPU implementer": ord("A"),
    283         ...         "CPU part": 0xc0e,
    284         ...         "CPU variant": 1,
    285         ...         "CPU revision": 1})
    286         ... except Exception:
    287         ...     import traceback
    288         ...     print traceback.format_exc(),
    289         Traceback (most recent call last):
    290         ...
    291         TestError: Missing bit 24 for erratum 852421: 0x00000000
    292 
    293         >>> _testobj._get_regid_to_val = lambda cpu_id: \
    294                {"(p15, 0, c15, c0, 1)": (1 << 12) | (1 << 24)}
    295         >>> _info_io.seek(0); _info_io.truncate()
    296         >>> _testobj._check_one_cortex_a17({
    297         ...    "processor": 2,
    298         ...     "model name": "ARMv7 Processor rev 1 (v7l)",
    299         ...     "CPU implementer": ord("A"),
    300         ...     "CPU part": 0xc0e,
    301         ...     "CPU variant": 1,
    302         ...     "CPU revision": 2})
    303         >>> "good" in _info_io.getvalue()
    304         True
    305 
    306         >>> _info_io.seek(0); _info_io.truncate()
    307         >>> _testobj._check_one_cortex_a17({
    308         ...    "processor": 2,
    309         ...     "model name": "ARMv7 Processor rev 1 (v7l)",
    310         ...     "CPU implementer": ord("A"),
    311         ...     "CPU part": 0xc0e,
    312         ...     "CPU variant": 2,
    313         ...     "CPU revision": 0})
    314         >>> print _info_io.getvalue()
    315         CPU 2: new A17 (r2p0) = no errata
    316         """
    317         cpu_id = cpuinfo["processor"]
    318         variant = cpuinfo.get("CPU variant", -1)
    319         revision = cpuinfo.get("CPU revision", -1)
    320 
    321         # Handy to express this the way ARM does
    322         rev_str = "r%dp%d" % (variant, revision)
    323 
    324         regid_to_val = self._get_regid_to_val(cpu_id)
    325 
    326         # Erratum 852421 and 852423 apply to "r1p0", "r1p1", "r1p2"
    327         if rev_str in ("r1p0", "r1p1", "r1p2"):
    328             # Getting this means we're missing the change to expose debug
    329             # registers via arm_coprocessor_debug
    330             if not regid_to_val:
    331                 raise error.TestError("Kernel didn't provide register vals")
    332 
    333             diag_reg = regid_to_val.get("(p15, 0, c15, c0, 1)")
    334             if diag_reg is None:
    335                 raise error.TestError("Kernel didn't provide diag register")
    336             elif not (diag_reg & (1 << 24)):
    337                 raise error.TestError(
    338                     "Missing bit 24 for erratum 852421: %#010x" % diag_reg)
    339             logging.info("CPU %d: erratum 852421 good",cpu_id)
    340 
    341             diag_reg = regid_to_val.get("(p15, 0, c15, c0, 1)")
    342             if diag_reg is None:
    343                 raise error.TestError("Kernel didn't provide diag register")
    344             elif not (diag_reg & (1 << 12)):
    345                 raise error.TestError(
    346                     "Missing bit 12 for erratum 852423: %#010x" % diag_reg)
    347             logging.info("CPU %d: erratum 852423 good",cpu_id)
    348         else:
    349             logging.info("CPU %d: new A17 (%s) = no errata", cpu_id, rev_str)
    350 
    351     def _check_one_armv7(self, cpuinfo):
    352         """
    353         Check the errata for one ARMv7 CPU.
    354 
    355         :param cpuinfo: The CPU info for one CPU.  See _parse_cpu_info for
    356                         the format.
    357 
    358         >>> _info_io.seek(0); _info_io.truncate()
    359         >>> _testobj._check_one_cpu({
    360         ...     "processor": 2,
    361         ...     "model name": "ARMv7 Processor rev 1 (v7l)",
    362         ...     "CPU implementer": ord("B"),
    363         ...     "CPU part": 0xc99,
    364         ...     "CPU variant": 7,
    365         ...     "CPU revision": 9})
    366         >>> print _info_io.getvalue()
    367         CPU 2: No errata tested: imp=0x42, part=0xc99
    368         """
    369         # Get things so we don't worry about key errors below
    370         cpu_id = cpuinfo["processor"]
    371         implementer = cpuinfo.get("CPU implementer", "?")
    372         part = cpuinfo.get("CPU part", 0xfff)
    373 
    374         if implementer == ord("A") and part == 0xc0d:
    375             self._check_one_cortex_a12(cpuinfo)
    376         elif implementer == ord("A") and part == 0xc0e:
    377             self._check_one_cortex_a17(cpuinfo)
    378         else:
    379             logging.info("CPU %d: No errata tested: imp=%#04x, part=%#05x",
    380                          cpu_id, implementer, part)
    381 
    382     def _check_one_cpu(self, cpuinfo):
    383         """
    384         Check the errata for one CPU.
    385 
    386         :param cpuinfo: The CPU info for one CPU.  See _parse_cpu_info for
    387                         the format.
    388 
    389         >>> _info_io.seek(0); _info_io.truncate()
    390         >>> _testobj._check_one_cpu({
    391         ...    "processor": 0,
    392         ...    "model name": "LEGv7 Processor"})
    393         >>> print _info_io.getvalue()
    394         CPU 0: Not an ARM, skipping: LEGv7 Processor
    395 
    396         >>> _info_io.seek(0); _info_io.truncate()
    397         >>> _testobj._check_one_cpu({
    398         ...    "processor": 1,
    399         ...    "model name": "ARMv99 Processor"})
    400         >>> print _info_io.getvalue()
    401         CPU 1: No errata tested; model: ARMv99 Processor
    402         """
    403         if cpuinfo["model name"].startswith("ARMv7"):
    404             self._check_one_armv7(cpuinfo)
    405         elif cpuinfo["model name"].startswith("ARM"):
    406             logging.info("CPU %d: No errata tested; model: %s",
    407                          cpuinfo["processor"], cpuinfo["model name"])
    408         else:
    409             logging.info("CPU %d: Not an ARM, skipping: %s",
    410                          cpuinfo["processor"], cpuinfo["model name"])
    411 
    412     def run_once(self, doctest=False):
    413         """
    414         Run the test.
    415 
    416         :param doctest: If true we will just run our doctests.  We'll set these
    417                         globals to help our tests:
    418                         - _testobj: An instance of this object.
    419                         - _info_io: A StringIO that's stuffed into logging.info
    420                           so we can see what was written there.
    421         ...
    422         """
    423         if doctest:
    424             import doctest, inspect, StringIO
    425             global _testobj, _info_io
    426 
    427             # Keep a backup of _get_regid_to_val() since tests will clobber.
    428             old_get_regid_to_val = self._get_regid_to_val
    429 
    430             # Mock out logging.info to help tests.
    431             _info_io = StringIO.StringIO()
    432             old_logging_info = logging.info
    433             logging.info = lambda fmt, *args: _info_io.write(fmt % args)
    434 
    435             # Stash an object in a global to help tests
    436             _testobj = self
    437             try:
    438                 failure_count, test_count = doctest.testmod(
    439                     inspect.getmodule(self), optionflags=doctest.ELLIPSIS)
    440             finally:
    441                 logging.info = old_logging_info
    442 
    443                 # Technically don't need to clean this up, but let's be nice.
    444                 self._get_regid_to_val = old_get_regid_to_val
    445 
    446             logging.info("Doctest ran %d tests, had %d failures",
    447                          test_count, failure_count)
    448             return
    449 
    450         cpuinfo = self._parse_cpu_info(utils.read_file('/proc/cpuinfo'))
    451 
    452         for cpu_id in sorted(cpuinfo.keys()):
    453             self._check_one_cpu(cpuinfo[cpu_id])
    454 
    455         sys_power.do_suspend(self.SECS_TO_SUSPEND)
    456 
    457         for cpu_id in sorted(cpuinfo.keys()):
    458             self._check_one_cpu(cpuinfo[cpu_id])
    459