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