1#!/usr/bin/env python3 2# SPDX-License-Identifier: GPL-2.0+ 3# 4# Copyright (c) 2014 Google, Inc 5# 6# Intel microcode update tool 7 8from optparse import OptionParser 9import os 10import re 11import struct 12import sys 13 14MICROCODE_DIR = 'arch/x86/dts/microcode' 15 16class Microcode: 17 """Holds information about the microcode for a particular model of CPU. 18 19 Attributes: 20 name: Name of the CPU this microcode is for, including any version 21 information (e.g. 'm12206a7_00000029') 22 model: Model code string (this is cpuid(1).eax, e.g. '206a7') 23 words: List of hex words containing the microcode. The first 16 words 24 are the public header. 25 """ 26 def __init__(self, name, data): 27 self.name = name 28 # Convert data into a list of hex words 29 self.words = [] 30 for value in ''.join(data).split(','): 31 hexval = value.strip() 32 if hexval: 33 self.words.append(int(hexval, 0)) 34 35 # The model is in the 4rd hex word 36 self.model = '%x' % self.words[3] 37 38def ParseFile(fname): 39 """Parse a micrcode.dat file and return the component parts 40 41 Args: 42 fname: Filename to parse 43 Returns: 44 3-Tuple: 45 date: String containing date from the file's header 46 license_text: List of text lines for the license file 47 microcodes: List of Microcode objects from the file 48 """ 49 re_date = re.compile('/\* *(.* [0-9]{4}) *\*/$') 50 re_license = re.compile('/[^-*+] *(.*)$') 51 re_name = re.compile('/\* *(.*)\.inc *\*/', re.IGNORECASE) 52 microcodes = {} 53 license_text = [] 54 date = '' 55 data = [] 56 name = None 57 with open(fname) as fd: 58 for line in fd: 59 line = line.rstrip() 60 m_date = re_date.match(line) 61 m_license = re_license.match(line) 62 m_name = re_name.match(line) 63 if m_name: 64 if name: 65 microcodes[name] = Microcode(name, data) 66 name = m_name.group(1).lower() 67 data = [] 68 elif m_license: 69 license_text.append(m_license.group(1)) 70 elif m_date: 71 date = m_date.group(1) 72 else: 73 data.append(line) 74 if name: 75 microcodes[name] = Microcode(name, data) 76 return date, license_text, microcodes 77 78def ParseHeaderFiles(fname_list): 79 """Parse a list of header files and return the component parts 80 81 Args: 82 fname_list: List of files to parse 83 Returns: 84 date: String containing date from the file's header 85 license_text: List of text lines for the license file 86 microcodes: List of Microcode objects from the file 87 """ 88 microcodes = {} 89 license_text = [] 90 date = '' 91 name = None 92 for fname in fname_list: 93 name = os.path.basename(fname).lower() 94 name = os.path.splitext(name)[0] 95 data = [] 96 with open(fname) as fd: 97 license_start = False 98 license_end = False 99 for line in fd: 100 line = line.rstrip() 101 102 if len(line) >= 2: 103 if line[0] == '/' and line[1] == '*': 104 license_start = True 105 continue 106 if line[0] == '*' and line[1] == '/': 107 license_end = True 108 continue 109 if license_start and not license_end: 110 # Ignore blank line 111 if len(line) > 0: 112 license_text.append(line) 113 continue 114 # Omit anything after the last comma 115 words = line.split(',')[:-1] 116 data += [word + ',' for word in words] 117 microcodes[name] = Microcode(name, data) 118 return date, license_text, microcodes 119 120 121def List(date, microcodes, model): 122 """List the available microcode chunks 123 124 Args: 125 date: Date of the microcode file 126 microcodes: Dict of Microcode objects indexed by name 127 model: Model string to search for, or None 128 """ 129 print('Date: %s' % date) 130 if model: 131 mcode_list, tried = FindMicrocode(microcodes, model.lower()) 132 print('Matching models %s:' % (', '.join(tried))) 133 else: 134 print('All models:') 135 mcode_list = [microcodes[m] for m in list(microcodes.keys())] 136 for mcode in mcode_list: 137 print('%-20s: model %s' % (mcode.name, mcode.model)) 138 139def FindMicrocode(microcodes, model): 140 """Find all the microcode chunks which match the given model. 141 142 This model is something like 306a9 (the value returned in eax from 143 cpuid(1) when running on Intel CPUs). But we allow a partial match, 144 omitting the last 1 or two characters to allow many families to have the 145 same microcode. 146 147 If the model name is ambiguous we return a list of matches. 148 149 Args: 150 microcodes: Dict of Microcode objects indexed by name 151 model: String containing model name to find 152 Returns: 153 Tuple: 154 List of matching Microcode objects 155 List of abbreviations we tried 156 """ 157 # Allow a full name to be used 158 mcode = microcodes.get(model) 159 if mcode: 160 return [mcode], [] 161 162 tried = [] 163 found = [] 164 for i in range(3): 165 abbrev = model[:-i] if i else model 166 tried.append(abbrev) 167 for mcode in list(microcodes.values()): 168 if mcode.model.startswith(abbrev): 169 found.append(mcode) 170 if found: 171 break 172 return found, tried 173 174def CreateFile(date, license_text, mcodes, outfile): 175 """Create a microcode file in U-Boot's .dtsi format 176 177 Args: 178 date: String containing date of original microcode file 179 license: List of text lines for the license file 180 mcodes: Microcode objects to write (normally only 1) 181 outfile: Filename to write to ('-' for stdout) 182 """ 183 out = '''/*%s 184 * --- 185 * This is a device tree fragment. Use #include to add these properties to a 186 * node. 187 * 188 * Date: %s 189 */ 190 191compatible = "intel,microcode"; 192intel,header-version = <%d>; 193intel,update-revision = <%#x>; 194intel,date-code = <%#x>; 195intel,processor-signature = <%#x>; 196intel,checksum = <%#x>; 197intel,loader-revision = <%d>; 198intel,processor-flags = <%#x>; 199 200/* The first 48-bytes are the public header which repeats the above data */ 201data = <%s 202\t>;''' 203 words = '' 204 add_comments = len(mcodes) > 1 205 for mcode in mcodes: 206 if add_comments: 207 words += '\n/* %s */' % mcode.name 208 for i in range(len(mcode.words)): 209 if not (i & 3): 210 words += '\n' 211 val = mcode.words[i] 212 # Change each word so it will be little-endian in the FDT 213 # This data is needed before RAM is available on some platforms so 214 # we cannot do an endianness swap on boot. 215 val = struct.unpack("<I", struct.pack(">I", val))[0] 216 words += '\t%#010x' % val 217 218 # Use the first microcode for the headers 219 mcode = mcodes[0] 220 221 # Take care to avoid adding a space before a tab 222 text = '' 223 for line in license_text: 224 if line[0] == '\t': 225 text += '\n *' + line 226 else: 227 text += '\n * ' + line 228 args = [text, date] 229 args += [mcode.words[i] for i in range(7)] 230 args.append(words) 231 if outfile == '-': 232 print(out % tuple(args)) 233 else: 234 if not outfile: 235 if not os.path.exists(MICROCODE_DIR): 236 print("Creating directory '%s'" % MICROCODE_DIR, file=sys.stderr) 237 os.makedirs(MICROCODE_DIR) 238 outfile = os.path.join(MICROCODE_DIR, mcode.name + '.dtsi') 239 print("Writing microcode for '%s' to '%s'" % ( 240 ', '.join([mcode.name for mcode in mcodes]), outfile), file=sys.stderr) 241 with open(outfile, 'w') as fd: 242 print(out % tuple(args), file=fd) 243 244def MicrocodeTool(): 245 """Run the microcode tool""" 246 commands = 'create,license,list'.split(',') 247 parser = OptionParser() 248 parser.add_option('-d', '--mcfile', type='string', action='store', 249 help='Name of microcode.dat file') 250 parser.add_option('-H', '--headerfile', type='string', action='append', 251 help='Name of .h file containing microcode') 252 parser.add_option('-m', '--model', type='string', action='store', 253 help="Model name to extract ('all' for all)") 254 parser.add_option('-M', '--multiple', type='string', action='store', 255 help="Allow output of multiple models") 256 parser.add_option('-o', '--outfile', type='string', action='store', 257 help='Filename to use for output (- for stdout), default is' 258 ' %s/<name>.dtsi' % MICROCODE_DIR) 259 parser.usage += """ command 260 261 Process an Intel microcode file (use -h for help). Commands: 262 263 create Create microcode .dtsi file for a model 264 list List available models in microcode file 265 license Print the license 266 267 Typical usage: 268 269 ./tools/microcode-tool -d microcode.dat -m 306a create 270 271 This will find the appropriate file and write it to %s.""" % MICROCODE_DIR 272 273 (options, args) = parser.parse_args() 274 if not args: 275 parser.error('Please specify a command') 276 cmd = args[0] 277 if cmd not in commands: 278 parser.error("Unknown command '%s'" % cmd) 279 280 if (not not options.mcfile) != (not not options.mcfile): 281 parser.error("You must specify either header files or a microcode file, not both") 282 if options.headerfile: 283 date, license_text, microcodes = ParseHeaderFiles(options.headerfile) 284 elif options.mcfile: 285 date, license_text, microcodes = ParseFile(options.mcfile) 286 else: 287 parser.error('You must specify a microcode file (or header files)') 288 289 if cmd == 'list': 290 List(date, microcodes, options.model) 291 elif cmd == 'license': 292 print('\n'.join(license_text)) 293 elif cmd == 'create': 294 if not options.model: 295 parser.error('You must specify a model to create') 296 model = options.model.lower() 297 if options.model == 'all': 298 options.multiple = True 299 mcode_list = list(microcodes.values()) 300 tried = [] 301 else: 302 mcode_list, tried = FindMicrocode(microcodes, model) 303 if not mcode_list: 304 parser.error("Unknown model '%s' (%s) - try 'list' to list" % 305 (model, ', '.join(tried))) 306 if not options.multiple and len(mcode_list) > 1: 307 parser.error("Ambiguous model '%s' (%s) matched %s - try 'list' " 308 "to list or specify a particular file" % 309 (model, ', '.join(tried), 310 ', '.join([m.name for m in mcode_list]))) 311 CreateFile(date, license_text, mcode_list, options.outfile) 312 else: 313 parser.error("Unknown command '%s'" % cmd) 314 315if __name__ == "__main__": 316 MicrocodeTool() 317