1#!/usr/bin/env python 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 for line in fd: 99 line = line.rstrip() 100 101 # Omit anything after the last comma 102 words = line.split(',')[:-1] 103 data += [word + ',' for word in words] 104 microcodes[name] = Microcode(name, data) 105 return date, license_text, microcodes 106 107 108def List(date, microcodes, model): 109 """List the available microcode chunks 110 111 Args: 112 date: Date of the microcode file 113 microcodes: Dict of Microcode objects indexed by name 114 model: Model string to search for, or None 115 """ 116 print 'Date: %s' % date 117 if model: 118 mcode_list, tried = FindMicrocode(microcodes, model.lower()) 119 print 'Matching models %s:' % (', '.join(tried)) 120 else: 121 print 'All models:' 122 mcode_list = [microcodes[m] for m in microcodes.keys()] 123 for mcode in mcode_list: 124 print '%-20s: model %s' % (mcode.name, mcode.model) 125 126def FindMicrocode(microcodes, model): 127 """Find all the microcode chunks which match the given model. 128 129 This model is something like 306a9 (the value returned in eax from 130 cpuid(1) when running on Intel CPUs). But we allow a partial match, 131 omitting the last 1 or two characters to allow many families to have the 132 same microcode. 133 134 If the model name is ambiguous we return a list of matches. 135 136 Args: 137 microcodes: Dict of Microcode objects indexed by name 138 model: String containing model name to find 139 Returns: 140 Tuple: 141 List of matching Microcode objects 142 List of abbreviations we tried 143 """ 144 # Allow a full name to be used 145 mcode = microcodes.get(model) 146 if mcode: 147 return [mcode], [] 148 149 tried = [] 150 found = [] 151 for i in range(3): 152 abbrev = model[:-i] if i else model 153 tried.append(abbrev) 154 for mcode in microcodes.values(): 155 if mcode.model.startswith(abbrev): 156 found.append(mcode) 157 if found: 158 break 159 return found, tried 160 161def CreateFile(date, license_text, mcodes, outfile): 162 """Create a microcode file in U-Boot's .dtsi format 163 164 Args: 165 date: String containing date of original microcode file 166 license: List of text lines for the license file 167 mcodes: Microcode objects to write (normally only 1) 168 outfile: Filename to write to ('-' for stdout) 169 """ 170 out = '''/*%s 171 * --- 172 * This is a device tree fragment. Use #include to add these properties to a 173 * node. 174 * 175 * Date: %s 176 */ 177 178compatible = "intel,microcode"; 179intel,header-version = <%d>; 180intel,update-revision = <%#x>; 181intel,date-code = <%#x>; 182intel,processor-signature = <%#x>; 183intel,checksum = <%#x>; 184intel,loader-revision = <%d>; 185intel,processor-flags = <%#x>; 186 187/* The first 48-bytes are the public header which repeats the above data */ 188data = <%s 189\t>;''' 190 words = '' 191 add_comments = len(mcodes) > 1 192 for mcode in mcodes: 193 if add_comments: 194 words += '\n/* %s */' % mcode.name 195 for i in range(len(mcode.words)): 196 if not (i & 3): 197 words += '\n' 198 val = mcode.words[i] 199 # Change each word so it will be little-endian in the FDT 200 # This data is needed before RAM is available on some platforms so 201 # we cannot do an endianness swap on boot. 202 val = struct.unpack("<I", struct.pack(">I", val))[0] 203 words += '\t%#010x' % val 204 205 # Use the first microcode for the headers 206 mcode = mcodes[0] 207 208 # Take care to avoid adding a space before a tab 209 text = '' 210 for line in license_text: 211 if line[0] == '\t': 212 text += '\n *' + line 213 else: 214 text += '\n * ' + line 215 args = [text, date] 216 args += [mcode.words[i] for i in range(7)] 217 args.append(words) 218 if outfile == '-': 219 print out % tuple(args) 220 else: 221 if not outfile: 222 if not os.path.exists(MICROCODE_DIR): 223 print >> sys.stderr, "Creating directory '%s'" % MICROCODE_DIR 224 os.makedirs(MICROCODE_DIR) 225 outfile = os.path.join(MICROCODE_DIR, mcode.name + '.dtsi') 226 print >> sys.stderr, "Writing microcode for '%s' to '%s'" % ( 227 ', '.join([mcode.name for mcode in mcodes]), outfile) 228 with open(outfile, 'w') as fd: 229 print >> fd, out % tuple(args) 230 231def MicrocodeTool(): 232 """Run the microcode tool""" 233 commands = 'create,license,list'.split(',') 234 parser = OptionParser() 235 parser.add_option('-d', '--mcfile', type='string', action='store', 236 help='Name of microcode.dat file') 237 parser.add_option('-H', '--headerfile', type='string', action='append', 238 help='Name of .h file containing microcode') 239 parser.add_option('-m', '--model', type='string', action='store', 240 help="Model name to extract ('all' for all)") 241 parser.add_option('-M', '--multiple', type='string', action='store', 242 help="Allow output of multiple models") 243 parser.add_option('-o', '--outfile', type='string', action='store', 244 help='Filename to use for output (- for stdout), default is' 245 ' %s/<name>.dtsi' % MICROCODE_DIR) 246 parser.usage += """ command 247 248 Process an Intel microcode file (use -h for help). Commands: 249 250 create Create microcode .dtsi file for a model 251 list List available models in microcode file 252 license Print the license 253 254 Typical usage: 255 256 ./tools/microcode-tool -d microcode.dat -m 306a create 257 258 This will find the appropriate file and write it to %s.""" % MICROCODE_DIR 259 260 (options, args) = parser.parse_args() 261 if not args: 262 parser.error('Please specify a command') 263 cmd = args[0] 264 if cmd not in commands: 265 parser.error("Unknown command '%s'" % cmd) 266 267 if (not not options.mcfile) != (not not options.mcfile): 268 parser.error("You must specify either header files or a microcode file, not both") 269 if options.headerfile: 270 date, license_text, microcodes = ParseHeaderFiles(options.headerfile) 271 elif options.mcfile: 272 date, license_text, microcodes = ParseFile(options.mcfile) 273 else: 274 parser.error('You must specify a microcode file (or header files)') 275 276 if cmd == 'list': 277 List(date, microcodes, options.model) 278 elif cmd == 'license': 279 print '\n'.join(license_text) 280 elif cmd == 'create': 281 if not options.model: 282 parser.error('You must specify a model to create') 283 model = options.model.lower() 284 if options.model == 'all': 285 options.multiple = True 286 mcode_list = microcodes.values() 287 tried = [] 288 else: 289 mcode_list, tried = FindMicrocode(microcodes, model) 290 if not mcode_list: 291 parser.error("Unknown model '%s' (%s) - try 'list' to list" % 292 (model, ', '.join(tried))) 293 if not options.multiple and len(mcode_list) > 1: 294 parser.error("Ambiguous model '%s' (%s) matched %s - try 'list' " 295 "to list or specify a particular file" % 296 (model, ', '.join(tried), 297 ', '.join([m.name for m in mcode_list]))) 298 CreateFile(date, license_text, mcode_list, options.outfile) 299 else: 300 parser.error("Unknown command '%s'" % cmd) 301 302if __name__ == "__main__": 303 MicrocodeTool() 304