• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
5import logging
6
7from autotest_lib.client.bin import test, utils
8from autotest_lib.client.common_lib import error
9
10from autotest_lib.client.cros import sys_power
11
12
13class 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