1# Copyright (C) 2021-2022 Intel Corporation.
2#
3# SPDX-License-Identifier: BSD-3-Clause
4#
5
6"""Base classes and infrastructure for CPUID and MSR decoding"""
7
8from __future__ import print_function
9import sys
10import re
11import functools
12import inspect
13import operator
14import textwrap
15import logging
16from collections import namedtuple
17from inspectorlib import external_tools
18
19_wrapper = textwrap.TextWrapper(width=78, initial_indent='  ', subsequent_indent='    ')
20regex_hex = "0x[0-9a-f]+"
21
22class cpuid_result(namedtuple('cpuid_result', ['eax', 'ebx', 'ecx', 'edx'])):
23    __slots__ = ()
24
25    def __repr__(self):
26        return "cpuid_result(eax={eax:#010x}, ebx={ebx:#010x}, ecx={ecx:#010x}, edx={edx:#010x})".format(**self._asdict())
27
28def cpuid(cpu_id, leaf, subleaf):
29    result = external_tools.run(["cpuid", "-l", str(leaf), "-s", str(subleaf), "-r"], check=True)
30    stdout = result.stdout.decode("ascii").replace("\n", "")
31    regex = re.compile(f"CPU {cpu_id}:[^:]*: eax=({regex_hex}) ebx=({regex_hex}) ecx=({regex_hex}) edx=({regex_hex})")
32    m = regex.search(stdout)
33    if m:
34        regs = list(map(lambda idx: int(m.group(idx), base=16), range(1, 5)))
35    else:
36        regs = [0] * 4
37    return cpuid_result(*regs)
38
39class CPUID(object):
40    # Subclasses must define a "leaf" field as part of the class definition.
41
42    def __init__(self, regs):
43        self.regs = regs
44
45    @classmethod
46    def read(cls, cpu_id, subleaf=0):
47        r = cls(cpuid(cpu_id, cls.leaf, subleaf))
48        r.cpu_id = cpu_id
49        r.subleaf = subleaf
50        return r
51
52    # FIXME: This allows getting subleaves, but requires having an instance of
53    # the class first, which means always reading subleaf 0 and then the
54    # desired subleaf.
55    def __getitem__(self, subleaf):
56        return self.read(self.cpu_id, subleaf)
57
58    def __eq__(self, other):
59        return self.regs == other.regs
60
61    def __ne__(self, other):
62        return self.regs != other.regs
63
64    def __str__(self):
65        T = type(self)
66        fields = dict((regnum, {}) for regnum in range(len(self.regs._fields)))
67        properties = list()
68        for field_name in dir(T):
69            field = getattr(T, field_name)
70            if isinstance(field, cpuidfield):
71                fields[field.reg][field_name] = field
72            elif isinstance(field, property):
73                properties.append(field_name)
74
75        heading = "CPU ID {:#x} -- ".format(self.cpu_id)
76        heading += "CPUID (EAX={:#x}".format(self.leaf)
77        if self.subleaf:
78            heading += ", ECX={:#x}".format(self.subleaf)
79        heading += ")"
80        s = heading + "\n" + "-"*len(heading) + "\n"
81        doc = inspect.getdoc(self)
82        if doc:
83            s += doc + "\n"
84
85        def format_range(msb, lsb):
86            if msb == lsb:
87                return "[{}]".format(msb)
88            return "[{}:{}]".format(msb, lsb)
89        def format_field(msb, lsb, value):
90            """Field formatter that special-cases single bits and drops the 0x"""
91            if msb == lsb:
92                return str(value)
93            return "{:#x}".format(value)
94        for regnum, regname in enumerate(self.regs._fields):
95            s += "\n"
96            s1 = "  {}={:#010x} ".format(regname, self.regs[regnum])
97            s += s1
98            inner = ("\n " + " " * len(s1)).join(
99                    "{}{} {}={}".format(regname, format_range(field.msb, field.lsb), field_name, format_field(field.msb, field.lsb, getattr(self, field_name)))
100                for field_name, field in sorted(fields[regnum].items(), key=(lambda x: x[1].lsb))
101                )
102            if inner:
103                s += " {}".format(inner)
104
105        properties = sorted(set(properties))
106        if len(properties):
107            s += "\n  Attributes derived from one or more fields:"
108            for property_name in properties:
109                s += '\n'
110                temp = "{}={}".format(property_name, getattr(self, property_name))
111                s += '\n'.join(_wrapper.wrap(temp))
112        return s
113
114class cpuidfield(property):
115    def __init__(self, reg, msb, lsb, doc="Bogus"):
116        self.reg = reg
117        self.msb = msb
118        self.lsb = lsb
119
120        max_value = (1 << (msb - lsb + 1)) - 1
121        field_mask = max_value << lsb
122
123        def getter(self):
124            return (self.regs[reg] & field_mask) >> lsb
125        super(cpuidfield, self).__init__(getter, doc=doc)
126
127class MSR(object):
128    # Subclasses must define a "addr" field as part of the class definition.
129
130    def __init__(self, value=0):
131        self.value = value
132
133    def __eq__(self, other):
134        return self.value == other.value
135
136    def __ne__(self, other):
137        return self.value != other.value
138
139    @classmethod
140    def rdmsr(cls, cpu_id: int) -> int:
141        try:
142            with open(f'/dev/cpu/{cpu_id}/msr', 'rb', buffering=0) as msr_reader:
143                msr_reader.seek(cls.addr)
144                r = msr_reader.read(8)
145                r = cls(int.from_bytes(r, 'little'))
146        except FileNotFoundError:
147            logging.critical(f"Missing CPU MSR file at /dev/cpu/{cpu_id}/msr. Check the value of CONFIG_X86_MSR " \
148                             "in the kernel config.  Set it to 'Y' and rebuild the kernel. Then rerun the Board Inspector.")
149            sys.exit(1)
150
151        r.cpu_id = cpu_id
152        return r
153
154    def wrmsr(self, cpu_id=None):
155        if cpu_id is None:
156            cpu_id = self.cpu_id
157        try:
158            with open(f'/dev/cpu/{cpu_id}/msr', 'wb', buffering=0) as msr_reader:
159                msr_reader.seek(self.addr)
160                r = msr_reader.write(int.to_bytes(self.value, 8, 'little'))
161        except FileNotFoundError:
162            logging.critical(f"Missing CPU MSR file at /dev/cpu/{cpu_id}/msr. Check the value of CONFIG_X86_MSR " \
163                             "in the kernel config.  Set it to 'Y' and rebuild the kernel. Then rerun the Board Inspector.")
164            sys.exit(1)
165
166    def __str__(self):
167        T = type(self)
168        fields = {}
169        properties = []
170        for field_name in dir(T):
171            field = getattr(T, field_name)
172            if isinstance(field, msrfield):
173                fields[field_name] = field
174            elif isinstance(field, property):
175                properties.append(field_name)
176
177        heading = "CPU ID {:#x} -- ".format(self.cpu_id)
178        heading += "MSR {:#x}".format(self.addr)
179        s = heading + "\n" + "-"*len(heading) + "\n"
180        doc = inspect.getdoc(self)
181        if doc:
182            s += doc + "\n\n"
183        s += "MSR {:#x}".format(self.addr)
184        if self.value is None:
185            s += ' value=GPF'
186            return s
187
188        s += ' value={:#x}'.format(self.value)
189
190        for field_name, field in sorted(fields.items(), key=(lambda x: x[1].lsb)):
191            s += '\n'
192            temp = "[{}:{}] {}={:#x}".format(field.msb, field.lsb, field_name, getattr(self, field_name))
193            # FIXME: check wrapper, and use a hanging indent to wrap the docstring to len(temp)+1
194            if field.__doc__:
195                temp += " " + inspect.getdoc(field)
196            s += '\n'.join(_wrapper.wrap(temp))
197
198        if properties:
199            s += "\n  Attributes derived from one or more fields:"
200            for property_name in sorted(properties):
201                s += '\n'
202                temp = "{}={}".format(property_name, getattr(self, property_name))
203                # FIXME: check wrapper, get the property documentation string if any, and use a hanging indent to wrap the docstring to len(temp)+1
204                s += '\n'.join(_wrapper.wrap(temp))
205        return s
206
207class msrfield(property):
208
209    def __init__(self, msb, lsb, doc=None):
210        self.msb = msb
211        self.lsb = lsb
212
213        max_value = (1 << (msb - lsb + 1)) - 1
214        field_mask = max_value << lsb
215
216        def getter(self):
217            return (self.value & field_mask) >> lsb
218
219        def setter(self, value):
220            if value > max_value:
221                if msb == lsb:
222                    field = "[{0}]".format(msb)
223                else:
224                    field = "[{0}:{1}]".format(msb, lsb)
225                raise OverflowError("Internal error: Value {value:#x} too big for MSR {self.addr:#x} field {field}.  " \
226                "Rerun the Board Inspector with `--loglevel debug`.  If this issue persists," \
227                "log a new issue at https://github.com/projectacrn/acrn-hypervisor/issues and attach the full logs.".format(**locals()))
228            self.value = (self.value & ~field_mask) | (value << lsb)
229
230        super(msrfield, self).__init__(getter, setter, doc=doc)
231