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